///////////////////////////////////////////////////////// // // HTTP Implementation. // // This is a fairly limited implementation of HTTP. // // It only supports HTTP 1.1 // // It only supports GET, POST, and HEAD. // ///////////////////////////////////////////////////////// #ifndef HTTP_HPP #define HTTP_HPP #include "eng-malloc.hpp" #include "wrap-string.hpp" #include "wrap-vector.hpp" #include "wrap-map.hpp" #include "luastack.hpp" #include "streambuffer.hpp" #include "drivenengine.hpp" #include using UrlParameters = eng::map; class HttpClientRequest : public eng::nevernew { private: // Request IDs. int64_t request_id_; int64_t place_id_; int64_t thread_id_; // If the request contains an error, the error // message is stored here. eng::string error_; // If true, verify the server's certificate. // True is the default. bool verify_certificate_; // Method: GET, HEAD, or POST. Must be all-caps. eng::string method_; // The hostname. Not yet url-encoded. eng::string host_; // Port number. int port_; // The path. Not yet url-encoded. Can not include URL parameters. eng::string path_; // 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; public: // Construct an empty HTTP request. // 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 // and store it in the error field. In that case, the set // will not happen. If there's already an error in the error // field, it will not be overwritten. // void set_verify_certificate(bool flag); void set_method(const eng::string &method); void set_host(const eng::string &host); void set_port(int port); 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); void set_host(LuaStack &LS, LuaSlot val); void set_port(LuaStack &LS, LuaSlot val); void set_path(LuaStack &LS, LuaSlot path); 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. void set_defaults(); // Populate request-related fields from a Lua table. void set_config(LuaStack &LS0, LuaSlot tab); // Get or Set the request IDs. // This class does just stores these. int64_t request_id() const { return request_id_; } int64_t place_id() const { return place_id_; } int64_t thread_id() const { return thread_id_; } void set_request_id(int64_t request_id) { request_id_ = request_id; } void set_place_id(int64_t place_id) { place_id_ = place_id; } 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(); }; // 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 status code, a 3-digit number. int status_; // 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. int64_t content_length_; // If empty, it means there was no transfer-encoding header. eng::string transfer_encoding_; // If empty, it means there was no content-encoding header. eng::string content_encoding_; // Only if location header present. eng::string location_; // MIME type of the content. eng::string mime_type_; // 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) 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 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); // 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. // 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 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; // 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, 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. // // 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); // 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, 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 { public: void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); }; using HttpClientResponseVec = eng::vector; // This class associates an HTTP request to an actual communication // channel that is executing that request. class HttpClientChannel { public: SharedChannel channel_; int64_t parsed_bytes_; HttpClientChannel() : parsed_bytes_(0) {} }; using HttpClientChannelMap = eng::map; #endif // HTTP_HPP