mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 10:31:50 +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
|
||||
return {
|
||||
status = 200,
|
||||
headers = {"Content-Type: text/plain"},
|
||||
headers = {["Content-Type"] = "text/plain"},
|
||||
body = "Hello, " .. request.query
|
||||
}
|
||||
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:
|
||||
|
||||
* 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`);
|
||||
* 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
|
||||
|
|
@ -213,7 +213,7 @@ The `request` class has the following fields and methods:
|
|||
request.method -> string, e.g. "GET"
|
||||
request.path -> string, decoded path without the query string
|
||||
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.remote_addr -> string
|
||||
request.remote_port -> int
|
||||
|
|
@ -226,14 +226,14 @@ request:json() -> any
|
|||
request:respond(
|
||||
[optional] status: int=200,
|
||||
[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.
|
||||
network.http_json(
|
||||
data: any,
|
||||
[optional] status: int=200,
|
||||
[optional] headers: table<string>
|
||||
[optional] headers: table<string,string>
|
||||
) --> table
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ network.http_open(
|
|||
|
||||
Ответить на запрос `handler` может двумя способами:
|
||||
|
||||
* вернуть таблицу ответа `{status: int, headers: table<string>, body: string}`
|
||||
* вернуть таблицу ответа `{status: int, headers: table<string,string>, body: string}`
|
||||
(любое поле можно опустить; `status` по умолчанию равен `200`);
|
||||
* или самостоятельно вызвать `request:respond(status, body, headers)`,
|
||||
например из корутины, для отложенного ответа. В этом случае возвращаемое
|
||||
|
|
@ -282,7 +282,7 @@ network.http_open(
|
|||
request.method -> string, например "GET"
|
||||
request.path -> string, декодированный путь без строки запроса
|
||||
request.query -> string, необработанная строка запроса (часть после '?', если есть)
|
||||
request.headers -> table<string>, записи вида "Имя: значение"
|
||||
request.headers -> table<string,string>, {["Имя"] = "значение", ...}
|
||||
request.body -> string|Bytearray
|
||||
request.remote_addr -> string
|
||||
request.remote_port -> int
|
||||
|
|
@ -295,14 +295,14 @@ request:json() -> any
|
|||
request:respond(
|
||||
[опционально] status: int=200,
|
||||
[опционально] body: string|Bytearray,
|
||||
[опционально] headers: table<string>
|
||||
[опционально] headers: table<string,string>
|
||||
)
|
||||
|
||||
-- Собирает таблицу JSON-ответа, готовую для возврата из обработчика.
|
||||
network.http_json(
|
||||
data: any,
|
||||
[опционально] status: int=200,
|
||||
[опционально] headers: table<string>
|
||||
[опционально] headers: table<string,string>
|
||||
) -> table
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,10 @@ local HttpRequest = {__index={
|
|||
return
|
||||
end
|
||||
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,
|
||||
json=function(self)
|
||||
return json.parse(self.body)
|
||||
|
|
@ -252,7 +255,7 @@ end
|
|||
network.http_json = function(data, status, headers)
|
||||
return {
|
||||
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)
|
||||
}
|
||||
end
|
||||
|
|
@ -292,7 +295,7 @@ function Router.__call(self, request)
|
|||
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
|
||||
|
||||
local function clean(iterable, checkFun, ...)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,25 @@ static std::vector<std::string> read_headers(lua::State* L, int index) {
|
|||
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 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;
|
||||
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) {
|
||||
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::rawseti(L, 6);
|
||||
|
||||
lua::createtable(L, req.headers.size(), 0);
|
||||
for (size_t j = 0; j < req.headers.size(); j++) {
|
||||
lua::pushlstring(L, req.headers[j]);
|
||||
lua::rawseti(L, j + 1);
|
||||
lua::createtable(L, 0, req.headers.size());
|
||||
for (const auto& header : req.headers) {
|
||||
lua::pushlstring(L, header.second);
|
||||
lua::setfield(L, header.first);
|
||||
}
|
||||
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);
|
||||
for (const auto& header : headers) {
|
||||
if (header.find(':') == std::string::npos) continue;
|
||||
auto [hname, hvalue] = util::split_at(header, ':');
|
||||
util::trim(hname);
|
||||
if (util::lower_case(hname) == lname) {
|
||||
if (util::lower_case(header.first) == lname) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -769,7 +769,7 @@ namespace {
|
|||
out += "HTTP/1.1 " + std::to_string(response.status) + " " +
|
||||
http_reason_phrase(response.status) + "\r\n";
|
||||
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")) {
|
||||
out += "Content-Length: " + std::to_string(response.body.size()) + "\r\n";
|
||||
|
|
@ -990,7 +990,7 @@ namespace {
|
|||
size_t headEnd;
|
||||
while ((headEnd = buffer.find("\r\n\r\n")) == std::string::npos) {
|
||||
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;
|
||||
}
|
||||
if (!http_recv_more(descriptor, buffer)) {
|
||||
|
|
@ -1012,7 +1012,7 @@ namespace {
|
|||
std::string target = tokens.size() > 1 ? tokens[1] : "";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1024,7 +1024,7 @@ namespace {
|
|||
}
|
||||
path = url_decode(path);
|
||||
|
||||
std::vector<std::string> headers;
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
std::string contentLength;
|
||||
std::string transferEncoding;
|
||||
|
||||
|
|
@ -1039,7 +1039,6 @@ namespace {
|
|||
auto [name, value] = util::split_at(line, ':');
|
||||
util::trim(name);
|
||||
util::trim(value);
|
||||
headers.push_back(name + ": " + value);
|
||||
|
||||
auto lname = util::lower_case(name);
|
||||
if (lname == "content-length") {
|
||||
|
|
@ -1047,10 +1046,12 @@ namespace {
|
|||
} else if (lname == "transfer-encoding") {
|
||||
transferEncoding = util::lower_case(value);
|
||||
}
|
||||
|
||||
headers.emplace_back(std::move(name), std::move(value));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1059,13 +1060,13 @@ namespace {
|
|||
try {
|
||||
bodyLength = std::stoull(contentLength);
|
||||
} catch (...) {
|
||||
finish({400, {"Content-Type: text/plain"}, http_reason_phrase(400)});
|
||||
finish({400, {{"Content-Type", "text/plain"}}, http_reason_phrase(400)});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1112,7 +1113,7 @@ namespace {
|
|||
response = std::move(pending->response);
|
||||
} else {
|
||||
response.status = 503;
|
||||
response.headers = {"Content-Type: text/plain"};
|
||||
response.headers = {{"Content-Type", "text/plain"}};
|
||||
response.body = http_reason_phrase(503);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "delegates.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ namespace network {
|
|||
std::string method;
|
||||
std::string path;
|
||||
std::string query;
|
||||
std::vector<std::string> headers;
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
std::string body;
|
||||
std::string remoteAddr;
|
||||
int remotePort = 0;
|
||||
|
|
@ -52,7 +53,7 @@ namespace network {
|
|||
|
||||
struct HttpServerResponse {
|
||||
int status = 200;
|
||||
std::vector<std::string> headers;
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue