Machinery for blocking http requests in place, except actual networking
This commit is contained in:
@@ -142,6 +142,9 @@ namespace eng {
|
|||||||
|
|
||||||
// eng::nevernew. A class containing private operator new and
|
// eng::nevernew. A class containing private operator new and
|
||||||
// operator delete, making it impossible to 'new' the class.
|
// operator delete, making it impossible to 'new' the class.
|
||||||
|
// This means the class must be embedded as a field in some other
|
||||||
|
// class, and it gets allocated when its enclosing object gets
|
||||||
|
// allocated.
|
||||||
namespace eng {
|
namespace eng {
|
||||||
class nevernew {
|
class nevernew {
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -189,6 +189,9 @@ public:
|
|||||||
HttpOutRequest::HttpOutRequest() {
|
HttpOutRequest::HttpOutRequest() {
|
||||||
verify_certificate_ = true;
|
verify_certificate_ = true;
|
||||||
port_ = 0;
|
port_ = 0;
|
||||||
|
request_id_ = 0;
|
||||||
|
place_id_ = 0;
|
||||||
|
thread_id_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpOutRequest::fail(string_view s) {
|
void HttpOutRequest::fail(string_view s) {
|
||||||
@@ -395,8 +398,6 @@ void HttpOutRequest::set_config(LuaStack &LS0, LuaSlot tab) {
|
|||||||
set_port(LS, val);
|
set_port(LS, val);
|
||||||
} else if (kstr == "path") {
|
} else if (kstr == "path") {
|
||||||
set_path(LS, val);
|
set_path(LS, val);
|
||||||
} else if (kstr == "encodedpath") {
|
|
||||||
set_path(LS, val);
|
|
||||||
} else if (kstr == "params") {
|
} else if (kstr == "params") {
|
||||||
set_params(LS, val);
|
set_params(LS, val);
|
||||||
} else if (kstr == "url") {
|
} else if (kstr == "url") {
|
||||||
@@ -430,6 +431,7 @@ eng::string HttpOutRequest::check() const {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpOutRequest::send_internal(StreamBuffer *sb, bool debug_string) const {
|
void HttpOutRequest::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
|
||||||
@@ -469,18 +471,78 @@ void HttpOutRequest::send_internal(StreamBuffer *sb, bool debug_string) const {
|
|||||||
sb->write_bytes("User-agent: Mozilla 5.0 (luprex)");
|
sb->write_bytes("User-agent: Mozilla 5.0 (luprex)");
|
||||||
sb->write_bytes(linebreak);
|
sb->write_bytes(linebreak);
|
||||||
|
|
||||||
|
// Add the requester IDs (debug string only)
|
||||||
|
if (debug_string && ((request_id_ != 0) || (place_id_ != 0) || (thread_id_ != 0))) {
|
||||||
|
sb->write_bytes("X-Requester-Ids: ");
|
||||||
|
sb->ostream() << "rid=" << request_id_ << "; pid=" << place_id_ << "; tid=" << thread_id_;
|
||||||
|
sb->write_bytes(linebreak);
|
||||||
|
}
|
||||||
|
|
||||||
// Send the extra linebreak.
|
// Send the extra linebreak.
|
||||||
if (!debug_string) {
|
if (!debug_string) {
|
||||||
sb->write_bytes(linebreak);
|
sb->write_bytes(linebreak);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpOutRequest::serialize(StreamBuffer *sb) const {
|
||||||
|
sb->write_int64(request_id_);
|
||||||
|
sb->write_int64(place_id_);
|
||||||
|
sb->write_int64(thread_id_);
|
||||||
|
sb->write_string(error_);
|
||||||
|
sb->write_bool(verify_certificate_);
|
||||||
|
sb->write_string(method_);
|
||||||
|
sb->write_string(host_);
|
||||||
|
sb->write_int32(port_);
|
||||||
|
sb->write_string(path_);
|
||||||
|
sb->write_int32(params_.size());
|
||||||
|
for (const auto &pair : params_) {
|
||||||
|
sb->write_string(pair.first);
|
||||||
|
sb->write_string(pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpOutRequest::deserialize(StreamBuffer *sb) {
|
||||||
|
request_id_ = sb->read_int64();
|
||||||
|
place_id_ = sb->read_int64();
|
||||||
|
thread_id_ = sb->read_int64();
|
||||||
|
error_ = sb->read_string();
|
||||||
|
verify_certificate_ = sb->read_bool();
|
||||||
|
method_ = sb->read_string();
|
||||||
|
host_ = sb->read_string();
|
||||||
|
port_ = sb->read_int32();
|
||||||
|
path_ = sb->read_string();
|
||||||
|
int32_t nparams = sb->read_int32();
|
||||||
|
params_.clear();
|
||||||
|
for (int i = 0; i < nparams; i++) {
|
||||||
|
eng::string k = sb->read_string();
|
||||||
|
eng::string v = sb->read_string();
|
||||||
|
params_[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
eng::string HttpOutRequest::DebugString() {
|
eng::string HttpOutRequest::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 {
|
||||||
|
sb->write_int32(size());
|
||||||
|
for (const auto &pair : *this) {
|
||||||
|
pair.second.serialize(sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpOutRequestMap::deserialize(StreamBuffer *sb) {
|
||||||
|
int32_t count = sb->read_int32();
|
||||||
|
clear();
|
||||||
|
HttpOutRequest req;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
req.deserialize(sb);
|
||||||
|
(*this)[req.request_id()] = req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HttpInResponse::HttpInResponse() {
|
HttpInResponse::HttpInResponse() {
|
||||||
status_code_ = 0;
|
status_code_ = 0;
|
||||||
response_length_ = 0;
|
response_length_ = 0;
|
||||||
@@ -513,7 +575,7 @@ void HttpInResponse::fail(int code, string_view message) {
|
|||||||
|
|
||||||
void HttpInResponse::incomplete(bool closed) {
|
void HttpInResponse::incomplete(bool closed) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
fail(500, "response truncated");
|
fail(500, "internal server error: response truncated");
|
||||||
} else {
|
} else {
|
||||||
fail(0, "response not yet fully received");
|
fail(0, "response not yet fully received");
|
||||||
}
|
}
|
||||||
@@ -526,7 +588,7 @@ void HttpInResponse::parse_content_encoding(string_view value) {
|
|||||||
void HttpInResponse::parse_content_length(string_view value) {
|
void HttpInResponse::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("unparseable content-length: ", value));
|
fail(500, util::ss("internal server error: unparseable content-length: ", value));
|
||||||
}
|
}
|
||||||
content_length_ = code;
|
content_length_ = code;
|
||||||
}
|
}
|
||||||
@@ -536,7 +598,7 @@ void HttpInResponse::parse_content_type(string_view 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, ';'));
|
||||||
if (mime_type_.empty()) {
|
if (mime_type_.empty()) {
|
||||||
fail(500, util::ss("unparseable content-type: ", value));
|
fail(500, util::ss("internal server error: unparseable content-type: ", value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -571,14 +633,14 @@ void HttpInResponse::parse_header(string_view header, string_view value) {
|
|||||||
} else if (header == "transfer-encoding") {
|
} else if (header == "transfer-encoding") {
|
||||||
parse_transfer_encoding(value);
|
parse_transfer_encoding(value);
|
||||||
} else if (header == "content-range") {
|
} else if (header == "content-range") {
|
||||||
fail(500, util::ss("unsupported response header: ", header));
|
fail(406, util::ss("not acceptable: unsupported response header: ", header));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpInResponse::parse_content_basic(std::string_view &view, bool closed) {
|
bool HttpInResponse::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(500, "content too long");
|
fail(413, util::ss("payload too large: luprex limit=", MAX_CONTENT_LENGTH));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (int(view.size()) < content_length_) {
|
if (int(view.size()) < content_length_) {
|
||||||
@@ -588,7 +650,7 @@ bool HttpInResponse::parse_content_basic(std::string_view &view, bool closed) {
|
|||||||
content_ = sv::read_nbytes(view, content_length_);
|
content_ = sv::read_nbytes(view, content_length_);
|
||||||
} else {
|
} else {
|
||||||
if (int64_t(view.size()) > MAX_CONTENT_LENGTH) {
|
if (int64_t(view.size()) > MAX_CONTENT_LENGTH) {
|
||||||
fail(500, "content too long");
|
fail(413, util::ss("payload too large: luprex limit=", MAX_CONTENT_LENGTH));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
@@ -611,17 +673,17 @@ bool HttpInResponse::parse_content_chunked(std::string_view &view, bool closed)
|
|||||||
}
|
}
|
||||||
int64_t chunk_size = sv::to_hex64(chunk_header, -1);
|
int64_t chunk_size = sv::to_hex64(chunk_header, -1);
|
||||||
if (chunk_size < 0) {
|
if (chunk_size < 0) {
|
||||||
fail(500, "unparseable chunk header");
|
fail(500, "internal server error: unparseable chunk header");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (chunk_size > MAX_CONTENT_LENGTH) {
|
if (chunk_size > MAX_CONTENT_LENGTH) {
|
||||||
fail(500, "content too long");
|
fail(413, util::ss("payload too large: luprex limit=", MAX_CONTENT_LENGTH));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (chunk_size == 0) break;
|
if (chunk_size == 0) break;
|
||||||
total_size += chunk_size;
|
total_size += chunk_size;
|
||||||
if (total_size > MAX_CONTENT_LENGTH) {
|
if (total_size > MAX_CONTENT_LENGTH) {
|
||||||
fail(500, "content too long");
|
fail(413, util::ss("payload too large: luprex limit=", MAX_CONTENT_LENGTH));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::string_view chunk = sv::read_nbytes(view, chunk_size);
|
std::string_view chunk = sv::read_nbytes(view, chunk_size);
|
||||||
@@ -631,7 +693,7 @@ bool HttpInResponse::parse_content_chunked(std::string_view &view, bool closed)
|
|||||||
}
|
}
|
||||||
std::string_view newline = sv::read_to_line(view);
|
std::string_view newline = sv::read_to_line(view);
|
||||||
if (!newline.empty()) {
|
if (!newline.empty()) {
|
||||||
fail(500, "corrupted chunk encoding");
|
fail(500, "internal server error: corrupted chunk encoding");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sv::isnull(view)) {
|
if (sv::isnull(view)) {
|
||||||
@@ -665,7 +727,7 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
|
|||||||
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("protocol error: invalid response code: ", scode));
|
fail(500, util::ss("internal server error: invalid response code: ", scode));
|
||||||
}
|
}
|
||||||
status_code_ = code;
|
status_code_ = code;
|
||||||
|
|
||||||
@@ -690,11 +752,11 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
|
|||||||
}
|
}
|
||||||
eng::string command = util::ascii_tolower(sv::trim(sv::read_to_sep(header, ':')));
|
eng::string command = util::ascii_tolower(sv::trim(sv::read_to_sep(header, ':')));
|
||||||
if (sv::isnull(header)) {
|
if (sv::isnull(header)) {
|
||||||
fail(500, util::ss("protocol error: no colon in header line: ", command));
|
fail(500, util::ss("internal server error: no colon in header line: ", command));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!words_separated_by_dashes(command)) {
|
if (!words_separated_by_dashes(command)) {
|
||||||
fail(500, util::ss("protocol error: invalid header: ", command));
|
fail(500, util::ss("internal server error: invalid header: ", command));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parse_header(command, sv::trim(header));
|
parse_header(command, sv::trim(header));
|
||||||
@@ -716,7 +778,7 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
|
|||||||
// If it's not a redirect, disallow 'location'.
|
// If it's not a redirect, disallow 'location'.
|
||||||
if ((status_code_ < 300) || (status_code_ > 399)) {
|
if ((status_code_ < 300) || (status_code_ > 399)) {
|
||||||
if (!location_.empty()) {
|
if (!location_.empty()) {
|
||||||
fail(500, util::ss("redirect specified, but result code not 300-399: ", code));
|
fail(500, util::ss("internal server error: location specified, but result code not 300-399: ", code));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,18 +796,18 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
|
|||||||
|
|
||||||
// If it's multipart, reject it.
|
// If it's multipart, reject it.
|
||||||
if (sv::has_prefix(mime_type_, "multipart/")) {
|
if (sv::has_prefix(mime_type_, "multipart/")) {
|
||||||
fail(500, "multipart messages not implemented");
|
fail(406, "not acceptable: multipart messages not supported");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's text, demand a reasonable charset. Otherwise,
|
// If it's text, demand a reasonable charset. Otherwise,
|
||||||
// ignore the charset.
|
// ignore the charset.
|
||||||
if (sv::has_prefix(mime_type_, "text/")) {
|
if (sv::has_prefix(mime_type_, "text/")) {
|
||||||
if (charset_.empty()) {
|
if (charset_.empty() || (charset_ == "ascii")) {
|
||||||
charset_ = "utf-8";
|
charset_ = "utf-8";
|
||||||
}
|
}
|
||||||
if (charset_ != "utf-8") {
|
if (charset_ != "utf-8") {
|
||||||
fail(500, util::ss("charset not supported: ", charset_));
|
fail(406, util::ss("not acceptable: charset not supported: ", charset_));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -755,7 +817,7 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
|
|||||||
// Uncompress the content.
|
// Uncompress the content.
|
||||||
if ((content_encoding_ == "") || (content_encoding_ == "identity")) {
|
if ((content_encoding_ == "") || (content_encoding_ == "identity")) {
|
||||||
} else {
|
} else {
|
||||||
fail(500, util::ss("content-encoding not supported: ", content_encoding_));
|
fail(406, util::ss("not acceptable: content-encoding not supported: ", content_encoding_));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,7 +829,7 @@ void HttpInResponse::parse(const StreamBuffer *sb, bool closed) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpInResponse::store(LuaStack &LS0, LuaSlot tab) {
|
void HttpInResponse::store(LuaStack &LS0, LuaSlot tab) const {
|
||||||
LuaStack LS(LS0.state());
|
LuaStack LS(LS0.state());
|
||||||
|
|
||||||
LS.newtable(tab);
|
LS.newtable(tab);
|
||||||
@@ -784,10 +846,24 @@ void HttpInResponse::store(LuaStack &LS0, LuaSlot tab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debugging fields. Do not use for lua programming.
|
// Debugging fields. Do not use for lua programming.
|
||||||
LS.rawset(tab, "dbg-content-length", content_length_);
|
if (content_length_ >= 0) {
|
||||||
LS.rawset(tab, "dbg-transfer-encoding", transfer_encoding_);
|
LS.rawset(tab, "dbg_contentlength", content_length_);
|
||||||
LS.rawset(tab, "dbg-charset", charset_);
|
}
|
||||||
LS.rawset(tab, "dbg-response-length", response_length_);
|
if (!transfer_encoding_.empty()) {
|
||||||
|
LS.rawset(tab, "dbg_transferencoding", transfer_encoding_);
|
||||||
|
}
|
||||||
|
if (!charset_.empty()) {
|
||||||
|
LS.rawset(tab, "dbg_charset", charset_);
|
||||||
|
}
|
||||||
|
if (response_length_ != 0) {
|
||||||
|
LS.rawset(tab, "dbg_responselength", response_length_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpInResponse::store_fail(LuaStack &LS, LuaSlot tab, int status_code, std::string_view error) {
|
||||||
|
HttpInResponse response;
|
||||||
|
response.fail(status_code, error);
|
||||||
|
response.store(LS, tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(http_fixurl, "url", "validate URL and repair minor flaws in the URL syntax") {
|
LuaDefine(http_fixurl, "url", "validate URL and repair minor flaws in the URL syntax") {
|
||||||
@@ -804,36 +880,35 @@ LuaDefine(http_fixurl, "url", "validate URL and repair minor flaws in the URL sy
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LuaDefine(http_request, "reqtab",
|
LuaDefine(http_request, "request",
|
||||||
"|Given an HTTP request in the form of a table, returns the same "
|
"|Takes an HTTP request in the form of a lua table."
|
||||||
"|request as a string, to assist with debugging."
|
"|The table may contain these fields:"
|
||||||
"|"
|
"|"
|
||||||
"|The table can contain:"
|
"| method (ie, 'GET', 'POST', etc)"
|
||||||
"|"
|
|
||||||
"| method (ie, GET, HEAD, POST, etc)"
|
|
||||||
"| host (ie, 'google.com')"
|
"| host (ie, 'google.com')"
|
||||||
"| port (default: 443)"
|
"| port (default: 443)"
|
||||||
"| url (ie, '/index.html')"
|
"| path (ie, '/index.html')"
|
||||||
"| params (a table of url parameters)"
|
"| params (a table of url parameters)"
|
||||||
"| verifycertificate (default: true)"
|
"| verifycertificate (default: true)"
|
||||||
|
"| url (ie, 'https://host:port/path.html?a=b&c=d')"
|
||||||
"|"
|
"|"
|
||||||
"|The url can start with 'https://', or with '/'. If it starts"
|
"|You can specify url components separately (host, port, path,"
|
||||||
"|with 'https://', then the URL includes the host and port, which"
|
"|and params), or you can specify the entire url as a unit. "
|
||||||
"|then must not be specified separately."
|
"|If you specify components, they must not be url-encoded. "
|
||||||
|
"|If you specify the url as a whole, it must already be url-encoded."
|
||||||
"|"
|
"|"
|
||||||
"|Note that plain HTTP is not supported - we only allow HTTPS."
|
"|You can omit the port, in which case it defaults to the"
|
||||||
|
"|standard https port. You can omit verifycertificate, in which"
|
||||||
|
"|case it defaults to true. You can omit the method if the"
|
||||||
|
"|method is implied by the function you called (eg, 'http.get')."
|
||||||
|
"|"
|
||||||
|
"|Note that unencrypted http is not supported - we only allow https."
|
||||||
"|However, you can talk to a server that has a dummy certificate"
|
"|However, you can talk to a server that has a dummy certificate"
|
||||||
"|by specifying verifycertificate=false."
|
"|by specifying verifycertificate=false."
|
||||||
"|"
|
"|"
|
||||||
"|This module will automatically url encode everything for you."
|
"|This routine, http.request, returns a debug string for the "
|
||||||
"|Therefore, you shouldn't url encode anything, otherwise,"
|
"|request. The debug string looks like the actual http headers"
|
||||||
"|you'll end up double-encoding."
|
"|that would be sent.") {
|
||||||
"|"
|
|
||||||
"|You cannot include url parameters as part of the url. If you try,"
|
|
||||||
"|then your ?, &, and = characters will get url encoded, which will"
|
|
||||||
"|cause them to not function. To use url parameters, you must"
|
|
||||||
"|use the separate params table."
|
|
||||||
"|") {
|
|
||||||
LuaArg tab;
|
LuaArg tab;
|
||||||
LuaRet str;
|
LuaRet str;
|
||||||
LuaStack LS(L, tab, str);
|
LuaStack LS(L, tab, str);
|
||||||
@@ -849,7 +924,48 @@ LuaDefine(http_request, "reqtab",
|
|||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(http_response, "text", "") {
|
LuaDefine(http_response, "response",
|
||||||
|
"|Returns an HTTP response in the form of a lua table."
|
||||||
|
"|The table will contain these important fields:"
|
||||||
|
"|"
|
||||||
|
"| responsecode - 3-digit HTTP response code."
|
||||||
|
"| error - an error message, or nil if no error."
|
||||||
|
"| content - on success, the content, as a string."
|
||||||
|
"| mimetype - on success, the mime type of the content."
|
||||||
|
"| location - for HTTP redirects, the target url."
|
||||||
|
"|"
|
||||||
|
"|If the mimetype is a text mimetype, then the content"
|
||||||
|
"|is automatically converted to utf-8."
|
||||||
|
"|"
|
||||||
|
"|The table may also contain these debugging-only fields."
|
||||||
|
"|"
|
||||||
|
"| dbg_transferencoding - If there was a Transfer-Encoding header."
|
||||||
|
"| dbg_contentlength - If there was a Content-length header."
|
||||||
|
"| dbg_charset - Original character set for text mime types."
|
||||||
|
"| dbg_responselength - Total bytes in the response."
|
||||||
|
"|"
|
||||||
|
"|None of the dbg fields is needed to understand the response."
|
||||||
|
"|For example, consider dbg_charset. When text content is"
|
||||||
|
"|passed to lua, the content is automatically converted to utf-8."
|
||||||
|
"|So dbg_charset only tells you what the character set used"
|
||||||
|
"|to be, before it was converted to utf-8."
|
||||||
|
"|"
|
||||||
|
"|If an http routine generates an error, that error will be"
|
||||||
|
"|expressed as a status code. These locally-generated status"
|
||||||
|
"|codes can be:"
|
||||||
|
"|"
|
||||||
|
"| 400 (bad request) - the request was malformed"
|
||||||
|
"| 503 (service unavailable) - dns fail, connect fail, or ssl fail"
|
||||||
|
"| 500 (internal server error)- the response contains invalid HTTP"
|
||||||
|
"| 406 (not acceptable) - the response used a feature we don't support yet"
|
||||||
|
"| 413 (payload too large) - we refuse to download something so big"
|
||||||
|
"| 425 (can't resume) - reloaded a save game with a pending request"
|
||||||
|
"|"
|
||||||
|
"|In this case, the error message will be the stock"
|
||||||
|
"|error message, followed by more information."
|
||||||
|
"|"
|
||||||
|
"|This routine, http.response, generates a response by parsing"
|
||||||
|
"|an actual HTTP response string. This is for debugging only.") {
|
||||||
LuaArg text;
|
LuaArg text;
|
||||||
LuaRet tab;
|
LuaRet tab;
|
||||||
LuaStack LS(L, text, tab);
|
LuaStack LS(L, text, tab);
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ using UrlParameters = eng::map<eng::string, eng::string>;
|
|||||||
|
|
||||||
class HttpOutRequest : public eng::nevernew {
|
class HttpOutRequest : public eng::nevernew {
|
||||||
private:
|
private:
|
||||||
|
// Request IDs.
|
||||||
|
int64_t request_id_;
|
||||||
|
int64_t place_id_;
|
||||||
|
int64_t thread_id_;
|
||||||
|
|
||||||
// If the request contains an error, the error
|
// If the request contains an error, the error
|
||||||
// message is stored here.
|
// message is stored here.
|
||||||
eng::string error_;
|
eng::string error_;
|
||||||
@@ -32,38 +37,36 @@ private:
|
|||||||
// True is the default.
|
// True is the default.
|
||||||
bool verify_certificate_;
|
bool verify_certificate_;
|
||||||
|
|
||||||
// Method: GET, HEAD, POST, etc.
|
// Method: GET, HEAD, POST, etc. Must be all-caps.
|
||||||
eng::string method_;
|
eng::string method_;
|
||||||
|
|
||||||
// The hostname. This is used both for DNS lookup,
|
// The hostname. Not yet url-encoded.
|
||||||
// and to create an HTTP Host header.
|
|
||||||
eng::string host_;
|
eng::string host_;
|
||||||
|
|
||||||
// Port number.
|
// Port number.
|
||||||
int port_;
|
int port_;
|
||||||
|
|
||||||
// You may specify either path or encoded_path.
|
// The path. Not yet url-encoded. Can not include URL parameters.
|
||||||
// The path is not url-encoded, and must not include URL parameters.
|
|
||||||
eng::string path_;
|
eng::string path_;
|
||||||
|
|
||||||
// If params is nonempty, then we will add URL parameters
|
// URL parameters to append to the path. Not yet url-encoded.
|
||||||
// to the URL. The contents of the params field should not be
|
|
||||||
// urlencoded, the urlencoding is done automatically when the
|
|
||||||
// request is sent. If you specify encoded_path, then the
|
|
||||||
// params must be empty, because the encoded path already contains
|
|
||||||
// the params.
|
|
||||||
UrlParameters params_;
|
UrlParameters params_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fail(std::string_view error);
|
void fail(std::string_view error);
|
||||||
|
|
||||||
void send_internal(StreamBuffer *target, bool debug_string) const;
|
void send_internal(StreamBuffer *target, bool debug_string) const;
|
||||||
|
|
||||||
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();
|
HttpOutRequest();
|
||||||
|
|
||||||
// Get fields.
|
// 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.
|
||||||
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,6 +77,11 @@ public:
|
|||||||
// Get the network target, eg, "cert:host:port"
|
// Get the network target, eg, "cert:host:port"
|
||||||
eng::string target() const;
|
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.
|
// 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
|
||||||
@@ -113,10 +121,20 @@ public:
|
|||||||
// 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); }
|
||||||
|
|
||||||
|
// Serialize and deserialize.
|
||||||
|
void serialize(StreamBuffer *sb) const;
|
||||||
|
void deserialize(StreamBuffer *sb);
|
||||||
|
|
||||||
// Get the request as a debug string.
|
// Get the request as a debug string.
|
||||||
eng::string DebugString();
|
eng::string DebugString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class HttpOutRequestMap : public eng::map<int64_t, HttpOutRequest> {
|
||||||
|
public:
|
||||||
|
void serialize(StreamBuffer *sb) const;
|
||||||
|
void deserialize(StreamBuffer *sb);
|
||||||
|
};
|
||||||
|
|
||||||
class HttpInResponse {
|
class HttpInResponse {
|
||||||
private:
|
private:
|
||||||
// The HTTP response status code.
|
// The HTTP response status code.
|
||||||
@@ -201,10 +219,13 @@ public:
|
|||||||
void parse(const StreamBuffer *sb, bool closed);
|
void parse(const StreamBuffer *sb, bool closed);
|
||||||
|
|
||||||
// Convert the HTTP response to a lua table.
|
// Convert the HTTP response to a lua table.
|
||||||
void store(LuaStack &LS, LuaSlot tab);
|
void store(LuaStack &LS, LuaSlot tab) const;
|
||||||
|
|
||||||
// Convert to a debug string.
|
// Convert to a debug string.
|
||||||
eng::string DebugString() const;
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HTTP_HPP
|
#endif // HTTP_HPP
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ public:
|
|||||||
stop_driver();
|
stop_driver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void do_aborthttp_command(const util::StringVec &words) {
|
||||||
|
master_->abort_all_http_requests(425, "http requests aborted from the server command line");
|
||||||
|
}
|
||||||
|
|
||||||
void do_command(const util::StringVec &words) {
|
void do_command(const util::StringVec &words) {
|
||||||
if (words.empty()) return;
|
if (words.empty()) return;
|
||||||
else if (words[0] == "luainvoke") do_luainvoke_command(words);
|
else if (words[0] == "luainvoke") do_luainvoke_command(words);
|
||||||
@@ -101,6 +105,7 @@ public:
|
|||||||
else if (words[0] == "tick") do_tick_command(words);
|
else if (words[0] == "tick") do_tick_command(words);
|
||||||
else if (words[0] == "cpl") do_cpl_command(words);
|
else if (words[0] == "cpl") do_cpl_command(words);
|
||||||
else if (words[0] == "quit") do_quit_command(words);
|
else if (words[0] == "quit") do_quit_command(words);
|
||||||
|
else if (words[0] == "aborthttp") do_aborthttp_command(words);
|
||||||
else {
|
else {
|
||||||
stdostream() << "Unsupported command: " << words[0] << std::endl;
|
stdostream() << "Unsupported command: " << words[0] << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ void LuaConsole::simplify(const StringVec &words) {
|
|||||||
if (words.size() != 1) {
|
if (words.size() != 1) {
|
||||||
synerr("/work takes no arguments");
|
synerr("/work takes no arguments");
|
||||||
}
|
}
|
||||||
|
} else if (words[0] == "aborthttp") {
|
||||||
|
if (words.size() != 1) {
|
||||||
|
synerr("/aborthttp takes no arguments");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
synerr("unrecognized command");
|
synerr("unrecognized command");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,9 @@ LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere",
|
|||||||
LuaDefine(wait, "nticks",
|
LuaDefine(wait, "nticks",
|
||||||
"|Wait the specified number of ticks.") {
|
"|Wait the specified number of ticks.") {
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
|
w->guard_blockable(L, "wait");
|
||||||
|
|
||||||
|
// Parse the argument.
|
||||||
LuaArg seconds;
|
LuaArg seconds;
|
||||||
LuaStack LS(L, seconds);
|
LuaStack LS(L, seconds);
|
||||||
int64_t n = LS.ckinteger(seconds);
|
int64_t n = LS.ckinteger(seconds);
|
||||||
@@ -302,30 +305,19 @@ LuaDefine(wait, "nticks",
|
|||||||
luaL_error(L, "Argument to wait must be between 0 and 1000000");
|
luaL_error(L, "Argument to wait must be between 0 and 1000000");
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
if (!lua_isyieldable(L)) {
|
|
||||||
// in a probe, wait throws an error.
|
// Schedule a continuation.
|
||||||
luaL_error(L, "cannot wait in a probe.");
|
w->schedule(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_);
|
||||||
return LS.result();
|
return lua_yield(L, 0);
|
||||||
} else if (!w->is_authoritative()) {
|
|
||||||
// in a nonauth model, yield is converted to nopredict.
|
|
||||||
lua_yield(L, 0);
|
|
||||||
return LS.result();
|
|
||||||
} else {
|
|
||||||
// in an authoritative model, wait schedules a continuation.
|
|
||||||
w->schedule(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_);
|
|
||||||
lua_yield(L, 0);
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(nopredict, "",
|
LuaDefine(nopredict, "",
|
||||||
"|Stop predictive execution of this thread.") {
|
"|Stop predictive execution of this thread.") {
|
||||||
if (lua_gettop(L) != 0) {
|
|
||||||
luaL_error(L, "tangible.nopredict takes no arguments");
|
|
||||||
}
|
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
if (lua_isyieldable(L) && !w->is_authoritative()) {
|
w->guard_nopredict(L, "nopredict");
|
||||||
return lua_yield(L, 0);
|
|
||||||
|
if (lua_gettop(L) != 0) {
|
||||||
|
luaL_error(L, "nopredict takes no arguments");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -530,3 +522,35 @@ LuaDefine(doc, "function",
|
|||||||
(*ostream) << doc;
|
(*ostream) << doc;
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LuaDefine(http_get, "request",
|
||||||
|
"|Make an HTTP GET request. Returns an HTTP response."
|
||||||
|
"|See doc(http.request) and doc(http.response).") {
|
||||||
|
World *w = World::fetch_global_pointer(L);
|
||||||
|
w->guard_blockable(L, "http.get");
|
||||||
|
|
||||||
|
LuaArg request;
|
||||||
|
LuaRet response;
|
||||||
|
LuaStack LS(L, request, response);
|
||||||
|
HttpOutRequest req;
|
||||||
|
|
||||||
|
// Parse the request and make sure it's valid.
|
||||||
|
req.set_config(LS, request);
|
||||||
|
req.set_defaults();
|
||||||
|
eng::string error = req.check();
|
||||||
|
if (!error.empty()) {
|
||||||
|
HttpInResponse::store_fail(LS, response, 400, util::ss("bad request: ", error));
|
||||||
|
return LS.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the request an ID.
|
||||||
|
req.set_request_id(w->id_global_pool_.get_one());
|
||||||
|
req.set_place_id(w->lthread_place_id_);
|
||||||
|
req.set_thread_id(w->lthread_thread_id_);
|
||||||
|
|
||||||
|
// Store it in the global request table.
|
||||||
|
w->http_requests_[req.request_id()] = req;
|
||||||
|
|
||||||
|
// Block.
|
||||||
|
return lua_yield(L, 0);
|
||||||
|
}
|
||||||
@@ -393,6 +393,66 @@ 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) {
|
||||||
|
// Find the request.
|
||||||
|
auto iter = http_requests_.find(request_id);
|
||||||
|
if (iter == http_requests_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HttpOutRequest request = iter->second;
|
||||||
|
http_requests_.erase(iter);
|
||||||
|
|
||||||
|
// Get the place and thread as lua objects.
|
||||||
|
LuaVar tangibles, place, mt, threads, thinfo, thread;
|
||||||
|
LuaStack LS(state(), tangibles, place, mt, threads, thinfo, thread);
|
||||||
|
LS.rawget(tangibles, LuaRegistry, "tangibles");
|
||||||
|
LS.rawget(place, tangibles, request.place_id());
|
||||||
|
if (!LS.istable(place)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LS.getmetatable(mt, place);
|
||||||
|
if (!LS.istable(mt)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LS.rawget(threads, mt, "threads");
|
||||||
|
if (!LS.istable(threads)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LS.rawget(thinfo, threads, request.thread_id());
|
||||||
|
if (!LS.istable(thinfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LS.rawget(thread, thinfo, "thread");
|
||||||
|
if (!LS.isthread(thread)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lua_State *CO = LS.ckthread(thread);
|
||||||
|
|
||||||
|
// Push the response onto the awakening thread.
|
||||||
|
LuaRet responsetable;
|
||||||
|
LuaStack LSCO(CO, responsetable);
|
||||||
|
response.store(LSCO, responsetable);
|
||||||
|
|
||||||
|
// Clean up lua stacks.
|
||||||
|
LSCO.result();
|
||||||
|
LS.result();
|
||||||
|
assert(stack_is_clear());
|
||||||
|
|
||||||
|
// Awaken the thread, with its new return value.
|
||||||
|
schedule(0, request.thread_id(), request.place_id());
|
||||||
|
run_scheduled_threads();
|
||||||
|
}
|
||||||
|
|
||||||
|
void World::abort_all_http_requests(int status_code, std::string_view error) {
|
||||||
|
HttpInResponse abortresponse;
|
||||||
|
abortresponse.fail(status_code, error);
|
||||||
|
while (true) {
|
||||||
|
auto iter = http_requests_.begin();
|
||||||
|
if (iter == http_requests_.end()) break;
|
||||||
|
http_response(iter->second.request_id(), abortresponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void World::run_unittests() {
|
void World::run_unittests() {
|
||||||
assert(stack_is_clear());
|
assert(stack_is_clear());
|
||||||
source_db_.run_unittests();
|
source_db_.run_unittests();
|
||||||
@@ -635,6 +695,30 @@ void World::invoke_lua_source(int64_t actor_id, int64_t place_id, const eng::str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void World::guard_blockable(lua_State *L, const char *fn) {
|
||||||
|
if (lthread_thread_id_ == 0) {
|
||||||
|
// in a probe, http.get throws an error.
|
||||||
|
luaL_error(L, "cannot %s in a probe", fn);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
if (!is_authoritative()) {
|
||||||
|
// in a nonauth model, http.get is converted to nopredict.
|
||||||
|
lua_yield(L, 0);
|
||||||
|
luaL_error(L, "unexplained nopredict failure in %s", fn);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void World::guard_nopredict(lua_State *L, const char *fn) {
|
||||||
|
if (lthread_thread_id_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!is_authoritative()) {
|
||||||
|
lua_yield(L, 0);
|
||||||
|
luaL_error(L, "unexplained nopredict failure in %s", fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void World::schedule(int64_t clk, int64_t thid, int64_t plid) {
|
void World::schedule(int64_t clk, int64_t thid, int64_t plid) {
|
||||||
assert(is_authoritative());
|
assert(is_authoritative());
|
||||||
thread_sched_.add(clk, thid, plid);
|
thread_sched_.add(clk, thid, plid);
|
||||||
@@ -796,6 +880,7 @@ void World::serialize(StreamBuffer *sb) {
|
|||||||
id_global_pool_.serialize(sb);
|
id_global_pool_.serialize(sb);
|
||||||
sb->write_int64(clock_);
|
sb->write_int64(clock_);
|
||||||
thread_sched_.serialize(sb);
|
thread_sched_.serialize(sb);
|
||||||
|
http_requests_.serialize(sb);
|
||||||
sb->write_uint32(tangibles_.size());
|
sb->write_uint32(tangibles_.size());
|
||||||
for (const auto &p : tangibles_) {
|
for (const auto &p : tangibles_) {
|
||||||
sb->write_int64(p.first);
|
sb->write_int64(p.first);
|
||||||
@@ -813,6 +898,7 @@ void World::deserialize(StreamBuffer *sb) {
|
|||||||
id_global_pool_.deserialize(sb);
|
id_global_pool_.deserialize(sb);
|
||||||
clock_ = sb->read_int64();
|
clock_ = sb->read_int64();
|
||||||
thread_sched_.deserialize(sb);
|
thread_sched_.deserialize(sb);
|
||||||
|
http_requests_.deserialize(sb);
|
||||||
// Mark all tangibles for deletion by setting ID to zero.
|
// Mark all tangibles for deletion by setting ID to zero.
|
||||||
for (const auto &p : tangibles_) {
|
for (const auto &p : tangibles_) {
|
||||||
p.second->plane_item_.set_id(0);
|
p.second->plane_item_.set_id(0);
|
||||||
@@ -837,6 +923,8 @@ void World::deserialize(StreamBuffer *sb) {
|
|||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// After a save and load, http requests no longer should exist
|
||||||
|
abort_all_http_requests(425, "http requests aborted by loading a save game");
|
||||||
assert(stack_is_clear());
|
assert(stack_is_clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "debugcollector.hpp"
|
#include "debugcollector.hpp"
|
||||||
#include "printbuffer.hpp"
|
#include "printbuffer.hpp"
|
||||||
#include "sched.hpp"
|
#include "sched.hpp"
|
||||||
|
#include "http.hpp"
|
||||||
#include "source.hpp"
|
#include "source.hpp"
|
||||||
#include "gui.hpp"
|
#include "gui.hpp"
|
||||||
#include "luasnap.hpp"
|
#include "luasnap.hpp"
|
||||||
@@ -188,6 +189,9 @@ public:
|
|||||||
// This is the primary dispatcher for all operations that mutate a world model.
|
// This is the primary dispatcher for all operations that mutate a world model.
|
||||||
// To mutate a world model, create an invocation, then invoke it.
|
// To mutate a world model, create an invocation, then invoke it.
|
||||||
//
|
//
|
||||||
|
// It is legal to mutate a world model without using 'Invoke', but
|
||||||
|
// only in authoritative world models.
|
||||||
|
//
|
||||||
void invoke(const Invocation &inv);
|
void invoke(const Invocation &inv);
|
||||||
|
|
||||||
// Get the PrintBuffer of the actor.
|
// Get the PrintBuffer of the actor.
|
||||||
@@ -201,6 +205,14 @@ public:
|
|||||||
void update_source(const util::LuaSourcePtr &source);
|
void update_source(const util::LuaSourcePtr &source);
|
||||||
void update_source(const util::LuaSourceVec &source);
|
void update_source(const util::LuaSourceVec &source);
|
||||||
|
|
||||||
|
// Supply an HTTP response to an outstanding HTTP request.
|
||||||
|
void http_response(int64_t request_id, const HttpInResponse &response);
|
||||||
|
|
||||||
|
// Abort all HTTP requests. This is typically used after
|
||||||
|
// reloading a world from a save-game. The http requests that
|
||||||
|
// were in progress are long-since dead.
|
||||||
|
void abort_all_http_requests(int status_code, std::string_view error);
|
||||||
|
|
||||||
// Run all unit tests.
|
// Run all unit tests.
|
||||||
//
|
//
|
||||||
void run_unittests();
|
void run_unittests();
|
||||||
@@ -216,6 +228,10 @@ public:
|
|||||||
//
|
//
|
||||||
bool is_authoritative() const { return (world_type_ == util::WORLD_TYPE_MASTER) || (world_type_ == util::WORLD_TYPE_STANDALONE); }
|
bool is_authoritative() const { return (world_type_ == util::WORLD_TYPE_MASTER) || (world_type_ == util::WORLD_TYPE_STANDALONE); }
|
||||||
|
|
||||||
|
// Get a table showing all outstanding HTTP requests.
|
||||||
|
//
|
||||||
|
const HttpOutRequestMap &http_requests() const { return http_requests_; }
|
||||||
|
|
||||||
// Serialize and deserialize.
|
// Serialize and deserialize.
|
||||||
//
|
//
|
||||||
void serialize(StreamBuffer *sb);
|
void serialize(StreamBuffer *sb);
|
||||||
@@ -263,6 +279,16 @@ public:
|
|||||||
//
|
//
|
||||||
int64_t alloc_id_predictable();
|
int64_t alloc_id_predictable();
|
||||||
|
|
||||||
|
// If we're in a probe, generate an error.
|
||||||
|
// If we're in a nonauthoritative model, do a nopredict yield.
|
||||||
|
// Otherwise, return.
|
||||||
|
void guard_blockable(lua_State *L, const char *fn);
|
||||||
|
|
||||||
|
// If we're in a probe, return.
|
||||||
|
// If we're in a nonauthoritative model, do a nopredict yield.
|
||||||
|
// Otherwise, return.
|
||||||
|
void guard_nopredict(lua_State *L, const char *fn);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Add a thread to the scheduler queue.
|
// Add a thread to the scheduler queue.
|
||||||
//
|
//
|
||||||
@@ -478,6 +504,7 @@ private:
|
|||||||
eng::unordered_map<int64_t, UniqueTangible> tangibles_;
|
eng::unordered_map<int64_t, UniqueTangible> tangibles_;
|
||||||
|
|
||||||
// Current time.
|
// Current time.
|
||||||
|
//
|
||||||
int64_t clock_;
|
int64_t clock_;
|
||||||
|
|
||||||
// Thread schedule: must include every thread, except
|
// Thread schedule: must include every thread, except
|
||||||
@@ -485,7 +512,13 @@ private:
|
|||||||
//
|
//
|
||||||
Schedule thread_sched_;
|
Schedule thread_sched_;
|
||||||
|
|
||||||
|
// Outstanding HTTP requests, indexed by request ID.
|
||||||
|
// Authoritative models only.
|
||||||
|
//
|
||||||
|
HttpOutRequestMap http_requests_;
|
||||||
|
|
||||||
// Serialized snapshot of world model.
|
// Serialized snapshot of world model.
|
||||||
|
//
|
||||||
StreamBuffer snapshot_;
|
StreamBuffer snapshot_;
|
||||||
|
|
||||||
// Redirects.
|
// Redirects.
|
||||||
@@ -513,6 +546,7 @@ private:
|
|||||||
friend int lfn_math_randomstate(lua_State *L);
|
friend int lfn_math_randomstate(lua_State *L);
|
||||||
friend int lfn_wait(lua_State *L);
|
friend int lfn_wait(lua_State *L);
|
||||||
friend int lfn_nopredict(lua_State *L);
|
friend int lfn_nopredict(lua_State *L);
|
||||||
|
friend int lfn_http_get(lua_State *L);
|
||||||
};
|
};
|
||||||
|
|
||||||
using UniqueWorld = std::unique_ptr<World>;
|
using UniqueWorld = std::unique_ptr<World>;
|
||||||
|
|||||||
@@ -42,3 +42,9 @@ function setfoo(n)
|
|||||||
function buildq()
|
function buildq()
|
||||||
return tangible.build{class="login", x=10, y=0, z=0, plane="nowhere", graphic="what"}
|
return tangible.build{class="login", x=10, y=0, z=0, plane="nowhere", graphic="what"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function htest()
|
||||||
|
print("hi")
|
||||||
|
pprint(http.get{url="https://mit.edu/"})
|
||||||
|
print("bye")
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user