///////////////////////////////////////////////////////// // // 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_; // 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. 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_; bool content_assigned_; private: void check_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(); // 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); void set_jsonvalue(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. // Doesn't throw errors, instead, returns errors via check(). void configure(LuaKeywordParser &kp); // Get or Set the request IDs. // // 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_; } 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; // Get the method. // eng::string method() const { return method_; } // 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 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_method(const eng::string &method); 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); void set_jsonvalue(LuaStack &LS, LuaSlot val); // Set default values. // void set_defaults(); // Populate request-related fields from a Lua table. // Doesn't throw errors, instead, returns them via check() // void configure(LuaKeywordParser &kp); // 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. // class HttpParser : public eng::nevernew { protected: // The request ID is not used by the parser, but // you can store a request ID in there for convenience. int64_t request_id_; // True if this parser parsed a request. If false, // then it parsed a response. 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 protocol: true for HTTP/1.1, false for HTTP/1.0 bool http11_; // The method. In a response, this is assigned before parsing. 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(); // 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); public: // Construct a blank parser. // HttpParser(); // 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. // void store(LuaStack &LS, LuaSlot tab) const; // The parser will not try to parse content longer than this. // static constexpr int64_t MAX_CONTENT_LENGTH = 1000000; // Parse a request or a response. // // You can only parse once. If you need to parse again, // construct a new parser. // void parse_request(std::string_view view, bool closed); void parse_response(std::string_view view, bool closed, std::string_view method); // 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); // Get or set the request ID. // int64_t request_id() const { return request_id_; } void set_request_id(int64_t v) { request_id_ = v; } // Return a debug string. // eng::string debug_string() const; // Synthesize an error and store it in lua. // static void store_fail(LuaStack &LS, LuaSlot tab, int status, std::string_view error); }; class HttpClientRequestMap : public eng::map { public: void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); }; using HttpParserVec = eng::vector; // This class is used by LpxServer to store the // incoming and outgoing http channels. // class HttpChannel { public: SharedChannel channel_; eng::string method_; int64_t parsed_bytes_; bool marked_for_deletion() const { return channel_ == nullptr; } HttpChannel() : parsed_bytes_(0) {} }; using HttpChannelMap = eng::map; using HttpChannelVec = eng::vector; #endif // HTTP_HPP