http.get is now operational

This commit is contained in:
2022-05-06 13:16:27 -04:00
parent c41d0522df
commit 0ee4167772
7 changed files with 174 additions and 98 deletions

View File

@@ -82,7 +82,7 @@ std::string errors_string(bool lastonly) {
} else { } else {
err = err + ", " + reason; err = err + ", " + reason;
} }
if (data != nullptr) { if ((data != nullptr) && (data[0] != 0)) {
err = err + " " + data; err = err + " " + data;
} }
} }

View File

@@ -186,21 +186,21 @@ public:
} }
}; };
HttpOutRequest::HttpOutRequest() { HttpClientRequest::HttpClientRequest() {
verify_certificate_ = true; verify_certificate_ = false;
port_ = 0; port_ = 0;
request_id_ = 0; request_id_ = 0;
place_id_ = 0; place_id_ = 0;
thread_id_ = 0; thread_id_ = 0;
} }
void HttpOutRequest::fail(string_view s) { void HttpClientRequest::fail(string_view s) {
if (error_.empty()) { if (error_.empty()) {
error_ = s; error_ = s;
} }
} }
eng::string HttpOutRequest::target() const { eng::string HttpClientRequest::target() const {
assert(check().empty()); assert(check().empty());
eng::ostringstream oss; eng::ostringstream oss;
oss << (verify_certificate_ ? "cert" : "nocert"); oss << (verify_certificate_ ? "cert" : "nocert");
@@ -208,11 +208,11 @@ eng::string HttpOutRequest::target() const {
return oss.str(); return oss.str();
} }
void HttpOutRequest::set_verify_certificate(bool flag) { void HttpClientRequest::set_verify_certificate(bool flag) {
verify_certificate_ = flag; verify_certificate_ = flag;
} }
void HttpOutRequest::set_method(const eng::string &s) { void HttpClientRequest::set_method(const eng::string &s) {
eng::string method = util::ascii_toupper(s); eng::string method = util::ascii_toupper(s);
if ((method != "GET") && (method != "HEAD")) { if ((method != "GET") && (method != "HEAD")) {
fail(util::ss("HTTP method not implemented: ", method, ".", fail(util::ss("HTTP method not implemented: ", method, ".",
@@ -226,7 +226,7 @@ void HttpOutRequest::set_method(const eng::string &s) {
method_ = method; method_ = method;
} }
void HttpOutRequest::set_host(const eng::string &s) { void HttpClientRequest::set_host(const eng::string &s) {
eng::string host = util::ascii_tolower(s); eng::string host = util::ascii_tolower(s);
if (host.empty()) { if (host.empty()) {
fail(util::ss("HTTP hostname cannot be empty string.")); fail(util::ss("HTTP hostname cannot be empty string."));
@@ -247,7 +247,7 @@ void HttpOutRequest::set_host(const eng::string &s) {
host_ = host; host_ = host;
} }
void HttpOutRequest::set_port(int port) { void HttpClientRequest::set_port(int port) {
if ((port < 1) || (port > 65535)) { if ((port < 1) || (port > 65535)) {
fail(util::ss("HTTP port must be between 1 and 65535: ", port)); fail(util::ss("HTTP port must be between 1 and 65535: ", port));
return; return;
@@ -259,7 +259,7 @@ void HttpOutRequest::set_port(int port) {
port_ = port; port_ = port;
} }
void HttpOutRequest::set_path(string_view path) { void HttpClientRequest::set_path(string_view path) {
if (!sv::has_prefix(path, "/")) { if (!sv::has_prefix(path, "/")) {
fail(util::ss("HTTP path must start with slash")); fail(util::ss("HTTP path must start with slash"));
return; return;
@@ -271,7 +271,7 @@ void HttpOutRequest::set_path(string_view path) {
path_ = path; path_ = path;
} }
void HttpOutRequest::set_param(const eng::string &key, const eng::string &val) { void HttpClientRequest::set_param(const eng::string &key, const eng::string &val) {
if (params_.find(key) != params_.end()) { if (params_.find(key) != params_.end()) {
fail(util::ss("HTTP url parameter specified twice: ", key)); fail(util::ss("HTTP url parameter specified twice: ", key));
return; return;
@@ -283,7 +283,7 @@ void HttpOutRequest::set_param(const eng::string &key, const eng::string &val) {
params_[key] = val; params_[key] = val;
} }
void HttpOutRequest::set_url(string_view url) { void HttpClientRequest::set_url(string_view url) {
ParsedURL parsed_url(url); ParsedURL parsed_url(url);
if (!parsed_url.valid) { if (!parsed_url.valid) {
fail(util::ss("syntactically invalid URL: ", url)); fail(util::ss("syntactically invalid URL: ", url));
@@ -301,7 +301,7 @@ void HttpOutRequest::set_url(string_view url) {
} }
} }
void HttpOutRequest::set_verify_certificate(LuaStack &LS, LuaSlot val) { void HttpClientRequest::set_verify_certificate(LuaStack &LS, LuaSlot val) {
if (!LS.isboolean(val)) { if (!LS.isboolean(val)) {
fail(util::ss("HTTP verify_certificate must be a boolean")); fail(util::ss("HTTP verify_certificate must be a boolean"));
return; return;
@@ -309,7 +309,7 @@ void HttpOutRequest::set_verify_certificate(LuaStack &LS, LuaSlot val) {
set_verify_certificate(LS.ckboolean(val)); set_verify_certificate(LS.ckboolean(val));
} }
void HttpOutRequest::set_method(LuaStack &LS, LuaSlot val) { void HttpClientRequest::set_method(LuaStack &LS, LuaSlot val) {
if (!LS.isstring(val)) { if (!LS.isstring(val)) {
fail(util::ss("HTTP method must be a string")); fail(util::ss("HTTP method must be a string"));
return; return;
@@ -317,7 +317,7 @@ void HttpOutRequest::set_method(LuaStack &LS, LuaSlot val) {
set_method(LS.ckstring(val)); set_method(LS.ckstring(val));
} }
void HttpOutRequest::set_host(LuaStack &LS, LuaSlot val) { void HttpClientRequest::set_host(LuaStack &LS, LuaSlot val) {
if (!LS.isstring(val)) { if (!LS.isstring(val)) {
fail(util::ss("HTTP host must be a string")); fail(util::ss("HTTP host must be a string"));
return; return;
@@ -325,7 +325,7 @@ void HttpOutRequest::set_host(LuaStack &LS, LuaSlot val) {
set_host(LS.ckstring(val)); set_host(LS.ckstring(val));
} }
void HttpOutRequest::set_port(LuaStack &LS, LuaSlot val) { void HttpClientRequest::set_port(LuaStack &LS, LuaSlot val) {
if (!LS.isint(val)) { if (!LS.isint(val)) {
fail(util::ss("HTTP port must be an int")); fail(util::ss("HTTP port must be an int"));
return; return;
@@ -333,7 +333,7 @@ void HttpOutRequest::set_port(LuaStack &LS, LuaSlot val) {
set_port(LS.ckint(val)); set_port(LS.ckint(val));
} }
void HttpOutRequest::set_path(LuaStack &LS, LuaSlot val) { void HttpClientRequest::set_path(LuaStack &LS, LuaSlot val) {
if (!LS.isstring(val)) { if (!LS.isstring(val)) {
fail(util::ss("HTTP path must be a string")); fail(util::ss("HTTP path must be a string"));
return; return;
@@ -341,7 +341,7 @@ void HttpOutRequest::set_path(LuaStack &LS, LuaSlot val) {
set_path(LS.ckstring(val)); set_path(LS.ckstring(val));
} }
void HttpOutRequest::set_param(LuaStack &LS, LuaSlot key, LuaSlot val) { void HttpClientRequest::set_param(LuaStack &LS, LuaSlot key, LuaSlot val) {
if (!LS.isstring(key)) { if (!LS.isstring(key)) {
fail(util::ss("HTTP url parameter key must be a string")); fail(util::ss("HTTP url parameter key must be a string"));
return; return;
@@ -353,7 +353,7 @@ void HttpOutRequest::set_param(LuaStack &LS, LuaSlot key, LuaSlot val) {
set_param(LS.ckstring(key), LS.ckstring(val)); set_param(LS.ckstring(key), LS.ckstring(val));
} }
void HttpOutRequest::set_params(LuaStack &LS0, LuaSlot tab) { void HttpClientRequest::set_params(LuaStack &LS0, LuaSlot tab) {
if (!LS0.istable(tab)) { if (!LS0.istable(tab)) {
fail(util::ss("HTTP params must be a table")); fail(util::ss("HTTP params must be a table"));
return; return;
@@ -366,7 +366,7 @@ void HttpOutRequest::set_params(LuaStack &LS0, LuaSlot tab) {
} }
} }
void HttpOutRequest::set_url(LuaStack &LS, LuaSlot val) { void HttpClientRequest::set_url(LuaStack &LS, LuaSlot val) {
if (!LS.isstring(val)) { if (!LS.isstring(val)) {
fail(util::ss("HTTP url must be a string")); fail(util::ss("HTTP url must be a string"));
return; return;
@@ -374,7 +374,7 @@ void HttpOutRequest::set_url(LuaStack &LS, LuaSlot val) {
set_url(LS.ckstring(val)); set_url(LS.ckstring(val));
} }
void HttpOutRequest::set_defaults() { void HttpClientRequest::set_defaults() {
if (method_.empty()) { if (method_.empty()) {
method_ = "GET"; method_ = "GET";
} }
@@ -383,7 +383,7 @@ void HttpOutRequest::set_defaults() {
} }
} }
void HttpOutRequest::set_config(LuaStack &LS0, LuaSlot tab) { void HttpClientRequest::set_config(LuaStack &LS0, LuaSlot tab) {
LuaVar key, val; LuaVar key, val;
LuaStack LS(LS0.state(), key, val); LuaStack LS(LS0.state(), key, val);
LS.set(key, LuaNil); LS.set(key, LuaNil);
@@ -412,7 +412,7 @@ void HttpOutRequest::set_config(LuaStack &LS0, LuaSlot tab) {
} }
} }
eng::string HttpOutRequest::check() const { eng::string HttpClientRequest::check() const {
if (!error_.empty()) { if (!error_.empty()) {
return error_; return error_;
} }
@@ -432,7 +432,7 @@ eng::string HttpOutRequest::check() const {
} }
void HttpOutRequest::send_internal(StreamBuffer *sb, bool debug_string) const { void HttpClientRequest::send_internal(StreamBuffer *sb, bool debug_string) const {
// If there's an error in the request, handle it. In debug string mode, // If there's an error in the request, handle it. In debug string mode,
// we just put the error into the output. In production mode, we assert // we just put the error into the output. In production mode, we assert
// fail. // fail.
@@ -484,7 +484,7 @@ void HttpOutRequest::send_internal(StreamBuffer *sb, bool debug_string) const {
} }
} }
void HttpOutRequest::serialize(StreamBuffer *sb) const { void HttpClientRequest::serialize(StreamBuffer *sb) const {
sb->write_int64(request_id_); sb->write_int64(request_id_);
sb->write_int64(place_id_); sb->write_int64(place_id_);
sb->write_int64(thread_id_); sb->write_int64(thread_id_);
@@ -501,7 +501,7 @@ void HttpOutRequest::serialize(StreamBuffer *sb) const {
} }
} }
void HttpOutRequest::deserialize(StreamBuffer *sb) { void HttpClientRequest::deserialize(StreamBuffer *sb) {
request_id_ = sb->read_int64(); request_id_ = sb->read_int64();
place_id_ = sb->read_int64(); place_id_ = sb->read_int64();
thread_id_ = sb->read_int64(); thread_id_ = sb->read_int64();
@@ -520,39 +520,40 @@ void HttpOutRequest::deserialize(StreamBuffer *sb) {
} }
} }
eng::string HttpOutRequest::DebugString() { eng::string HttpClientRequest::DebugString() {
StreamBuffer sb; StreamBuffer sb;
send_internal(&sb, true); send_internal(&sb, true);
return eng::string(sb.view()); return eng::string(sb.view());
} }
void HttpOutRequestMap::serialize(StreamBuffer *sb) const { void HttpClientRequestMap::serialize(StreamBuffer *sb) const {
sb->write_int32(size()); sb->write_int32(size());
for (const auto &pair : *this) { for (const auto &pair : *this) {
pair.second.serialize(sb); pair.second.serialize(sb);
} }
} }
void HttpOutRequestMap::deserialize(StreamBuffer *sb) { void HttpClientRequestMap::deserialize(StreamBuffer *sb) {
int32_t count = sb->read_int32(); int32_t count = sb->read_int32();
clear(); clear();
HttpOutRequest req; HttpClientRequest req;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
req.deserialize(sb); req.deserialize(sb);
(*this)[req.request_id()] = req; (*this)[req.request_id()] = req;
} }
} }
HttpInResponse::HttpInResponse() { HttpClientResponse::HttpClientResponse() {
request_id_ = 0;
status_code_ = 0; status_code_ = 0;
response_length_ = 0; response_length_ = 0;
mime_type_ = ""; mime_type_ = "";
content_length_ = -1; content_length_ = -1;
} }
eng::string HttpInResponse::DebugString() const { eng::string HttpClientResponse::DebugString() const {
eng::ostringstream oss; eng::ostringstream oss;
oss << "HttpInResponse:" << std::endl; oss << "HttpClientResponse:" << std::endl;
oss << " status_code: " << status_code_ << std::endl; oss << " status_code: " << status_code_ << std::endl;
oss << " error: " << error_ << std::endl; oss << " error: " << error_ << std::endl;
oss << " content_length: " << content_length_ << std::endl; oss << " content_length: " << content_length_ << std::endl;
@@ -565,7 +566,7 @@ eng::string HttpInResponse::DebugString() const {
return oss.str(); return oss.str();
} }
void HttpInResponse::fail(int code, string_view message) { void HttpClientResponse::fail(int code, string_view message) {
status_code_ = code; status_code_ = code;
error_ = message; error_ = message;
mime_type_ = ""; mime_type_ = "";
@@ -573,7 +574,7 @@ void HttpInResponse::fail(int code, string_view message) {
content_ = ""; content_ = "";
} }
void HttpInResponse::incomplete(bool closed) { void HttpClientResponse::incomplete(bool closed) {
if (closed) { if (closed) {
fail(500, "internal server error: response truncated"); fail(500, "internal server error: response truncated");
} else { } else {
@@ -581,11 +582,11 @@ void HttpInResponse::incomplete(bool closed) {
} }
} }
void HttpInResponse::parse_content_encoding(string_view value) { void HttpClientResponse::parse_content_encoding(string_view value) {
content_encoding_ = util::ascii_tolower(value); content_encoding_ = util::ascii_tolower(value);
} }
void HttpInResponse::parse_content_length(string_view value) { void HttpClientResponse::parse_content_length(string_view value) {
int64_t code = sv::to_int64(value); int64_t code = sv::to_int64(value);
if ((code < 0) || (code > INT_MAX)) { if ((code < 0) || (code > INT_MAX)) {
fail(500, util::ss("internal server error: unparseable content-length: ", value)); fail(500, util::ss("internal server error: unparseable content-length: ", value));
@@ -593,7 +594,7 @@ void HttpInResponse::parse_content_length(string_view value) {
content_length_ = code; content_length_ = code;
} }
void HttpInResponse::parse_content_type(string_view value) { void HttpClientResponse::parse_content_type(string_view value) {
eng::string ctype = util::ascii_tolower(value); eng::string ctype = util::ascii_tolower(value);
string_view ctview(ctype); string_view ctview(ctype);
mime_type_ = sv::trim(sv::read_to_sep(ctview, ';')); mime_type_ = sv::trim(sv::read_to_sep(ctview, ';'));
@@ -613,15 +614,15 @@ void HttpInResponse::parse_content_type(string_view value) {
} }
} }
void HttpInResponse::parse_location(string_view value) { void HttpClientResponse::parse_location(string_view value) {
location_ = url_decode(value); location_ = url_decode(value);
} }
void HttpInResponse::parse_transfer_encoding(string_view value) { void HttpClientResponse::parse_transfer_encoding(string_view value) {
transfer_encoding_ = util::ascii_tolower(value); transfer_encoding_ = util::ascii_tolower(value);
} }
void HttpInResponse::parse_header(string_view header, string_view value) { void HttpClientResponse::parse_header(string_view header, string_view value) {
if (header == "content-encoding") { if (header == "content-encoding") {
parse_content_encoding(value); parse_content_encoding(value);
} else if (header == "content-length") { } else if (header == "content-length") {
@@ -637,7 +638,7 @@ void HttpInResponse::parse_header(string_view header, string_view value) {
} }
} }
bool HttpInResponse::parse_content_basic(std::string_view &view, bool closed) { bool HttpClientResponse::parse_content_basic(std::string_view &view, bool closed) {
if (content_length_ >= 0) { if (content_length_ >= 0) {
if (content_length_ > MAX_CONTENT_LENGTH) { if (content_length_ > MAX_CONTENT_LENGTH) {
fail(413, util::ss("payload too large: luprex limit=", MAX_CONTENT_LENGTH)); fail(413, util::ss("payload too large: luprex limit=", MAX_CONTENT_LENGTH));
@@ -662,7 +663,7 @@ bool HttpInResponse::parse_content_basic(std::string_view &view, bool closed) {
return true; return true;
} }
bool HttpInResponse::parse_content_chunked(std::string_view &view, bool closed) { bool HttpClientResponse::parse_content_chunked(std::string_view &view, bool closed) {
int64_t total_size = 0; int64_t total_size = 0;
std::vector<string_view> chunks; std::vector<string_view> chunks;
while (true) { while (true) {
@@ -711,7 +712,7 @@ bool HttpInResponse::parse_content_chunked(std::string_view &view, bool closed)
return true; return true;
} }
void HttpInResponse::parse(const StreamBuffer *sb, bool closed) { void HttpClientResponse::parse(const StreamBuffer *sb, bool closed) {
// We're not going to modify the StreamBuffer at all. // We're not going to modify the StreamBuffer at all.
// Instead, we work entirely on a view. // Instead, we work entirely on a view.
string_view view = sb->view(); string_view view = sb->view();
@@ -724,19 +725,26 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
} }
// Parse the status line. // Parse the status line.
string_view protoversion = sv::read_to_space(status);
if (!sv::has_prefix(protoversion, "HTTP/")) {
fail(500, util::ss("internal server error: status line appears corrupt"));
return;
}
string_view scode = sv::read_to_space(status); string_view scode = sv::read_to_space(status);
int64_t code = sv::to_int64(scode, 0); int64_t code = sv::to_int64(scode, 0);
if ((code < 100) || (code > 599)) { if ((code < 100) || (code > 599)) {
fail(500, util::ss("internal server error: invalid response code: ", scode)); fail(500, util::ss("internal server error: invalid response code: ", scode));
return;
} }
status_code_ = code; status_code_ = code;
// Responses outside the range 200-299 are errors, // Responses outside the range 200-299 are errors,
// and therefore must store an error message. // and therefore must store an error message.
if ((code < 200) || (code > 299)) { if ((code < 200) || (code > 299)) {
error_ = status; if (status.empty()) {
if (error_.empty()) { error_ = util::ss("error code ", code);
fail(code, util::ss("error code ", code)); } else {
error_ = status;
} }
} }
@@ -829,7 +837,7 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
} }
} }
void HttpInResponse::store(LuaStack &LS0, LuaSlot tab) const { void HttpClientResponse::store(LuaStack &LS0, LuaSlot tab) const {
LuaStack LS(LS0.state()); LuaStack LS(LS0.state());
LS.newtable(tab); LS.newtable(tab);
@@ -860,8 +868,8 @@ void HttpInResponse::store(LuaStack &LS0, LuaSlot tab) const {
} }
} }
void HttpInResponse::store_fail(LuaStack &LS, LuaSlot tab, int status_code, std::string_view error) { void HttpClientResponse::store_fail(LuaStack &LS, LuaSlot tab, int status_code, std::string_view error) {
HttpInResponse response; HttpClientResponse response;
response.fail(status_code, error); response.fail(status_code, error);
response.store(LS, tab); response.store(LS, tab);
} }
@@ -912,7 +920,7 @@ LuaDefine(http_request, "request",
LuaArg tab; LuaArg tab;
LuaRet str; LuaRet str;
LuaStack LS(L, tab, str); LuaStack LS(L, tab, str);
HttpOutRequest req; HttpClientRequest req;
req.set_config(LS, tab); req.set_config(LS, tab);
req.set_defaults(); req.set_defaults();
eng::string error = req.check(); eng::string error = req.check();
@@ -969,7 +977,7 @@ LuaDefine(http_response, "response",
LuaArg text; LuaArg text;
LuaRet tab; LuaRet tab;
LuaStack LS(L, text, tab); LuaStack LS(L, text, tab);
HttpInResponse resp; HttpClientResponse resp;
StreamBuffer sb; StreamBuffer sb;
sb.write_bytes(LS.ckstring(text)); sb.write_bytes(LS.ckstring(text));
resp.parse(&sb, true); resp.parse(&sb, true);

View File

@@ -19,10 +19,11 @@
#include "wrap-map.hpp" #include "wrap-map.hpp"
#include "luastack.hpp" #include "luastack.hpp"
#include "streambuffer.hpp" #include "streambuffer.hpp"
#include "drivenengine.hpp"
using UrlParameters = eng::map<eng::string, eng::string>; using UrlParameters = eng::map<eng::string, eng::string>;
class HttpOutRequest : public eng::nevernew { class HttpClientRequest : public eng::nevernew {
private: private:
// Request IDs. // Request IDs.
int64_t request_id_; int64_t request_id_;
@@ -59,14 +60,10 @@ private:
public: public:
// Construct an empty HTTP request. // Construct an empty HTTP request.
// All of the fields have empty values. // All of the fields have empty values.
HttpOutRequest(); HttpClientRequest();
// Get the request IDs.
int64_t request_id() const { return request_id_; }
int64_t place_id() const { return place_id_; }
int64_t thread_id() const { return thread_id_; }
// Get request-related fields. // Get request-related fields.
//
const eng::string &error() const { return error_; } const eng::string &error() const { return error_; }
bool verify_certificate() const { return verify_certificate_; } bool verify_certificate() const { return verify_certificate_; }
const eng::string &method() const { return method_; } const eng::string &method() const { return method_; }
@@ -74,20 +71,13 @@ public:
int port() const { return port_; } int port() const { return port_; }
const eng::string &path() const { return path_; } const eng::string &path() const { return path_; }
// Get the network target, eg, "cert:host:port" // Populate an request-related fields one piece at a time.
eng::string target() const;
// Set the request IDs.
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; }
// Populate an HTTP request a piece at a time.
// If you pass an invalid value, or if the field is // If you pass an invalid value, or if the field is
// already set, the routine will generate an error message // already set, the routine will generate an error message
// and store it in the error field. In that case, the set // and store it in the error field. In that case, the set
// will not happen. If there's already an error in the error // will not happen. If there's already an error in the error
// field, it will not be overwritten. // field, it will not be overwritten.
//
void set_verify_certificate(bool flag); void set_verify_certificate(bool flag);
void set_method(const eng::string &method); void set_method(const eng::string &method);
void set_host(const eng::string &host); void set_host(const eng::string &host);
@@ -96,7 +86,6 @@ public:
void set_param(const eng::string &key, const eng::string &value); void set_param(const eng::string &key, const eng::string &value);
void set_url(std::string_view url); void set_url(std::string_view url);
// Same as above, but using Lua values.
void set_verify_certificate(LuaStack &LS, LuaSlot val); void set_verify_certificate(LuaStack &LS, LuaSlot val);
void set_method(LuaStack &LS, LuaSlot val); void set_method(LuaStack &LS, LuaSlot val);
void set_host(LuaStack &LS, LuaSlot val); void set_host(LuaStack &LS, LuaSlot val);
@@ -106,18 +95,29 @@ public:
void set_params(LuaStack &LS, LuaSlot tab); void set_params(LuaStack &LS, LuaSlot tab);
void set_url(LuaStack &LS, LuaSlot val); void set_url(LuaStack &LS, LuaSlot val);
// Set default values for any fields that should have // Set default values for method and port.
// defaults. This must be done after setting regular // This must be done after setting regular values.
// values.
void set_defaults(); 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 // Verify that the request is error free and that
// defaults have been set. // defaults have been set.
eng::string check() const; eng::string check() const;
// Populate an HTTP request from a Lua table.
void set_config(LuaStack &LS0, LuaSlot tab);
// Put the request into the stream, assuming HTTP/1.1 // Put the request into the stream, assuming HTTP/1.1
void send(StreamBuffer *target) const { send_internal(target, false); } void send(StreamBuffer *target) const { send_internal(target, false); }
@@ -129,14 +129,11 @@ public:
eng::string DebugString(); eng::string DebugString();
}; };
class HttpOutRequestMap : public eng::map<int64_t, HttpOutRequest> { class HttpClientResponse {
public:
void serialize(StreamBuffer *sb) const;
void deserialize(StreamBuffer *sb);
};
class HttpInResponse {
private: private:
// The request ID.
int64_t request_id_;
// The HTTP response status code. // The HTTP response status code.
int status_code_; int status_code_;
@@ -198,7 +195,7 @@ public:
const int64_t MAX_CONTENT_LENGTH = 1000000; const int64_t MAX_CONTENT_LENGTH = 1000000;
// Construct a blank response. // Construct a blank response.
HttpInResponse(); HttpClientResponse();
// Store a result code and an error message, and clear the content. // Store a result code and an error message, and clear the content.
// This is generally used when the client detects an error, // This is generally used when the client detects an error,
@@ -209,7 +206,7 @@ public:
// Parse the HTTP response. The closed flag is to be set to true if the // Parse the HTTP response. The closed flag is to be set to true if the
// remote has closed the connection. // remote has closed the connection.
// //
// If the request is incomplete, generates a 600 incomplete error. In that // If the request is incomplete, generates a status code of zero. In that
// case, loading more data from the server might improve the situation. // case, loading more data from the server might improve the situation.
// //
// Note that the response is not ever removed from the StreamBuffer, which // Note that the response is not ever removed from the StreamBuffer, which
@@ -218,6 +215,14 @@ public:
// //
void parse(const StreamBuffer *sb, bool closed); void parse(const StreamBuffer *sb, bool closed);
// Return true if the response is complete.
bool complete() const { return status_code_ != 0; }
// 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. // Convert the HTTP response to a lua table.
void store(LuaStack &LS, LuaSlot tab) const; void store(LuaStack &LS, LuaSlot tab) const;
@@ -228,4 +233,25 @@ public:
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_code, std::string_view error);
}; };
class HttpClientRequestMap : public eng::map<int64_t, HttpClientRequest> {
public:
void serialize(StreamBuffer *sb) const;
void deserialize(StreamBuffer *sb);
};
using HttpClientResponseVec = eng::vector<HttpClientResponse>;
// 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<int64_t, HttpClientChannel>;
#endif // HTTP_HPP #endif // HTTP_HPP

View File

@@ -25,6 +25,7 @@ public:
LuaConsole console_; LuaConsole console_;
ClientVector clients_; ClientVector clients_;
PrintChanneler print_channeler_; PrintChanneler print_channeler_;
HttpClientChannelMap http_client_channels_;
int64_t admin_id_; int64_t admin_id_;
Gui gui_; Gui gui_;
@@ -201,6 +202,41 @@ public:
while (handle_invocation(client)); while (handle_invocation(client));
} }
util::remove_nullptrs(clients_); util::remove_nullptrs(clients_);
// Look for new outgoing HTTP client requests.
for (const auto &pair : master_->http_requests()) {
const HttpClientRequest &request = pair.second;
HttpClientChannel &channel = http_client_channels_[request.request_id()];
if (channel.channel_ == nullptr) {
channel.channel_ = new_outgoing_channel(request.target());
channel.parsed_bytes_ = 0;
request.send(channel.channel_->out());
}
}
// Maintain existing outgoing HTTP client requests.
HttpClientResponseVec http_responses;
for (auto &pair : http_client_channels_) {
HttpClientChannel &htchan = pair.second;
Channel &channel = *htchan.channel_;
if (channel.closed() || (channel.in()->fill() > htchan.parsed_bytes_)) {
HttpClientResponse response;
if (!channel.error().empty()) {
response.fail(503, util::ss("Service Unavailable: ", channel.error()));
} else {
htchan.parsed_bytes_ = channel.in()->fill();
response.parse(channel.in(), channel.closed());
}
if (response.complete()) {
response.set_request_id(pair.first);
http_responses.push_back(response);
}
}
}
for (const HttpClientResponse &response : http_responses) {
http_client_channels_.erase(response.request_id());
}
master_->http_responses(http_responses);
} }
}; };

View File

@@ -532,14 +532,14 @@ LuaDefine(http_get, "request",
LuaArg request; LuaArg request;
LuaRet response; LuaRet response;
LuaStack LS(L, request, response); LuaStack LS(L, request, response);
HttpOutRequest req; HttpClientRequest req;
// Parse the request and make sure it's valid. // Parse the request and make sure it's valid.
req.set_config(LS, request); req.set_config(LS, request);
req.set_defaults(); req.set_defaults();
eng::string error = req.check(); eng::string error = req.check();
if (!error.empty()) { if (!error.empty()) {
HttpInResponse::store_fail(LS, response, 400, util::ss("bad request: ", error)); HttpClientResponse::store_fail(LS, response, 400, util::ss("bad request: ", error));
return LS.result(); return LS.result();
} }

View File

@@ -393,13 +393,13 @@ void World::update_source(const util::LuaSourceVec &source) {
assert(stack_is_clear()); assert(stack_is_clear());
} }
void World::http_response(int64_t request_id, const HttpInResponse &response) { void World::http_response(const HttpClientResponse &response) {
// Find the request. // Find the request.
auto iter = http_requests_.find(request_id); auto iter = http_requests_.find(response.request_id());
if (iter == http_requests_.end()) { if (iter == http_requests_.end()) {
return; return;
} }
HttpOutRequest request = iter->second; HttpClientRequest request = iter->second;
http_requests_.erase(iter); http_requests_.erase(iter);
// Get the place and thread as lua objects. // Get the place and thread as lua objects.
@@ -443,13 +443,18 @@ void World::http_response(int64_t request_id, const HttpInResponse &response) {
run_scheduled_threads(); run_scheduled_threads();
} }
void World::http_responses(const HttpClientResponseVec &responses) {
for (const HttpClientResponse &response : responses) {
http_response(response);
}
}
void World::abort_all_http_requests(int status_code, std::string_view error) { void World::abort_all_http_requests(int status_code, std::string_view error) {
HttpInResponse abortresponse; HttpClientResponse abortresponse;
abortresponse.fail(status_code, error); abortresponse.fail(status_code, error);
while (true) { while (!http_requests_.empty()) {
auto iter = http_requests_.begin(); abortresponse.set_request_id(http_requests_.begin()->first);
if (iter == http_requests_.end()) break; http_response(abortresponse);
http_response(iter->second.request_id(), abortresponse);
} }
} }

View File

@@ -206,7 +206,8 @@ public:
void update_source(const util::LuaSourceVec &source); void update_source(const util::LuaSourceVec &source);
// Supply an HTTP response to an outstanding HTTP request. // Supply an HTTP response to an outstanding HTTP request.
void http_response(int64_t request_id, const HttpInResponse &response); void http_response(const HttpClientResponse &response);
void http_responses(const HttpClientResponseVec &responses);
// Abort all HTTP requests. This is typically used after // Abort all HTTP requests. This is typically used after
// reloading a world from a save-game. The http requests that // reloading a world from a save-game. The http requests that
@@ -230,7 +231,7 @@ public:
// Get a table showing all outstanding HTTP requests. // Get a table showing all outstanding HTTP requests.
// //
const HttpOutRequestMap &http_requests() const { return http_requests_; } const HttpClientRequestMap &http_requests() const { return http_requests_; }
// Serialize and deserialize. // Serialize and deserialize.
// //
@@ -515,7 +516,7 @@ private:
// Outstanding HTTP requests, indexed by request ID. // Outstanding HTTP requests, indexed by request ID.
// Authoritative models only. // Authoritative models only.
// //
HttpOutRequestMap http_requests_; HttpClientRequestMap http_requests_;
// Serialized snapshot of world model. // Serialized snapshot of world model.
// //