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

View File

@ -8071,11 +8071,13 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`.
timeout = 10,
-- Timeout for connection in seconds. Default is 3 seconds.
post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"},
-- Optional, if specified a POST request with post_data is performed.
method = "GET", "POST", "PUT" or "DELETE"
-- 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
-- table as x-www-form-urlencoded key-value pairs.
-- If post_data is not specified, a GET request is performed instead.
user_agent = "ExampleUserAgent",
-- Optional, if specified replaces the default minetest user agent with
@ -8089,6 +8091,10 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`.
multipart = boolean
-- Optional, if true performs a multipart HTTP request.
-- 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

View File

@ -260,7 +260,8 @@ void ClientMediaDownloader::initialStep(Client *client)
fetch_request.request_id = m_httpfetch_next_id; // == i
fetch_request.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(
"Content-Type: application/octet-stream");

View File

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

View File

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

View File

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

View File

@ -49,17 +49,40 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
req.multipart = getboolfield_default(L, 1, "multipart", false);
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");
if (lua_isnil(L, 2)) {
lua_pop(L, 1);
lua_getfield(L, 1, "data");
}
else {
req.method = HTTP_POST;
}
if (lua_istable(L, 2)) {
lua_pushnil(L);
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);
}
} 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_getfield(L, 1, "extra_headers");

View File

@ -32,10 +32,10 @@ private:
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);
// http_fetch_sync({url=, timeout=, post_data=})
// http_fetch_sync({url=, timeout=, data=})
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);
// http_fetch_async_get(handle)

View File

@ -261,11 +261,11 @@ void sendAnnounce(AnnounceAction action,
HTTPFetchRequest fetch_request;
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;
httpfetch_async(fetch_request);
}
#endif
} // namespace ServerList