Add PUT and DELETE request + specific method value to HTTP API (#9909)

This commit is contained in:
Lejo 2020-07-30 00:16:21 +03:00 committed by GitHub
parent f34abaedd2
commit 715a123a33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 98 additions and 44 deletions

@ -8071,11 +8071,13 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`.
timeout = 10, timeout = 10,
-- Timeout for connection in seconds. Default is 3 seconds. -- Timeout for connection in seconds. Default is 3 seconds.
post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"}, method = "GET", "POST", "PUT" or "DELETE"
-- Optional, if specified a POST request with post_data is performed. -- The http method to use. Defaults to "GET".
data = "Raw request data string" OR {field1 = "data1", field2 = "data2"},
-- Data for the POST, PUT or DELETE request.
-- Accepts both a string and a table. If a table is specified, encodes -- Accepts both a string and a table. If a table is specified, encodes
-- table as x-www-form-urlencoded key-value pairs. -- table as x-www-form-urlencoded key-value pairs.
-- If post_data is not specified, a GET request is performed instead.
user_agent = "ExampleUserAgent", user_agent = "ExampleUserAgent",
-- Optional, if specified replaces the default minetest user agent with -- Optional, if specified replaces the default minetest user agent with
@ -8089,6 +8091,10 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`.
multipart = boolean multipart = boolean
-- Optional, if true performs a multipart HTTP request. -- Optional, if true performs a multipart HTTP request.
-- Default is false. -- Default is false.
-- Post only, data must be array
post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"},
-- Deprecated, use `data` instead. Forces `method = "POST"`.
} }
`HTTPRequestResult` definition `HTTPRequestResult` definition

@ -260,7 +260,8 @@ void ClientMediaDownloader::initialStep(Client *client)
fetch_request.request_id = m_httpfetch_next_id; // == i fetch_request.request_id = m_httpfetch_next_id; // == i
fetch_request.timeout = m_httpfetch_timeout; fetch_request.timeout = m_httpfetch_timeout;
fetch_request.connect_timeout = m_httpfetch_timeout; fetch_request.connect_timeout = m_httpfetch_timeout;
fetch_request.post_data = required_hash_set; fetch_request.method = HTTP_POST;
fetch_request.raw_data = required_hash_set;
fetch_request.extra_headers.emplace_back( fetch_request.extra_headers.emplace_back(
"Content-Type: application/octet-stream"); "Content-Type: application/octet-stream");

@ -294,13 +294,11 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss);
} }
// Set POST (or GET) data // Set data from fields or raw_data
if (request.post_fields.empty() && request.post_data.empty()) { if (request.multipart) {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
} else if (request.multipart) {
curl_httppost *last = NULL; curl_httppost *last = NULL;
for (StringMap::iterator it = request.post_fields.begin(); for (StringMap::iterator it = request.fields.begin();
it != request.post_fields.end(); ++it) { it != request.fields.end(); ++it) {
curl_formadd(&post, &last, curl_formadd(&post, &last,
CURLFORM_NAMELENGTH, it->first.size(), CURLFORM_NAMELENGTH, it->first.size(),
CURLFORM_PTRNAME, it->first.c_str(), CURLFORM_PTRNAME, it->first.c_str(),
@ -311,28 +309,42 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
curl_easy_setopt(curl, CURLOPT_HTTPPOST, post); curl_easy_setopt(curl, CURLOPT_HTTPPOST, post);
// request.post_fields must now *never* be // request.post_fields must now *never* be
// modified until CURLOPT_HTTPPOST is cleared // modified until CURLOPT_HTTPPOST is cleared
} else if (request.post_data.empty()) { } else {
switch (request.method) {
case HTTP_GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
break;
case HTTP_POST:
curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POST, 1);
break;
case HTTP_PUT:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
break;
case HTTP_DELETE:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
}
if (request.method != HTTP_GET) {
if (!request.raw_data.empty()) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
request.raw_data.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
request.raw_data.c_str());
} else if (!request.fields.empty()) {
std::string str; std::string str;
for (auto &post_field : request.post_fields) { for (auto &field : request.fields) {
if (!str.empty()) if (!str.empty())
str += "&"; str += "&";
str += urlencode(post_field.first); str += urlencode(field.first);
str += "="; str += "=";
str += urlencode(post_field.second); str += urlencode(field.second);
} }
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
str.size()); str.size());
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS,
str.c_str()); str.c_str());
} else { }
curl_easy_setopt(curl, CURLOPT_POST, 1); }
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
request.post_data.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
request.post_data.c_str());
// request.post_data must now *never* be
// modified until CURLOPT_POSTFIELDS is cleared
} }
// Set additional HTTP headers // Set additional HTTP headers
for (const std::string &extra_header : request.extra_headers) { for (const std::string &extra_header : request.extra_headers) {

@ -28,6 +28,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define HTTPFETCH_DISCARD 0 #define HTTPFETCH_DISCARD 0
#define HTTPFETCH_SYNC 1 #define HTTPFETCH_SYNC 1
// Methods
enum HttpMethod : u8
{
HTTP_GET,
HTTP_POST,
HTTP_PUT,
HTTP_DELETE,
};
struct HTTPFetchRequest struct HTTPFetchRequest
{ {
std::string url = ""; std::string url = "";
@ -50,12 +59,15 @@ struct HTTPFetchRequest
// application/x-www-form-urlencoded. POST-only. // application/x-www-form-urlencoded. POST-only.
bool multipart = false; bool multipart = false;
// POST fields. Fields are escaped properly. // The Method to use default = GET
// If this is empty a GET request is done instead. // Avaible methods GET, POST, PUT, DELETE
StringMap post_fields; HttpMethod method = HTTP_GET;
// Raw POST data, overrides post_fields. // Fields of the request
std::string post_data; StringMap fields;
// Raw data of the request overrides fields
std::string raw_data;
// If not empty, should contain entries such as "Accept: text/html" // If not empty, should contain entries such as "Accept: text/html"
std::vector<std::string> extra_headers; std::vector<std::string> extra_headers;

@ -1034,7 +1034,7 @@ const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = {
"This server has experienced an internal error. You will now be disconnected." "This server has experienced an internal error. You will now be disconnected."
}; };
enum PlayerListModifer: u8 enum PlayerListModifer : u8
{ {
PLAYER_LIST_INIT, PLAYER_LIST_INIT,
PLAYER_LIST_ADD, PLAYER_LIST_ADD,

@ -49,17 +49,40 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
req.multipart = getboolfield_default(L, 1, "multipart", false); req.multipart = getboolfield_default(L, 1, "multipart", false);
req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000; req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000;
// post_data: if table, post form data, otherwise raw data lua_getfield(L, 1, "method");
if (lua_isstring(L, -1)) {
std::string mth = getstringfield_default(L, 1, "method", "");
if (mth == "GET")
req.method = HTTP_GET;
else if (mth == "POST")
req.method = HTTP_POST;
else if (mth == "PUT")
req.method = HTTP_PUT;
else if (mth == "DELETE")
req.method = HTTP_DELETE;
}
lua_pop(L, 1);
// post_data: if table, post form data, otherwise raw data DEPRECATED use data and method instead
lua_getfield(L, 1, "post_data"); lua_getfield(L, 1, "post_data");
if (lua_isnil(L, 2)) {
lua_pop(L, 1);
lua_getfield(L, 1, "data");
}
else {
req.method = HTTP_POST;
}
if (lua_istable(L, 2)) { if (lua_istable(L, 2)) {
lua_pushnil(L); lua_pushnil(L);
while (lua_next(L, 2) != 0) { while (lua_next(L, 2) != 0) {
req.post_fields[readParam<std::string>(L, -2)] = readParam<std::string>(L, -1); req.fields[readParam<std::string>(L, -2)] = readParam<std::string>(L, -1);
lua_pop(L, 1); lua_pop(L, 1);
} }
} else if (lua_isstring(L, 2)) { } else if (lua_isstring(L, 2)) {
req.post_data = readParam<std::string>(L, 2); req.raw_data = readParam<std::string>(L, 2);
} }
lua_pop(L, 1); lua_pop(L, 1);
lua_getfield(L, 1, "extra_headers"); lua_getfield(L, 1, "extra_headers");

@ -32,10 +32,10 @@ private:
static void read_http_fetch_request(lua_State *L, HTTPFetchRequest &req); static void read_http_fetch_request(lua_State *L, HTTPFetchRequest &req);
static void push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed = true); static void push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed = true);
// http_fetch_sync({url=, timeout=, post_data=}) // http_fetch_sync({url=, timeout=, data=})
static int l_http_fetch_sync(lua_State *L); static int l_http_fetch_sync(lua_State *L);
// http_fetch_async({url=, timeout=, post_data=}) // http_fetch_async({url=, timeout=, data=})
static int l_http_fetch_async(lua_State *L); static int l_http_fetch_async(lua_State *L);
// http_fetch_async_get(handle) // http_fetch_async_get(handle)

@ -261,11 +261,11 @@ void sendAnnounce(AnnounceAction action,
HTTPFetchRequest fetch_request; HTTPFetchRequest fetch_request;
fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce"); fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce");
fetch_request.post_fields["json"] = fastWriteJson(server); fetch_request.method = HTTP_POST;
fetch_request.fields["json"] = fastWriteJson(server);
fetch_request.multipart = true; fetch_request.multipart = true;
httpfetch_async(fetch_request); httpfetch_async(fetch_request);
} }
#endif #endif
} // namespace ServerList } // namespace ServerList