HTTP server functionality is in there.

This commit is contained in:
2022-05-20 17:12:58 -04:00
parent cd3064eb05
commit ca271b8db1
9 changed files with 932 additions and 208 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -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;
} }
} }

View File

@@ -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)
{ {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
} }

View File

@@ -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();

View File

@@ -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();