HTTP stuff now uses keyword parser.

This commit is contained in:
2022-07-22 17:07:40 -04:00
parent 28ec61e47c
commit a5ef765524
6 changed files with 135 additions and 101 deletions

View File

@@ -734,50 +734,55 @@ void HttpClientRequest::set_defaults() {
}
}
void HttpClientRequest::set_config(LuaStack &LS0, LuaSlot tab) {
LuaVar key, val;
LuaStack LS(LS0.state(), key, val);
LS.set(key, LuaNil);
while (LS.next(tab, key, val)) {
eng::string kstr;
if (LS.isstring(key)) kstr = LS.ckstring(key);
if (kstr == "method") {
set_method(LS, val);
} else if (kstr == "host") {
set_host(LS, val);
} else if (kstr == "port") {
set_port(LS, val);
} else if (kstr == "path") {
set_path(LS, val);
} else if (kstr == "params") {
set_params(LS, val);
} else if (kstr == "url") {
set_url(LS, val);
} else if (kstr == "verifycertificate") {
set_verify_certificate(LS, val);
} else if (kstr == "mimetype") {
set_mime_type(LS, val);
} else if (kstr == "content") {
set_content(LS, val);
} else if (kstr == "html") {
set_content(LS, val);
set_mime_type("text/html");
} else if (kstr == "text") {
set_content(LS, val);
set_mime_type("text/plain");
} else if (kstr == "json") {
set_content(LS, val);
set_mime_type("application/json");
} else if (kstr == "bytes") {
set_content(LS, val);
set_mime_type("application/octet-stream");
} else if (kstr == "jsonvalue") {
set_jsonvalue(LS, val);
} else if (kstr == "") {
check_fail(util::ss("configuration parameter names must be strings."));
} else {
check_fail(util::ss("unrecognized request configuration parameter: ", kstr));
}
void HttpClientRequest::configure(LuaKeywordParser &kp) {
LuaVar val;
LuaStack LS(kp.state(), val);
if (kp.parse(val, "method")) {
set_method(LS, val);
}
if (kp.parse(val, "host")) {
set_host(LS, val);
}
if (kp.parse(val, "port")) {
set_port(LS, val);
}
if (kp.parse(val, "path")) {
set_path(LS, val);
}
if (kp.parse(val, "params")) {
set_params(LS, val);
}
if (kp.parse(val, "url")) {
set_url(LS, val);
}
if (kp.parse(val, "verifycertificate")) {
set_verify_certificate(LS, val);
}
if (kp.parse(val, "mimetype")) {
set_mime_type(LS, val);
}
if (kp.parse(val, "content")) {
set_content(LS, val);
}
if (kp.parse(val, "html")) {
set_content(LS, val);
set_mime_type("text/html");
}
if (kp.parse(val, "text")) {
set_content(LS, val);
set_mime_type("text/plain");
}
if (kp.parse(val, "json")) {
set_content(LS, val);
set_mime_type("application/json");
}
if (kp.parse(val, "bytes")) {
set_content(LS, val);
set_mime_type("application/octet-stream");
}
if (kp.parse(val, "jsonvalue")) {
set_jsonvalue(LS, val);
}
}
@@ -1025,40 +1030,39 @@ void HttpServerResponse::set_jsonvalue(LuaStack &LS, LuaSlot val) {
set_mime_type("application/json");
}
void HttpServerResponse::set_config(LuaStack &LS0, LuaSlot tab) {
LuaVar key, val;
LuaStack LS(LS0.state(), key, val);
LS.set(key, LuaNil);
while (LS.next(tab, key, val)) {
eng::string kstr;
if (LS.isstring(key)) kstr = LS.ckstring(key);
if (kstr == "status") {
set_status(LS, val);
} else if (kstr == "maxage") {
set_max_age(LS, val);
} else if (kstr == "mimetype") {
set_mime_type(LS, val);
} else if (kstr == "content") {
set_content(LS, val);
} else if (kstr == "html") {
set_content(LS, val);
set_mime_type("text/html");
} else if (kstr == "text") {
set_content(LS, val);
set_mime_type("text/plain");
} else if (kstr == "json") {
set_content(LS, val);
set_mime_type("application/json");
} else if (kstr == "bytes") {
set_content(LS, val);
set_mime_type("application/octet-stream");
} else if (kstr == "jsonvalue") {
set_jsonvalue(LS, val);
} else if (kstr == "") {
check_fail(util::ss("response configuration parameters must be strings."));
} else {
check_fail(util::ss("unrecognized response configuration parameter: ", kstr));
}
void HttpServerResponse::configure(LuaKeywordParser &kp) {
LuaVar val;
LuaStack LS(kp.state(), val);
if (kp.parse(val, "status")) {
set_status(LS, val);
}
if (kp.parse(val, "maxage")) {
set_max_age(LS, val);
}
if (kp.parse(val, "mimetype")) {
set_mime_type(LS, val);
}
if (kp.parse(val, "content")) {
set_content(LS, val);
}
if (kp.parse(val, "html")) {
set_content(LS, val);
set_mime_type("text/html");
}
if (kp.parse(val, "text")) {
set_content(LS, val);
set_mime_type("text/plain");
}
if (kp.parse(val, "json")) {
set_content(LS, val);
set_mime_type("application/json");
}
if (kp.parse(val, "bytes")) {
set_content(LS, val);
set_mime_type("application/octet-stream");
}
if (kp.parse(val, "jsonvalue")) {
set_jsonvalue(LS, val);
}
}
@@ -1712,8 +1716,10 @@ LuaDefine(http_clientrequest, "request",
LuaArg tab;
LuaRet str;
LuaStack LS(L, tab, str);
LuaKeywordParser kp(LS, tab);
HttpClientRequest req;
req.set_config(LS, tab);
req.configure(kp);
kp.final_check_throw();
req.set_defaults();
eng::string error = req.check();
if (!error.empty()) {
@@ -1882,8 +1888,10 @@ LuaDefine(http_serverresponse, "response",
LuaArg tab;
LuaRet str;
LuaStack LS(L, tab, str);
LuaKeywordParser kp(LS, tab);
HttpServerResponse resp;
resp.set_config(LS, tab);
resp.configure(kp);
kp.final_check_throw();
resp.set_defaults();
LS.set(str, resp.debug_string());
return LS.result();

View File

@@ -104,7 +104,8 @@ public:
void set_defaults();
// Populate request-related fields from a Lua table.
void set_config(LuaStack &LS0, LuaSlot tab);
// Doesn't throw errors, instead, returns errors via check().
void configure(LuaKeywordParser &kp);
// Get or Set the request IDs.
//
@@ -208,8 +209,9 @@ public:
void set_defaults();
// Populate request-related fields from a Lua table.
// Doesn't throw errors, instead, returns them via check()
//
void set_config(LuaStack &LS0, LuaSlot tab);
void configure(LuaKeywordParser &kp);
// Set the keep_alive flag.
//

View File

@@ -5,6 +5,7 @@
#include <climits>
#include "wrap-string.hpp"
#include "wrap-set.hpp"
#include "wrap-sstream.hpp"
LuaSpecial LuaRegistry(LUA_REGISTRYINDEX);
LuaNilMarker LuaNil;
@@ -468,8 +469,10 @@ void LuaStack::setvisited(LuaSlot tab, bool visited) const {
LuaKeywordParser::LuaKeywordParser(lua_State *L, int slot) {
L_ = L;
slot_ = slot;
if (!lua_istable(L_, slot_)) {
luaL_error(L_, "expected an argument which is a table full of keywords");
not_table_ = !lua_istable(L_, slot_);
if (not_table_) {
lua_newtable(L_);
lua_replace(L_, slot_);
}
}
@@ -485,20 +488,32 @@ bool LuaKeywordParser::parse(LuaSlot out, const char *kw) {
}
};
void LuaKeywordParser::check_unparsed_keywords() {
eng::string LuaKeywordParser::final_check() {
if (not_table_) {
return "expected a keyword table";
}
if (lua_nkeys(L_, slot_) != int(parsed_.size())) {
lua_pushnil(L_);
while (lua_next(L_, slot_) != 0) {
lua_pop(L_, 1); // Don't need the value.
if (!lua_isstring(L_, -1)) {
luaL_error(L_, "keyword table contains non-string key");
return "keyword table contains non-string key";
}
const char *kw = lua_tostring(L_, -1);
if (parsed_.find(kw) == parsed_.end()) {
luaL_error(L_, "keyword %s not known", kw);
eng::ostringstream oss;
oss << "keyword " << kw << " not known";
return oss.str();
}
}
assert(false && "should never get here in check_unparsed_keywords");
}
return "";
}
void LuaKeywordParser::final_check_throw() {
eng::string err = final_check();
if (!err.empty()) {
luaL_error(L_, "%s", err.c_str());
}
}

View File

@@ -531,14 +531,15 @@ class LuaKeywordParser {
};
};
private:
bool not_table_;
lua_State *L_;
int slot_;
eng::set<const char *, cmp_char> parsed_;
void init(const lua_State *L, int slot);
public:
// The constructor will throw a lua error if the
// slot is not a table.
// If the slot is not a table, sets the not_table
// flag and creates a dummy table in the slot.
LuaKeywordParser(lua_State *L, int slot);
LuaKeywordParser(const LuaStack &LS, LuaSlot slot) : LuaKeywordParser(LS.state(), slot.index()) {}
@@ -546,9 +547,12 @@ public:
// Return true if the value is non-nil.
bool parse(LuaSlot slot, const char *kw);
// Check if there are any keywords in the table that
// were never parsed. If so, throw an error.
void check_unparsed_keywords();
// Check if there were any errors. If so, return an
// error message.
eng::string final_check();
// Check if there are any errors. If so, throw a lua error.
void final_check_throw();
// Fetch the state pointer.
lua_State *state() const { return L_; }

View File

@@ -64,7 +64,7 @@ LuaDefine(tangible_animate, "tan,configtable",
int64_t id = w->alloc_id_predictable();
AnimStep step;
step.configure(kp, tan->anim_queue_.back());
kp.check_unparsed_keywords();
kp.final_check_throw();
if (step.action() == "") {
luaL_error(L, "animation action must be specified");
}
@@ -152,7 +152,7 @@ LuaDefine(tangible_build, "config",
// Parse the initial animation step.
AnimStep initstep, blank;
initstep.configure(kp, blank);
kp.check_unparsed_keywords();
kp.final_check_throw();
if (!initstep.has_xyz()) {
luaL_error(L, "You must specify (X,Y,Z) for new tangible");
}
@@ -368,7 +368,7 @@ LuaDefine(tangible_find, "config",
LuaKeywordParser kw(LS, config);
PlaneScan scan;
scan.configure(kw);
kw.check_unparsed_keywords();
kw.final_check_throw();
// When the configure routine sees the 'near' flag, it stores the tangible
// ID, but not the center and plane, because doing so would require it to
@@ -761,12 +761,14 @@ int lfn_http_request(lua_State *L, const char *method) {
LuaArg request;
LuaRet response;
LuaStack LS(L, request, response);
LuaKeywordParser kp(LS, request);
HttpClientRequest req;
// Parse the request and make sure it's valid.
// If not, immediately pass a '400 bad request' back to lua.
req.set_method(method);
req.set_config(LS, request);
req.configure(kp);
kp.final_check_throw();
req.set_defaults();
eng::string error = req.check();
if (!error.empty()) {

View File

@@ -533,10 +533,13 @@ HttpServerResponse World::http_serve(const HttpParser &request) {
}
// Try to convert the table into a response.
// If this results in an error, we don't have to do
// anything special, set_config will store the error.
response.set_config(LS, LuaSpecial(newtop));
LuaKeywordParser kp(LS, LuaSpecial(newtop));
response.configure(kp);
response.set_defaults();
eng::string kperr = kp.final_check();
if (!kperr.empty()) {
response.fail(500, kperr);
}
LS.result();
return response;
}