Skip to content

Commit 5d13239

Browse files
Adds HEAD request functionality with http_head (#21)
* Adds an initial implementation of http_head * Fixes compiler warning and seg fault due to missing headers on failed requests * Cleans up comments --------- Co-authored-by: Lorenzo Mangani <[email protected]>
1 parent 04f2097 commit 5d13239

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

src/http_client_extension.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,63 @@ static int ConvertListEntryToMap(const list_entry_t& list_entry, const duckdb::V
156156
return result.size();
157157
}
158158

159+
160+
161+
std::string headers_to_string(const duckdb_httplib_openssl::Headers& headers) {
162+
std::string result = "{";
163+
164+
for (const auto& pair : headers) {
165+
const std::string& key = pair.first;
166+
const std::string& value = pair.second;
167+
168+
std::string lower_key = key;
169+
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(),
170+
[](unsigned char c){ return std::tolower(c); });
171+
172+
result += "\"" + escape_json(lower_key) + "\":\"" + escape_json(value) + "\",";
173+
}
174+
175+
if (result.length() > 1) {
176+
result.pop_back(); // Remove trailing comma
177+
}
178+
result += "}";
179+
180+
return result;
181+
}
182+
183+
184+
static void HTTPHeadRequestFunction(DataChunk &args, ExpressionState &state, Vector &result) {
185+
D_ASSERT(args.data.size() == 1);
186+
187+
UnaryExecutor::Execute<string_t, string_t>(args.data[0], result, args.size(), [&](string_t input) {
188+
std::string url = input.GetString();
189+
190+
// Use helper to setup client and parse URL
191+
auto client_and_path = SetupHttpClient(url);
192+
auto &client = client_and_path.first;
193+
auto &path = client_and_path.second;
194+
195+
// Make the HEAD request
196+
auto res = client.Head(path.c_str());
197+
if (res) {
198+
auto headers = headers_to_string(res->headers);
199+
std::string response = StringUtil::Format(
200+
"{ \"status\": %i, \"reason\": \"%s\", \"headers\": \"%s\" }",
201+
res->status,
202+
escape_json(res->reason),
203+
escape_json(headers)
204+
);
205+
return StringVector::AddString(result, response);
206+
} else {
207+
std::string response = StringUtil::Format(
208+
"{ \"status\": %i, \"reason\": \"%s\", \"headers\": \"%s\" }",
209+
-1, GetHttpErrorMessage(res, "HEAD"), ""
210+
);
211+
return StringVector::AddString(result, response);
212+
}
213+
});
214+
}
215+
159216
static void HTTPGetRequestFunction(DataChunk &args, ExpressionState &state, Vector &result) {
160217
D_ASSERT(args.data.size() == 1);
161218

@@ -303,6 +360,10 @@ static void HTTPPostFormRequestFunction(DataChunk &args, ExpressionState &state,
303360

304361

305362
static void LoadInternal(DatabaseInstance &instance) {
363+
ScalarFunctionSet http_head("http_head");
364+
http_head.AddFunction(ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), HTTPHeadRequestFunction));
365+
ExtensionUtil::RegisterFunction(instance, http_head);
366+
306367
ScalarFunctionSet http_get("http_get");
307368
http_get.AddFunction(ScalarFunction({LogicalType::VARCHAR}, LogicalType::JSON(), HTTPGetRequestFunction));
308369
http_get.AddFunction(ScalarFunction(
@@ -358,4 +419,3 @@ DUCKDB_EXTENSION_API const char *http_client_version() {
358419
#ifndef DUCKDB_EXTENSION_MAIN
359420
#error DUCKDB_EXTENSION_MAIN not defined
360421
#endif
361-

test/sql/httpclient.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ require http_client
1313

1414
require json
1515

16-
# temporarily removing public tests - they regularly fail from actions
16+
# temporarily removing public tests - they regularly fail from actions

0 commit comments

Comments
 (0)