HTTP server functionality is in there.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -31,9 +31,9 @@ private:
|
|||||||
int64_t place_id_;
|
int64_t place_id_;
|
||||||
int64_t thread_id_;
|
int64_t thread_id_;
|
||||||
|
|
||||||
// If the request contains an error, the error
|
// When we detect that we generated an invalid
|
||||||
// message is stored here.
|
// outgoing request, the error is stored here.
|
||||||
eng::string error_;
|
eng::string check_fail_;
|
||||||
|
|
||||||
// If true, verify the server's certificate.
|
// If true, verify the server's certificate.
|
||||||
// True is the default.
|
// True is the default.
|
||||||
@@ -59,9 +59,10 @@ private:
|
|||||||
|
|
||||||
// The content as a string, only for POST requests.
|
// The content as a string, only for POST requests.
|
||||||
eng::string content_;
|
eng::string content_;
|
||||||
|
bool content_assigned_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fail(std::string_view error);
|
void check_fail(std::string_view error);
|
||||||
void send_internal(StreamBuffer *target, bool debug_string) const;
|
void send_internal(StreamBuffer *target, bool debug_string) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -69,17 +70,6 @@ public:
|
|||||||
// All of the fields have empty values.
|
// All of the fields have empty values.
|
||||||
HttpClientRequest();
|
HttpClientRequest();
|
||||||
|
|
||||||
// Get request-related fields.
|
|
||||||
//
|
|
||||||
const eng::string &error() const { return error_; }
|
|
||||||
bool verify_certificate() const { return verify_certificate_; }
|
|
||||||
const eng::string &method() const { return method_; }
|
|
||||||
const eng::string &host() const { return host_; }
|
|
||||||
int port() const { return port_; }
|
|
||||||
const eng::string &path() const { return path_; }
|
|
||||||
const eng::string &mime_type() const { return mime_type_; }
|
|
||||||
const eng::string &content() const { return content_; }
|
|
||||||
|
|
||||||
// Populate an request-related fields one piece at a time.
|
// Populate an request-related fields one piece at a time.
|
||||||
// If you pass an invalid value, or if the field is
|
// If you pass an invalid value, or if the field is
|
||||||
// already set, the routine will generate an error message
|
// already set, the routine will generate an error message
|
||||||
@@ -116,7 +106,10 @@ public:
|
|||||||
void set_config(LuaStack &LS0, LuaSlot tab);
|
void set_config(LuaStack &LS0, LuaSlot tab);
|
||||||
|
|
||||||
// Get or Set the request IDs.
|
// Get or Set the request IDs.
|
||||||
// This class does just stores these.
|
//
|
||||||
|
// This class does not use these fields, it just stores
|
||||||
|
// them as a convenience.
|
||||||
|
//
|
||||||
int64_t request_id() const { return request_id_; }
|
int64_t request_id() const { return request_id_; }
|
||||||
int64_t place_id() const { return place_id_; }
|
int64_t place_id() const { return place_id_; }
|
||||||
int64_t thread_id() const { return thread_id_; }
|
int64_t thread_id() const { return thread_id_; }
|
||||||
@@ -125,25 +118,123 @@ public:
|
|||||||
void set_thread_id(int64_t thread_id) { thread_id_ = thread_id; }
|
void set_thread_id(int64_t thread_id) { thread_id_ = thread_id; }
|
||||||
|
|
||||||
// Get the network target, eg, "cert:host:port"
|
// Get the network target, eg, "cert:host:port"
|
||||||
|
//
|
||||||
eng::string target() const;
|
eng::string target() const;
|
||||||
|
|
||||||
// Verify that the request is error free and that
|
// Verify that the request is error free and that
|
||||||
// defaults have been set.
|
// defaults have been set.
|
||||||
|
//
|
||||||
eng::string check() const;
|
eng::string check() const;
|
||||||
|
|
||||||
// Put the request into the stream, assuming HTTP/1.1
|
// Put the request into the stream, assuming HTTP/1.1
|
||||||
|
//
|
||||||
void send(StreamBuffer *target) const { send_internal(target, false); }
|
void send(StreamBuffer *target) const { send_internal(target, false); }
|
||||||
|
|
||||||
// Serialize and deserialize.
|
// Serialize and deserialize.
|
||||||
|
//
|
||||||
void serialize(StreamBuffer *sb) const;
|
void serialize(StreamBuffer *sb) const;
|
||||||
void deserialize(StreamBuffer *sb);
|
void deserialize(StreamBuffer *sb);
|
||||||
|
|
||||||
// Get the request as a debug string.
|
// Get the request as a debug string.
|
||||||
eng::string DebugString();
|
//
|
||||||
|
eng::string debug_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpServerResponse {
|
||||||
|
private:
|
||||||
|
// When we detect that our own response is
|
||||||
|
// invalid, the error is stored here.
|
||||||
|
//
|
||||||
|
eng::string check_fail_;
|
||||||
|
|
||||||
|
// The status code to send back to the client
|
||||||
|
// as part of the response status line.
|
||||||
|
//
|
||||||
|
int status_;
|
||||||
|
|
||||||
|
// Maximum age for the cache-control directive.
|
||||||
|
//
|
||||||
|
int max_age_;
|
||||||
|
|
||||||
|
// Mime type of the content.
|
||||||
|
//
|
||||||
|
eng::string mime_type_;
|
||||||
|
|
||||||
|
// Content to send to the client.
|
||||||
|
//
|
||||||
|
eng::string content_;
|
||||||
|
bool content_assigned_;
|
||||||
|
|
||||||
|
// If the keep-alive flag is set, then a connection: keep-alive
|
||||||
|
// header is sent. Otherwise, a connection: close is sent.
|
||||||
|
// The keep-alive flag cannot be controlled from Lua. It is
|
||||||
|
// designed to be handled automatically by the server code.
|
||||||
|
//
|
||||||
|
bool keep_alive_;
|
||||||
|
|
||||||
|
// The protocol is taken from the request.
|
||||||
|
//
|
||||||
|
bool http11_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void check_fail(std::string_view error);
|
||||||
|
void send_internal(StreamBuffer *target, bool debug_string) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Construct an empty response.
|
||||||
|
// All of the fields have empty values.
|
||||||
|
//
|
||||||
|
HttpServerResponse();
|
||||||
|
|
||||||
|
void set_status(int status);
|
||||||
|
void set_max_age(int max_age);
|
||||||
|
void set_mime_type(const eng::string &mime_type);
|
||||||
|
void set_content(const eng::string &content);
|
||||||
|
|
||||||
|
void set_status(LuaStack &LS, LuaSlot val);
|
||||||
|
void set_max_age(LuaStack &LS, LuaSlot val);
|
||||||
|
void set_mime_type(LuaStack &LS, LuaSlot val);
|
||||||
|
void set_content(LuaStack &LS, LuaSlot val);
|
||||||
|
|
||||||
|
// Set default values.
|
||||||
|
//
|
||||||
|
void set_defaults();
|
||||||
|
|
||||||
|
// Populate request-related fields from a Lua table.
|
||||||
|
//
|
||||||
|
void set_config(LuaStack &LS0, LuaSlot tab);
|
||||||
|
|
||||||
|
// Set the keep_alive flag.
|
||||||
|
//
|
||||||
|
void set_keep_alive(bool k) { keep_alive_ = k; };
|
||||||
|
|
||||||
|
// Set the protocol.
|
||||||
|
//
|
||||||
|
void set_http11(bool k) { http11_= k; }
|
||||||
|
|
||||||
|
// Store a fail response.
|
||||||
|
//
|
||||||
|
void fail(int status, const eng::string &message);
|
||||||
|
|
||||||
|
// Verify that the response is error free.
|
||||||
|
//
|
||||||
|
eng::string check() const;
|
||||||
|
|
||||||
|
// Get the status of the response.
|
||||||
|
//
|
||||||
|
int status() const { return status_; }
|
||||||
|
bool errstatus() const { return (status_ >= 400)&&(status_ <= 599); }
|
||||||
|
|
||||||
|
// Put the response into the stream, assuming HTTP/1.1
|
||||||
|
//
|
||||||
|
void send(StreamBuffer *target) const { send_internal(target, false); }
|
||||||
|
|
||||||
|
// Get the response as a debug string.
|
||||||
|
//
|
||||||
|
eng::string debug_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
// HttpParser is used for parsing both requests and responses.
|
// HttpParser is used for parsing both requests and responses.
|
||||||
// Stores a status code, an error message, headers, and content.
|
|
||||||
//
|
//
|
||||||
class HttpParser : public eng::nevernew {
|
class HttpParser : public eng::nevernew {
|
||||||
protected:
|
protected:
|
||||||
@@ -181,7 +272,10 @@ protected:
|
|||||||
// Charset of the content before the content was translated to utf-8.
|
// Charset of the content before the content was translated to utf-8.
|
||||||
eng::string charset_;
|
eng::string charset_;
|
||||||
|
|
||||||
// The method, GET, HEAD, or POST. (only when parsing requests)
|
// The protocol: true for HTTP/1.1, false for HTTP/1.0
|
||||||
|
bool http11_;
|
||||||
|
|
||||||
|
// The method: always "GET". (only when parsing requests)
|
||||||
eng::string method_;
|
eng::string method_;
|
||||||
|
|
||||||
// The URL path, not url-encoded. (only when parsing requests)
|
// The URL path, not url-encoded. (only when parsing requests)
|
||||||
@@ -216,10 +310,6 @@ protected:
|
|||||||
//
|
//
|
||||||
void oversized();
|
void oversized();
|
||||||
|
|
||||||
// If the status is not in the range 200-299 (OK),
|
|
||||||
// then discard the content.
|
|
||||||
void clear_content_on_error();
|
|
||||||
|
|
||||||
// Parse a response status line, such as "HTTP/1.1 200 OK"
|
// Parse a response status line, such as "HTTP/1.1 200 OK"
|
||||||
// returns false if we couldn't parse the whole line.
|
// returns false if we couldn't parse the whole line.
|
||||||
//
|
//
|
||||||
@@ -269,9 +359,23 @@ public:
|
|||||||
//
|
//
|
||||||
HttpParser();
|
HttpParser();
|
||||||
|
|
||||||
// Get the parsed status.
|
// Get the parsed status or error message.
|
||||||
//
|
//
|
||||||
int status() const { return status_; }
|
int status() const { return status_; }
|
||||||
|
bool errstatus() const { return (status_ >= 400) && (status_ <= 599); }
|
||||||
|
eng::string error() const { return error_; }
|
||||||
|
|
||||||
|
// Return true if the communication was complete.
|
||||||
|
//
|
||||||
|
bool complete() const { return status_ != 0; }
|
||||||
|
|
||||||
|
// Get the first component of the path.
|
||||||
|
// If the path is empty, return defval.
|
||||||
|
eng::string first_path_component(std::string_view defval) const;
|
||||||
|
|
||||||
|
// Get the parsed protocol.
|
||||||
|
//
|
||||||
|
bool http11() const { return http11_; }
|
||||||
|
|
||||||
// Store the parsed fields into a lua table.
|
// Store the parsed fields into a lua table.
|
||||||
//
|
//
|
||||||
@@ -301,10 +405,6 @@ public:
|
|||||||
int64_t request_id() const { return request_id_; }
|
int64_t request_id() const { return request_id_; }
|
||||||
void set_request_id(int64_t v) { request_id_ = v; }
|
void set_request_id(int64_t v) { request_id_ = v; }
|
||||||
|
|
||||||
// Return true if the communication was complete.
|
|
||||||
//
|
|
||||||
bool complete() const { return status_ != 0; }
|
|
||||||
|
|
||||||
// Return a debug string.
|
// Return a debug string.
|
||||||
//
|
//
|
||||||
eng::string debug_string() const;
|
eng::string debug_string() const;
|
||||||
|
|||||||
@@ -198,7 +198,6 @@ public:
|
|||||||
HttpChannel htchan;
|
HttpChannel htchan;
|
||||||
htchan.channel_ = chan;
|
htchan.channel_ = chan;
|
||||||
http_server_channels_.push_back(htchan);
|
http_server_channels_.push_back(htchan);
|
||||||
stdostream() << "Http Server got new client " << chan->chid() << std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +257,8 @@ public:
|
|||||||
htchan.parsed_bytes_ = chan->in()->fill();
|
htchan.parsed_bytes_ = chan->in()->fill();
|
||||||
if (parser.complete()) {
|
if (parser.complete()) {
|
||||||
StreamBuffer *sb = chan->out();
|
StreamBuffer *sb = chan->out();
|
||||||
sb->ostream() << "HTTP/1.1 200 OK\n\n";
|
HttpServerResponse resp = master_->http_serve(parser);
|
||||||
|
resp.send(sb);
|
||||||
htchan.channel_ = nullptr;
|
htchan.channel_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,9 +246,8 @@ private:
|
|||||||
template<int NARG, int NVAR, int NRET, class... SS>
|
template<int NARG, int NVAR, int NRET, class... SS>
|
||||||
void count_slots(LuaArg &v, SS & ... stackslots)
|
void count_slots(LuaArg &v, SS & ... stackslots)
|
||||||
{
|
{
|
||||||
count_slots<NARG+1, NRET, NVAR>(stackslots...);
|
count_slots<NARG+1, NVAR, NRET>(stackslots...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int NARG, int NVAR, int NRET, class... SS>
|
template<int NARG, int NVAR, int NRET, class... SS>
|
||||||
void count_slots(LuaVar &v, SS & ... stackslots)
|
void count_slots(LuaVar &v, SS & ... stackslots)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,18 @@
|
|||||||
|
|
||||||
namespace sv {
|
namespace sv {
|
||||||
|
|
||||||
|
bool case_insensitive_eq(string_view s1, string_view s2) {
|
||||||
|
if (s1.size() != s2.size()) return false;
|
||||||
|
for (int i = 0; i < int(s1.size()); i++) {
|
||||||
|
char c1 = s1[i];
|
||||||
|
char c2 = s2[i];
|
||||||
|
if (ascii_isupper(c1)) c1 += 'a'-'A';
|
||||||
|
if (ascii_isupper(c2)) c2 += 'a'-'A';
|
||||||
|
if (c1 != c2) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool valid_int64(string_view value) {
|
bool valid_int64(string_view value) {
|
||||||
int64_t result;
|
int64_t result;
|
||||||
const char *last = value.data() + value.size();
|
const char *last = value.data() + value.size();
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ inline bool ascii_isspace(char c) { return (c==' ')||(c=='\t')||(c=='\r')||(c=='
|
|||||||
//
|
//
|
||||||
inline bool isnull(string_view v) { return v.data() == nullptr; }
|
inline bool isnull(string_view v) { return v.data() == nullptr; }
|
||||||
|
|
||||||
|
// Return true if the two strings are equal, ignoring case.
|
||||||
|
//
|
||||||
|
bool case_insensitive_eq(std::string_view s1, std::string_view s2);
|
||||||
|
|
||||||
// Check if numbers can be parsed as int64/double
|
// Check if numbers can be parsed as int64/double
|
||||||
bool valid_double(string_view v);
|
bool valid_double(string_view v);
|
||||||
bool valid_int64(string_view v);
|
bool valid_int64(string_view v);
|
||||||
|
|||||||
@@ -666,11 +666,13 @@ LuaDefine(http_get, "request",
|
|||||||
HttpClientRequest req;
|
HttpClientRequest req;
|
||||||
|
|
||||||
// Parse the request and make sure it's valid.
|
// Parse the request and make sure it's valid.
|
||||||
|
// If not, immediately pass a '400 bad request' back to lua.
|
||||||
|
req.set_method("GET");
|
||||||
req.set_config(LS, request);
|
req.set_config(LS, request);
|
||||||
req.set_defaults();
|
req.set_defaults();
|
||||||
eng::string error = req.check();
|
eng::string error = req.check();
|
||||||
if (!error.empty()) {
|
if (!error.empty()) {
|
||||||
HttpParser::store_fail(LS, response, 400, util::ss("bad request: ", error));
|
HttpParser::store_fail(LS, response, 400, util::ss("Bad Request: ", error));
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -458,6 +458,91 @@ void World::abort_all_http_requests(int status_code, std::string_view error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpServerResponse World::http_serve(const HttpParser &request) {
|
||||||
|
assert(stack_is_clear());
|
||||||
|
HttpServerResponse response;
|
||||||
|
|
||||||
|
// We're only supposed to be passed complete requests.
|
||||||
|
assert(request.complete());
|
||||||
|
|
||||||
|
// If the request is HTTP/1.1, then the response should be HTTP/1.1
|
||||||
|
response.set_http11(request.http11());
|
||||||
|
|
||||||
|
// If the incoming request has already been detected to be
|
||||||
|
// invalid by the HTTP parser, then just send the error
|
||||||
|
// message back to the client without involving lua at all.
|
||||||
|
if (request.errstatus()) {
|
||||||
|
response.fail(request.status(), request.error());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the name of the desired function.
|
||||||
|
std::string_view fn = request.first_path_component("index");
|
||||||
|
if (!sv::is_lua_id(fn)) {
|
||||||
|
response.fail(404, util::ss("not a function name: ", fn));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State *L = state();
|
||||||
|
LuaVar www, func, reqtab;
|
||||||
|
LuaStack LS(L, www, func, reqtab);
|
||||||
|
|
||||||
|
// Get the www class. If there's no such class,
|
||||||
|
// return a 503 Service Unavailable to the client.
|
||||||
|
eng::string err = LS.getclass(www, "www");
|
||||||
|
if (!err.empty()) {
|
||||||
|
response.fail(503, "class www doesn't exist");
|
||||||
|
LS.result();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the closure. If there's no such closure,
|
||||||
|
// return a 404 Not Found to the client.
|
||||||
|
LS.rawget(func, www, fn);
|
||||||
|
if (!LS.isfunction(func)) {
|
||||||
|
response.fail(404, util::ss("no such function: www.", fn));
|
||||||
|
LS.result();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the request into a lua table.
|
||||||
|
request.store(LS, reqtab);
|
||||||
|
|
||||||
|
// Call the function.
|
||||||
|
int oldtop = lua_gettop(L);
|
||||||
|
lua_pushvalue(L, func.index());
|
||||||
|
lua_pushvalue(L, reqtab.index());
|
||||||
|
Gui::store_global_pointer(L, nullptr);
|
||||||
|
open_lthread_state(0, 0, 0, false, false);
|
||||||
|
eng::string msg = traceback_pcall(L, 1, LUA_MULTRET);
|
||||||
|
close_lthread_state();
|
||||||
|
|
||||||
|
// If the call threw an error, return
|
||||||
|
// a 500 Internal Server Error to the client.
|
||||||
|
if (!msg.empty()) {
|
||||||
|
response.fail(500, msg);
|
||||||
|
LS.result();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the call didn't return a single table, return
|
||||||
|
// a 500 Internal Server Error to the client.
|
||||||
|
int newtop = lua_gettop(L);
|
||||||
|
if ((newtop != oldtop + 1) || (!lua_istable(L, newtop))) {
|
||||||
|
response.fail(500, util::ss("lua function www.", fn, " didn't return a table"));
|
||||||
|
LS.result();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to convert the table into a response.
|
||||||
|
// If this results in an error, we don't have to do
|
||||||
|
// anything special, set_config will store the error.
|
||||||
|
response.set_config(LS, LuaSpecial(newtop));
|
||||||
|
response.set_defaults();
|
||||||
|
LS.result();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
void World::run_unittests() {
|
void World::run_unittests() {
|
||||||
assert(stack_is_clear());
|
assert(stack_is_clear());
|
||||||
source_db_.run_unittests();
|
source_db_.run_unittests();
|
||||||
|
|||||||
@@ -206,14 +206,23 @@ public:
|
|||||||
void update_source(const util::LuaSourceVec &source);
|
void update_source(const util::LuaSourceVec &source);
|
||||||
|
|
||||||
// Supply an HTTP response to an outstanding HTTP request.
|
// Supply an HTTP response to an outstanding HTTP request.
|
||||||
|
//
|
||||||
void http_response(const HttpParser &response);
|
void http_response(const HttpParser &response);
|
||||||
void http_responses(const HttpParserVec &responses);
|
void http_responses(const HttpParserVec &responses);
|
||||||
|
|
||||||
// Abort all HTTP requests. This is typically used after
|
// Abort all HTTP requests. This is typically used after
|
||||||
// reloading a world from a save-game. The http requests that
|
// reloading a world from a save-game. The http requests that
|
||||||
// were in progress are long-since dead.
|
// were in progress are long-since dead.
|
||||||
|
//
|
||||||
void abort_all_http_requests(int status_code, std::string_view error);
|
void abort_all_http_requests(int status_code, std::string_view error);
|
||||||
|
|
||||||
|
// Serve an HTTP query coming in from outside.
|
||||||
|
//
|
||||||
|
// Note: the lua code for the http_serve runs in a nonblocking
|
||||||
|
// context. It must produce a result instantly.
|
||||||
|
//
|
||||||
|
HttpServerResponse http_serve(const HttpParser &request);
|
||||||
|
|
||||||
// Run all unit tests.
|
// Run all unit tests.
|
||||||
//
|
//
|
||||||
void run_unittests();
|
void run_unittests();
|
||||||
|
|||||||
Reference in New Issue
Block a user