mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-14 15:31:49 +00:00
improve headers api
This commit is contained in:
parent
e8d36427ba
commit
4667b801d3
7 changed files with 57 additions and 33 deletions
|
|
@ -10,7 +10,7 @@ do
|
||||||
if request.path == "/hello" then
|
if request.path == "/hello" then
|
||||||
return {
|
return {
|
||||||
status = 200,
|
status = 200,
|
||||||
headers = {"Content-Type: text/plain"},
|
headers = {["Content-Type"] = "text/plain"},
|
||||||
body = "Hello, " .. request.query
|
body = "Hello, " .. request.query
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ The ServerSocket class for the HTTP server is identical to the TCP server's
|
||||||
|
|
||||||
The `handler` may respond in two ways:
|
The `handler` may respond in two ways:
|
||||||
|
|
||||||
* return a response table `{status: int, headers: table<string>, body: string|Bytearray}`
|
* return a response table `{status: int, headers: table<string,string>, body: string|Bytearray}`
|
||||||
(any field may be omitted; `status` defaults to `200`);
|
(any field may be omitted; `status` defaults to `200`);
|
||||||
* or call `request:respond(status, body, headers)` itself, e.g. from a
|
* or call `request:respond(status, body, headers)` itself, e.g. from a
|
||||||
coroutine, for a delayed answer. In that case the handler's return value
|
coroutine, for a delayed answer. In that case the handler's return value
|
||||||
|
|
@ -213,7 +213,7 @@ The `request` class has the following fields and methods:
|
||||||
request.method -> string, e.g. "GET"
|
request.method -> string, e.g. "GET"
|
||||||
request.path -> string, decoded path without the query string
|
request.path -> string, decoded path without the query string
|
||||||
request.query -> string, raw query string (part after '?', if any)
|
request.query -> string, raw query string (part after '?', if any)
|
||||||
request.headers -> table<string>, "Name: value" entries
|
request.headers -> table<string,string>, {["Name"] = "value", ...}
|
||||||
request.body -> string|Bytearray
|
request.body -> string|Bytearray
|
||||||
request.remote_addr -> string
|
request.remote_addr -> string
|
||||||
request.remote_port -> int
|
request.remote_port -> int
|
||||||
|
|
@ -226,14 +226,14 @@ request:json() -> any
|
||||||
request:respond(
|
request:respond(
|
||||||
[optional] status: int=200,
|
[optional] status: int=200,
|
||||||
[optional] body: string|Bytearray,
|
[optional] body: string|Bytearray,
|
||||||
[optional] headers: table<string>
|
[optional] headers: table<string,string>
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Builds a JSON response table ready to be returned from a handler.
|
-- Builds a JSON response table ready to be returned from a handler.
|
||||||
network.http_json(
|
network.http_json(
|
||||||
data: any,
|
data: any,
|
||||||
[optional] status: int=200,
|
[optional] status: int=200,
|
||||||
[optional] headers: table<string>
|
[optional] headers: table<string,string>
|
||||||
) --> table
|
) --> table
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ network.http_open(
|
||||||
|
|
||||||
Ответить на запрос `handler` может двумя способами:
|
Ответить на запрос `handler` может двумя способами:
|
||||||
|
|
||||||
* вернуть таблицу ответа `{status: int, headers: table<string>, body: string}`
|
* вернуть таблицу ответа `{status: int, headers: table<string,string>, body: string}`
|
||||||
(любое поле можно опустить; `status` по умолчанию равен `200`);
|
(любое поле можно опустить; `status` по умолчанию равен `200`);
|
||||||
* или самостоятельно вызвать `request:respond(status, body, headers)`,
|
* или самостоятельно вызвать `request:respond(status, body, headers)`,
|
||||||
например из корутины, для отложенного ответа. В этом случае возвращаемое
|
например из корутины, для отложенного ответа. В этом случае возвращаемое
|
||||||
|
|
@ -282,7 +282,7 @@ network.http_open(
|
||||||
request.method -> string, например "GET"
|
request.method -> string, например "GET"
|
||||||
request.path -> string, декодированный путь без строки запроса
|
request.path -> string, декодированный путь без строки запроса
|
||||||
request.query -> string, необработанная строка запроса (часть после '?', если есть)
|
request.query -> string, необработанная строка запроса (часть после '?', если есть)
|
||||||
request.headers -> table<string>, записи вида "Имя: значение"
|
request.headers -> table<string,string>, {["Имя"] = "значение", ...}
|
||||||
request.body -> string|Bytearray
|
request.body -> string|Bytearray
|
||||||
request.remote_addr -> string
|
request.remote_addr -> string
|
||||||
request.remote_port -> int
|
request.remote_port -> int
|
||||||
|
|
@ -295,14 +295,14 @@ request:json() -> any
|
||||||
request:respond(
|
request:respond(
|
||||||
[опционально] status: int=200,
|
[опционально] status: int=200,
|
||||||
[опционально] body: string|Bytearray,
|
[опционально] body: string|Bytearray,
|
||||||
[опционально] headers: table<string>
|
[опционально] headers: table<string,string>
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Собирает таблицу JSON-ответа, готовую для возврата из обработчика.
|
-- Собирает таблицу JSON-ответа, готовую для возврата из обработчика.
|
||||||
network.http_json(
|
network.http_json(
|
||||||
data: any,
|
data: any,
|
||||||
[опционально] status: int=200,
|
[опционально] status: int=200,
|
||||||
[опционально] headers: table<string>
|
[опционально] headers: table<string,string>
|
||||||
) -> table
|
) -> table
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,10 @@ local HttpRequest = {__index={
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.responded = true
|
self.responded = true
|
||||||
http_respond(self.server_id, self.id, status or 200, headers or {}, body or "")
|
http_respond(
|
||||||
|
self.server_id, self.id, status or 200,
|
||||||
|
headers or {}, body or ""
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
json=function(self)
|
json=function(self)
|
||||||
return json.parse(self.body)
|
return json.parse(self.body)
|
||||||
|
|
@ -252,7 +255,7 @@ end
|
||||||
network.http_json = function(data, status, headers)
|
network.http_json = function(data, status, headers)
|
||||||
return {
|
return {
|
||||||
status = status or 200,
|
status = status or 200,
|
||||||
headers = table.extend({"Content-Type: application/json"}, headers or {}),
|
headers = table.extend({["Content-Type"]="application/json"}, headers or {}),
|
||||||
body = json.tostring(data)
|
body = json.tostring(data)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
@ -292,7 +295,7 @@ function Router.__call(self, request)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return {status=404, headers={"Content-Type: text/plain"}, body="Not Found"}
|
return {status=404, headers={["Content-Type"]="text/plain"}, body="Not Found"}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function clean(iterable, checkFun, ...)
|
local function clean(iterable, checkFun, ...)
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,25 @@ static std::vector<std::string> read_headers(lua::State* L, int index) {
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<std::pair<std::string, std::string>> read_header_pairs(
|
||||||
|
lua::State* L, int index
|
||||||
|
) {
|
||||||
|
std::vector<std::pair<std::string, std::string>> headers;
|
||||||
|
if (lua::istable(L, index)) {
|
||||||
|
lua::pushnil(L);
|
||||||
|
while (lua::next(L, index)) {
|
||||||
|
if (lua::type(L, -2) == LUA_TSTRING) {
|
||||||
|
headers.emplace_back(
|
||||||
|
std::string(lua::tolstring(L, -2)),
|
||||||
|
std::string(lua::tolstring(L, -1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
static int request_id = 1;
|
static int request_id = 1;
|
||||||
|
|
||||||
static int l_request(lua::State* L, network::Network& network) {
|
static int l_request(lua::State* L, network::Network& network) {
|
||||||
|
|
@ -371,7 +390,7 @@ static int l_http_respond(lua::State* L, network::Network& network) {
|
||||||
|
|
||||||
network::HttpServerResponse response;
|
network::HttpServerResponse response;
|
||||||
response.status = lua::tointeger(L, 3);
|
response.status = lua::tointeger(L, 3);
|
||||||
response.headers = read_headers(L, 4);
|
response.headers = read_header_pairs(L, 4);
|
||||||
|
|
||||||
if (lua::type(L, 5) == LUA_TCDATA) {
|
if (lua::type(L, 5) == LUA_TCDATA) {
|
||||||
response.body = lua::bytearray_as_string(L, 5);
|
response.body = lua::bytearray_as_string(L, 5);
|
||||||
|
|
@ -595,10 +614,10 @@ static int l_pull_events(lua::State* L) {
|
||||||
lua::pushlstring(L, req.query);
|
lua::pushlstring(L, req.query);
|
||||||
lua::rawseti(L, 6);
|
lua::rawseti(L, 6);
|
||||||
|
|
||||||
lua::createtable(L, req.headers.size(), 0);
|
lua::createtable(L, 0, req.headers.size());
|
||||||
for (size_t j = 0; j < req.headers.size(); j++) {
|
for (const auto& header : req.headers) {
|
||||||
lua::pushlstring(L, req.headers[j]);
|
lua::pushlstring(L, header.second);
|
||||||
lua::rawseti(L, j + 1);
|
lua::setfield(L, header.first);
|
||||||
}
|
}
|
||||||
lua::rawseti(L, 7);
|
lua::rawseti(L, 7);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -751,13 +751,13 @@ namespace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool http_header_has(const std::vector<std::string>& headers, const std::string& name) {
|
bool http_header_has(
|
||||||
|
const std::vector<std::pair<std::string, std::string>>& headers,
|
||||||
|
const std::string& name
|
||||||
|
) {
|
||||||
auto lname = util::lower_case(name);
|
auto lname = util::lower_case(name);
|
||||||
for (const auto& header : headers) {
|
for (const auto& header : headers) {
|
||||||
if (header.find(':') == std::string::npos) continue;
|
if (util::lower_case(header.first) == lname) {
|
||||||
auto [hname, hvalue] = util::split_at(header, ':');
|
|
||||||
util::trim(hname);
|
|
||||||
if (util::lower_case(hname) == lname) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -769,7 +769,7 @@ namespace {
|
||||||
out += "HTTP/1.1 " + std::to_string(response.status) + " " +
|
out += "HTTP/1.1 " + std::to_string(response.status) + " " +
|
||||||
http_reason_phrase(response.status) + "\r\n";
|
http_reason_phrase(response.status) + "\r\n";
|
||||||
for (const auto& header : response.headers) {
|
for (const auto& header : response.headers) {
|
||||||
out += header + "\r\n";
|
out += header.first + ": " + header.second + "\r\n";
|
||||||
}
|
}
|
||||||
if (!http_header_has(response.headers, "Content-Length")) {
|
if (!http_header_has(response.headers, "Content-Length")) {
|
||||||
out += "Content-Length: " + std::to_string(response.body.size()) + "\r\n";
|
out += "Content-Length: " + std::to_string(response.body.size()) + "\r\n";
|
||||||
|
|
@ -990,7 +990,7 @@ namespace {
|
||||||
size_t headEnd;
|
size_t headEnd;
|
||||||
while ((headEnd = buffer.find("\r\n\r\n")) == std::string::npos) {
|
while ((headEnd = buffer.find("\r\n\r\n")) == std::string::npos) {
|
||||||
if (buffer.size() > HTTP_MAX_HEADER_SIZE) {
|
if (buffer.size() > HTTP_MAX_HEADER_SIZE) {
|
||||||
finish({431, {"Content-Type: text/plain"}, http_reason_phrase(431)});
|
finish({431, {{"Content-Type", "text/plain"}}, http_reason_phrase(431)});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!http_recv_more(descriptor, buffer)) {
|
if (!http_recv_more(descriptor, buffer)) {
|
||||||
|
|
@ -1012,7 +1012,7 @@ namespace {
|
||||||
std::string target = tokens.size() > 1 ? tokens[1] : "";
|
std::string target = tokens.size() > 1 ? tokens[1] : "";
|
||||||
|
|
||||||
if (method.empty() || target.empty()) {
|
if (method.empty() || target.empty()) {
|
||||||
finish({400, {"Content-Type: text/plain"}, http_reason_phrase(400)});
|
finish({400, {{"Content-Type", "text/plain"}}, http_reason_phrase(400)});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1024,7 +1024,7 @@ namespace {
|
||||||
}
|
}
|
||||||
path = url_decode(path);
|
path = url_decode(path);
|
||||||
|
|
||||||
std::vector<std::string> headers;
|
std::vector<std::pair<std::string, std::string>> headers;
|
||||||
std::string contentLength;
|
std::string contentLength;
|
||||||
std::string transferEncoding;
|
std::string transferEncoding;
|
||||||
|
|
||||||
|
|
@ -1039,7 +1039,6 @@ namespace {
|
||||||
auto [name, value] = util::split_at(line, ':');
|
auto [name, value] = util::split_at(line, ':');
|
||||||
util::trim(name);
|
util::trim(name);
|
||||||
util::trim(value);
|
util::trim(value);
|
||||||
headers.push_back(name + ": " + value);
|
|
||||||
|
|
||||||
auto lname = util::lower_case(name);
|
auto lname = util::lower_case(name);
|
||||||
if (lname == "content-length") {
|
if (lname == "content-length") {
|
||||||
|
|
@ -1047,10 +1046,12 @@ namespace {
|
||||||
} else if (lname == "transfer-encoding") {
|
} else if (lname == "transfer-encoding") {
|
||||||
transferEncoding = util::lower_case(value);
|
transferEncoding = util::lower_case(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers.emplace_back(std::move(name), std::move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transferEncoding.find("chunked") != std::string::npos) {
|
if (transferEncoding.find("chunked") != std::string::npos) {
|
||||||
finish({501, {"Content-Type: text/plain"}, http_reason_phrase(501)});
|
finish({501, {{"Content-Type", "text/plain"}}, http_reason_phrase(501)});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1059,13 +1060,13 @@ namespace {
|
||||||
try {
|
try {
|
||||||
bodyLength = std::stoull(contentLength);
|
bodyLength = std::stoull(contentLength);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
finish({400, {"Content-Type: text/plain"}, http_reason_phrase(400)});
|
finish({400, {{"Content-Type", "text/plain"}}, http_reason_phrase(400)});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bodyLength > HTTP_MAX_BODY_SIZE) {
|
if (bodyLength > HTTP_MAX_BODY_SIZE) {
|
||||||
finish({413, {"Content-Type: text/plain"}, http_reason_phrase(413)});
|
finish({413, {{"Content-Type", "text/plain"}}, http_reason_phrase(413)});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1112,7 +1113,7 @@ namespace {
|
||||||
response = std::move(pending->response);
|
response = std::move(pending->response);
|
||||||
} else {
|
} else {
|
||||||
response.status = 503;
|
response.status = 503;
|
||||||
response.headers = {"Content-Type: text/plain"};
|
response.headers = {{"Content-Type", "text/plain"}};
|
||||||
response.body = http_reason_phrase(503);
|
response.body = http_reason_phrase(503);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "delegates.hpp"
|
#include "delegates.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ namespace network {
|
||||||
std::string method;
|
std::string method;
|
||||||
std::string path;
|
std::string path;
|
||||||
std::string query;
|
std::string query;
|
||||||
std::vector<std::string> headers;
|
std::vector<std::pair<std::string, std::string>> headers;
|
||||||
std::string body;
|
std::string body;
|
||||||
std::string remoteAddr;
|
std::string remoteAddr;
|
||||||
int remotePort = 0;
|
int remotePort = 0;
|
||||||
|
|
@ -52,7 +53,7 @@ namespace network {
|
||||||
|
|
||||||
struct HttpServerResponse {
|
struct HttpServerResponse {
|
||||||
int status = 200;
|
int status = 200;
|
||||||
std::vector<std::string> headers;
|
std::vector<std::pair<std::string, std::string>> headers;
|
||||||
std::string body;
|
std::string body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue