From 893a74f9d76a73ccc2c588f78443cb77447ed9f2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 26 Apr 2025 15:17:25 +0200 Subject: [PATCH] Support HEAD and PATCH methods in http api --- builtin/game/features.lua | 1 + doc/lua_api.md | 17 ++++++++++------- src/httpfetch.cpp | 12 ++++++++++-- src/httpfetch.h | 2 ++ src/script/lua_api/l_http.cpp | 27 ++++++++++++++------------- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 4d5e919b4..1b329c7a3 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -46,6 +46,7 @@ core.features = { biome_weights = true, particle_blend_clip = true, remove_item_match_meta = true, + httpfetch_additional_methods = true, } function core.has_feature(arg) diff --git a/doc/lua_api.md b/doc/lua_api.md index f9b166bf6..ab2a4229c 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5780,6 +5780,8 @@ Utilities particle_blend_clip = true, -- The `match_meta` optional parameter is available for `InvRef:remove_item()` (5.12.0) remove_item_match_meta = true, + -- The HTTP API supports the HEAD and PATCH methods (5.12.0) + httpfetch_additional_methods = true, } ``` @@ -11824,22 +11826,22 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`. ```lua { - url = "http://example.org", + url = "https://example.org", timeout = 10, -- Timeout for request to be completed in seconds. Default depends on engine settings. - method = "GET", "POST", "PUT" or "DELETE" + method = "GET", "HEAD", "POST", "PUT", "PATCH" 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. + data = "Raw request data string" or {field1 = "data1", field2 = "data2"}, + -- Data for the POST, PUT, PATCH 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. user_agent = "ExampleUserAgent", -- Optional, if specified replaces the default Luanti user agent with - -- given string + -- given string. extra_headers = { "Accept-Language: en-us", "Accept-Charset: utf-8" }, -- Optional, if specified adds additional headers to the HTTP request. @@ -11849,7 +11851,7 @@ Used by `HTTPApiTable.fetch` and `HTTPApiTable.fetch_async`. multipart = boolean -- Optional, if true performs a multipart HTTP request. -- Default is false. - -- Not allowed for GET method and `data` must be a table. + -- Not allowed for GET or HEAD method and `data` must be a table. post_data = "Raw POST request data string" OR {field1 = "data1", field2 = "data2"}, -- Deprecated, use `data` instead. Forces `method = "POST"`. @@ -11877,7 +11879,8 @@ Passed to `HTTPApiTable.fetch` callback. Returned by code = 200, -- HTTP status code - data = "response" + data = "", + -- Response body } ``` diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 2f452ff57..3fac21f92 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -282,20 +282,28 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, case HTTP_GET: curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); break; + case HTTP_HEAD: + // This is kinda pointless right now, since we don't return response headers (TODO?) + curl_easy_setopt(curl, CURLOPT_NOBODY, 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_PATCH: + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + break; case HTTP_DELETE: curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); break; } + const bool has_request_body = request.method != HTTP_GET && request.method != HTTP_HEAD; // Set data from fields or raw_data if (request.multipart) { - assert(request.method != HTTP_GET); + assert(has_request_body); multipart_mime = curl_mime_init(curl); for (auto &it : request.fields) { curl_mimepart *part = curl_mime_addpart(multipart_mime); @@ -303,7 +311,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_, curl_mime_data(part, it.second.c_str(), it.second.size()); } curl_easy_setopt(curl, CURLOPT_MIMEPOST, multipart_mime); - } else if (request.method != HTTP_GET) { + } else if (has_request_body) { if (request.fields.empty()) { // Note that we need to set this to an empty buffer (not NULL) // even if no data is to be sent. diff --git a/src/httpfetch.h b/src/httpfetch.h index 286647e3f..99c382191 100644 --- a/src/httpfetch.h +++ b/src/httpfetch.h @@ -31,8 +31,10 @@ namespace { enum HttpMethod : u8 { HTTP_GET, + HTTP_HEAD, HTTP_POST, HTTP_PUT, + HTTP_PATCH, HTTP_DELETE, }; diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index 2669779e5..eadc6f95d 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -7,6 +7,7 @@ #include "common/c_content.h" #include "lua_api/l_http.h" #include "cpp_api/s_security.h" +#include "util/enum_string.h" #include "httpfetch.h" #include "settings.h" #include "debug.h" @@ -19,6 +20,16 @@ lua_pushcfunction(L, l_http_##name); \ lua_settable(L, -3); +const static EnumString es_HttpMethod[] = { + {HTTP_GET, "GET"}, + {HTTP_HEAD, "HEAD"}, + {HTTP_POST, "POST"}, + {HTTP_PUT, "PUT"}, + {HTTP_PATCH, "PATCH"}, + {HTTP_DELETE, "DELETE"}, + {0, nullptr} +}; + #if USE_CURL void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) { @@ -32,17 +43,8 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) req.timeout *= 1000; 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; - } + if (lua_isstring(L, -1)) + string_to_enum(es_HttpMethod, req.method, lua_tostring(L, -1)); lua_pop(L, 1); // post_data: if table, post form data, otherwise raw data DEPRECATED use data and method instead @@ -50,8 +52,7 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) if (lua_isnil(L, 2)) { lua_pop(L, 1); lua_getfield(L, 1, "data"); - } - else { + } else { req.method = HTTP_POST; }