Files
integration/luprex/cpp/core/http.hpp

459 lines
14 KiB
C++

/////////////////////////////////////////////////////////
//
// 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 "keywords.hpp"
#include "streambuffer.hpp"
#include "drivenengine.hpp"
#include <ostream>
using UrlParameters = eng::map<eng::string, eng::string>;
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(LuaCoreStack &LS, LuaSlot val);
void set_method(LuaCoreStack &LS, LuaSlot val);
void set_host(LuaCoreStack &LS, LuaSlot val);
void set_port(LuaCoreStack &LS, LuaSlot val);
void set_path(LuaCoreStack &LS, LuaSlot path);
void set_param(LuaCoreStack &LS, LuaSlot key, LuaSlot val);
void set_params(LuaCoreStack &LS, LuaSlot tab);
void set_url(LuaCoreStack &LS, LuaSlot val);
void set_mime_type(LuaCoreStack &LS, LuaSlot val);
void set_content(LuaCoreStack &LS, LuaSlot val);
void set_jsonvalue(LuaCoreStack &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(LuaCoreStack &LS, LuaSlot val);
void set_max_age(LuaCoreStack &LS, LuaSlot val);
void set_mime_type(LuaCoreStack &LS, LuaSlot val);
void set_content(LuaCoreStack &LS, LuaSlot val);
void set_jsonvalue(LuaCoreStack &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(LuaCoreStack &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(LuaCoreStack &LS, LuaSlot tab, int status, std::string_view error);
// Convert a path component into a lua identifier.
//
// Returns the empty string if the path component cannot be converted
// to a lua identifier.
//
static eng::string to_lua_identifier(std::string_view pathcomp);
};
class HttpClientRequestMap : public eng::map<int64_t, HttpClientRequest> {
public:
void serialize(StreamBuffer *sb) const;
void deserialize(StreamBuffer *sb);
};
using HttpParserVec = eng::vector<HttpParser>;
// 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<int64_t, HttpChannel>;
using HttpChannelVec = eng::vector<HttpChannel>;
#endif // HTTP_HPP