Did a lot of work on the HTTP server side

This commit is contained in:
2022-05-12 13:48:42 -04:00
parent 297ca3d56e
commit 9cf04a7741
6 changed files with 747 additions and 236 deletions

View File

@@ -20,6 +20,7 @@
#include "luastack.hpp"
#include "streambuffer.hpp"
#include "drivenengine.hpp"
#include <ostream>
using UrlParameters = eng::map<eng::string, eng::string>;
@@ -38,7 +39,7 @@ private:
// True is the default.
bool verify_certificate_;
// Method: GET, HEAD, POST, etc. Must be all-caps.
// Method: GET, HEAD, or POST. Must be all-caps.
eng::string method_;
// The hostname. Not yet url-encoded.
@@ -53,6 +54,12 @@ private:
// URL parameters to append to the path. Not yet url-encoded.
UrlParameters params_;
// The mime type of the content, only for POST requests.
eng::string mime_type_;
// The content as a string, only for POST requests.
eng::string content_;
private:
void fail(std::string_view error);
void send_internal(StreamBuffer *target, bool debug_string) const;
@@ -70,6 +77,8 @@ public:
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
@@ -85,6 +94,8 @@ public:
void set_path(std::string_view path);
void set_param(const eng::string &key, const eng::string &value);
void set_url(std::string_view url);
void set_mime_type(const eng::string &mime_type);
void set_content(const eng::string &content);
void set_verify_certificate(LuaStack &LS, LuaSlot val);
void set_method(LuaStack &LS, LuaSlot val);
@@ -94,6 +105,8 @@ public:
void set_param(LuaStack &LS, LuaSlot key, LuaSlot val);
void set_params(LuaStack &LS, LuaSlot tab);
void set_url(LuaStack &LS, LuaSlot val);
void set_mime_type(LuaStack &LS, LuaSlot val);
void set_content(LuaStack &LS, LuaSlot val);
// Set default values for method and port.
// This must be done after setting regular values.
@@ -129,18 +142,21 @@ public:
eng::string DebugString();
};
class HttpClientResponse {
private:
// The request ID.
int64_t request_id_;
// 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:
// True if this is for parsing a request.
// This is only used for generating error messages.
bool is_request_;
// The HTTP response status code.
int status_code_;
// The status code, a 3-digit number.
int status_;
// If the HTTP response contains an error, the
// error message is stored here. If the HTTP response
// is a success such as "200 OK" or "201 Created", this
// is the empty string, not "OK" or "Created".
// If the communication contains an error, the error can
// be stored here. In the event of a successful communication,
// this should always be empty string.
eng::string error_;
// Only if content-length header present, otherwise, -1.
@@ -158,50 +174,144 @@ private:
// MIME type of the content.
eng::string mime_type_;
// Charset of the content. Hopefully utf-8.
// Charset of the content before the content was translated to utf-8.
eng::string charset_;
// The content as string.
// The method, GET, HEAD, or POST. (only when parsing requests)
eng::string method_;
// The URL path, not url-encoded. (only when parsing requests)
eng::string path_;
// The URL parameters, not url-encoded (only when parsing requests)
UrlParameters params_;
// The content as string. If it's text, it's been translated to utf-8.
eng::string content_;
// The length in bytes of the entire response.
// May be zero, which means that the response
// was so garbled that we couldn't determine the length.
int response_length_;
private:
// The length in bytes of the entire communication.
// If the response is complete, but the comm_length_
// is zero, it means we couldn't find the end of
// the request.
int comm_length_;
protected:
// Store a message indicating that we haven't received enough
// bytes yet. If the connection is closed and we still haven't
// received enough bytes, that's a fatal error.
//
void incomplete(bool closed);
// Parse a response header. Most headers are ignored.
// If the header contains an error, the error is stored.
void parse_header(std::string_view header, std::string_view value);
// Store a message indicating that we couldn't parse because
// something was syntactically malformed, ie, not valid HTTP.
//
void syntax(std::string_view detail);
// Store a message indicating that the content was too large
// and we refused to download it.
//
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.
//
bool parse_status_line(std::string_view &view, bool closed);
// Parse a request line, such as "GET /index.html HTTP/1.1"
// returns false if we couldn't parse the whole line.
//
bool parse_request_line(std::string_view &view, bool closed);
// Parse specific headers.
// For several headers, all we do is verify that they aren't
// invoking unsupported features.
//
void parse_content_encoding(std::string_view value);
void parse_content_length(std::string_view value);
void parse_content_type(std::string_view value);
void parse_location(std::string_view value);
void parse_transfer_encoding(std::string_view value);
// parse the body
// Parse a single response header. Most headers are ignored.
// If the header contains an error, the error is stored.
//
void parse_header(std::string_view header, std::string_view value);
// Parse all of the headers, including the blank line after
// the headers. Return true if the view is at the end of the headers.
//
bool parse_headers(std::string_view &view, bool closed);
// parse the content, for different transfer encodings.
// Returns true if the view is at the end of the content.
//
bool parse_content_basic(std::string_view &view, bool closed);
bool parse_content_chunked(std::string_view &view, bool closed);
// Parse the content. Return true if the view is at the end of the
// content.
//
// - Uses the appropriate transfer-encoding specified in the headers.
// - Decompresses the content using the appropriate content-encoding.
// - Guesses a mimetype and charset if one was not specified in headers.
// - If it's text, converts the charset to utf-8.
//
bool parse_content(std::string_view &view, bool closed);
// Get or set the communication length.
//
int comm_length() const { return comm_length_; }
void set_comm_length(int len) { comm_length_ = len; }
// Construct a blank parser.
//
HttpParser();
// Store the parsed fields into a lua table.
//
void store_parsed(LuaStack &LS, LuaSlot tab) const;
// Emit all the parser fields as a debug string.
//
void parser_generate_debug_string(std::ostream &oss) const;
public:
// The parser will not try to parse content longer than this.
//
const int64_t MAX_CONTENT_LENGTH = 1000000;
// Construct a blank response.
HttpClientResponse();
// Store a result code and an error message, and clear the content.
// Store a status code and an error message, and clear the content.
// This is generally used when the client detects an error,
// such as a DNS lookup fail, a connection failed, an SSL negotiation
// failed, or the like.
void fail(int status_code, std::string_view error);
//
void fail(int status, std::string_view error);
// Return true if the communication was complete.
//
bool complete() const { return status_ != 0; }
};
class HttpClientResponse : public HttpParser {
private:
// Most of the data is stored in the HttpParser.
// That includes the status code, error, headers, and content.
// The request ID.
int64_t request_id_;
// The length in bytes of the entire communication.
// If the response is complete, but the comm_length_
// is zero, it means we couldn't find the end of
// the request.
//
int comm_length_;
public:
// Construct a blank response.
HttpClientResponse();
// Parse the HTTP response. The closed flag is to be set to true if the
// remote has closed the connection.
@@ -209,28 +319,62 @@ public:
// If the request is incomplete, generates a status code of zero. In that
// case, loading more data from the server might improve the situation.
//
// Note that the response is not ever removed from the StreamBuffer, which
// is always unmodified. If you want to remove the response from the
// StreamBuffer, see response_length.
// If successful, indicates how much of the text was consumed by
// setting comm_length_.
//
void parse(const StreamBuffer *sb, bool closed);
// Return true if the response is complete.
bool complete() const { return status_code_ != 0; }
void parse(std::string_view text, bool closed);
// Get or Set the request ID.
// This class does nothing with the request ID, it just stores it.
//
int64_t request_id() const { return request_id_; }
void set_request_id(int64_t v) { request_id_ = v; }
// Convert the HTTP response to a lua table.
//
void store(LuaStack &LS, LuaSlot tab) const;
// Convert to a debug string.
//
eng::string DebugString() const;
// Synthesize an error response and store it in lua.
static void store_fail(LuaStack &LS, LuaSlot tab, int status_code, std::string_view error);
//
static void store_fail(LuaStack &LS, LuaSlot tab, int status, std::string_view error);
};
class HttpServerRequest : public HttpParser {
private:
// The length in bytes of the entire communication.
// If the request is complete, but the comm_length_
// is zero, it means we couldn't find the end of
// the request.
//
int comm_length_;
public:
// Construct a blank request.
//
HttpServerRequest();
// Parse the HTTP request. The closed flag is to be set to true if the
// remote has closed the connection.
//
// If the request is incomplete, generates a status code of zero. In that
// case, loading more data from the server might improve the situation.
//
// If successful, indicates how much of the text was consumed by
// setting comm_length.
//
void parse(std::string_view text, bool closed);
// Convert the HTTP request to a lua table.
//
void store(LuaStack &LS, LuaSlot tab) const;
// Convert to a debug string.
//
eng::string DebugString() const;
};
class HttpClientRequestMap : public eng::map<int64_t, HttpClientRequest> {