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 thread_id_;
|
||||
|
||||
// If the request contains an error, the error
|
||||
// message is stored here.
|
||||
eng::string error_;
|
||||
// When we detect that we generated an invalid
|
||||
// outgoing request, the error is stored here.
|
||||
eng::string check_fail_;
|
||||
|
||||
// If true, verify the server's certificate.
|
||||
// True is the default.
|
||||
@@ -59,9 +59,10 @@ private:
|
||||
|
||||
// The content as a string, only for POST requests.
|
||||
eng::string content_;
|
||||
bool content_assigned_;
|
||||
|
||||
private:
|
||||
void fail(std::string_view error);
|
||||
void check_fail(std::string_view error);
|
||||
void send_internal(StreamBuffer *target, bool debug_string) const;
|
||||
|
||||
public:
|
||||
@@ -69,17 +70,6 @@ public:
|
||||
// All of the fields have empty values.
|
||||
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.
|
||||
// If you pass an invalid value, or if the field is
|
||||
// already set, the routine will generate an error message
|
||||
@@ -116,7 +106,10 @@ public:
|
||||
void set_config(LuaStack &LS0, LuaSlot tab);
|
||||
|
||||
// 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 place_id() const { return place_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; }
|
||||
|
||||
// Get the network target, eg, "cert:host:port"
|
||||
//
|
||||
eng::string target() const;
|
||||
|
||||
// Verify that the request is error free and that
|
||||
// defaults have been set.
|
||||
//
|
||||
eng::string check() const;
|
||||
|
||||
// Put the request into the stream, assuming HTTP/1.1
|
||||
//
|
||||
void send(StreamBuffer *target) const { send_internal(target, false); }
|
||||
|
||||
// Serialize and deserialize.
|
||||
//
|
||||
void serialize(StreamBuffer *sb) const;
|
||||
void deserialize(StreamBuffer *sb);
|
||||
|
||||
// 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.
|
||||
// Stores a status code, an error message, headers, and content.
|
||||
//
|
||||
class HttpParser : public eng::nevernew {
|
||||
protected:
|
||||
@@ -181,7 +272,10 @@ protected:
|
||||
// Charset of the content before the content was translated to utf-8.
|
||||
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_;
|
||||
|
||||
// The URL path, not url-encoded. (only when parsing requests)
|
||||
@@ -216,10 +310,6 @@ protected:
|
||||
//
|
||||
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"
|
||||
// returns false if we couldn't parse the whole line.
|
||||
//
|
||||
@@ -269,9 +359,23 @@ public:
|
||||
//
|
||||
HttpParser();
|
||||
|
||||
// Get the parsed status.
|
||||
// Get the parsed status or error message.
|
||||
//
|
||||
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.
|
||||
//
|
||||
@@ -301,10 +405,6 @@ public:
|
||||
int64_t request_id() const { return request_id_; }
|
||||
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.
|
||||
//
|
||||
eng::string debug_string() const;
|
||||
|
||||
@@ -198,7 +198,6 @@ public:
|
||||
HttpChannel htchan;
|
||||
htchan.channel_ = chan;
|
||||
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();
|
||||
if (parser.complete()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,9 +246,8 @@ private:
|
||||
template<int NARG, int NVAR, int NRET, class... SS>
|
||||
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>
|
||||
void count_slots(LuaVar &v, SS & ... stackslots)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,18 @@
|
||||
|
||||
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) {
|
||||
int64_t result;
|
||||
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; }
|
||||
|
||||
// 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
|
||||
bool valid_double(string_view v);
|
||||
bool valid_int64(string_view v);
|
||||
|
||||
@@ -666,11 +666,13 @@ LuaDefine(http_get, "request",
|
||||
HttpClientRequest req;
|
||||
|
||||
// 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_defaults();
|
||||
eng::string error = req.check();
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
assert(stack_is_clear());
|
||||
source_db_.run_unittests();
|
||||
|
||||
@@ -206,14 +206,23 @@ public:
|
||||
void update_source(const util::LuaSourceVec &source);
|
||||
|
||||
// Supply an HTTP response to an outstanding HTTP request.
|
||||
//
|
||||
void http_response(const HttpParser &response);
|
||||
void http_responses(const HttpParserVec &responses);
|
||||
|
||||
// Abort all HTTP requests. This is typically used after
|
||||
// reloading a world from a save-game. The http requests that
|
||||
// were in progress are long-since dead.
|
||||
//
|
||||
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.
|
||||
//
|
||||
void run_unittests();
|
||||
|
||||
Reference in New Issue
Block a user