Skip to content

Commit 77b634e

Browse files
committed
bootstrap httpserver
1 parent f551307 commit 77b634e

8 files changed

+196
-102
lines changed

.github/workflows/MainDistributionPipeline.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ jobs:
1818
with:
1919
duckdb_version: main
2020
ci_tools_version: main
21-
extension_name: quack
21+
extension_name: httpserver
2222

2323
duckdb-stable-build:
2424
name: Build extension binaries
2525
uses: duckdb/extension-ci-tools/.github/workflows/[email protected]
2626
with:
2727
duckdb_version: v1.1.1
2828
ci_tools_version: v1.1.1
29-
extension_name: quack
29+
extension_name: httpserver
3030

3131
duckdb-stable-deploy:
3232
name: Deploy extension binaries
@@ -35,5 +35,5 @@ jobs:
3535
secrets: inherit
3636
with:
3737
duckdb_version: v1.1.1
38-
extension_name: quack
38+
extension_name: httpserver
3939
deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' }}

CMakeLists.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.5)
22

33
# Set extension name here
4-
set(TARGET_NAME quack)
4+
set(TARGET_NAME httpserver)
55

66
# DuckDB's extension distribution supports vcpkg. As such, dependencies can be added in ./vcpkg.json and then
77
# used in cmake with find_package. Feel free to remove or replace with other dependencies.
@@ -12,9 +12,9 @@ set(EXTENSION_NAME ${TARGET_NAME}_extension)
1212
set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension)
1313

1414
project(${TARGET_NAME})
15-
include_directories(src/include)
15+
include_directories(src/include duckdb/third_party/httplib)
1616

17-
set(EXTENSION_SOURCES src/quack_extension.cpp)
17+
set(EXTENSION_SOURCES src/httpserver_extension.cpp)
1818

1919
build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES})
2020
build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES})

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
22

33
# Configuration of extension
4-
EXT_NAME=quack
4+
EXT_NAME=httpserver
55
EXT_CONFIG=${PROJ_DIR}extension_config.cmake
66

77
# Include the Makefile from extension-ci-tools
8-
include extension-ci-tools/makefiles/duckdb_extension.Makefile
8+
include extension-ci-tools/makefiles/duckdb_extension.Makefile

extension_config.cmake

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# This file is included by DuckDB's build system. It specifies which extension to load
22

33
# Extension from this repo
4-
duckdb_extension_load(quack
4+
duckdb_extension_load(httpserver
55
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}
66
LOAD_TESTS
77
)
88

99
# Any extra extensions that should be built
10-
# e.g.: duckdb_extension_load(json)
10+
# e.g.: duckdb_extension_load(json)

src/httpserver_extension.cpp

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#define DUCKDB_EXTENSION_MAIN
2+
#include "httpserver_extension.hpp"
3+
#include "duckdb.hpp"
4+
#include "duckdb/common/exception.hpp"
5+
#include "duckdb/common/string_util.hpp"
6+
#include "duckdb/function/scalar_function.hpp"
7+
#include "duckdb/main/extension_util.hpp"
8+
#include "duckdb/common/atomic.hpp"
9+
#include "duckdb/common/exception/http_exception.hpp"
10+
11+
#define CPPHTTPLIB_OPENSSL_SUPPORT
12+
#include "httplib.hpp"
13+
14+
#include <thread>
15+
#include <memory>
16+
17+
namespace duckdb {
18+
19+
struct HttpServerState {
20+
std::unique_ptr<duckdb_httplib_openssl::Server> server;
21+
std::unique_ptr<std::thread> server_thread;
22+
std::atomic<bool> is_running;
23+
DatabaseInstance* db_instance;
24+
25+
HttpServerState() : is_running(false), db_instance(nullptr) {}
26+
};
27+
28+
static HttpServerState global_state;
29+
30+
static void HandleQuery(const string& query, duckdb_httplib_openssl::Response& res) {
31+
try {
32+
if (!global_state.db_instance) {
33+
throw IOException("Database instance not initialized");
34+
}
35+
36+
Connection con(*global_state.db_instance);
37+
auto result = con.Query(query);
38+
39+
if (result->HasError()) {
40+
res.status = 400;
41+
res.set_content(result->GetError(), "text/plain");
42+
return;
43+
}
44+
45+
res.set_content(result->ToString(), "text/plain");
46+
} catch (const Exception& ex) {
47+
res.status = 400;
48+
res.set_content(ex.what(), "text/plain");
49+
}
50+
}
51+
52+
void HttpServerStart(DatabaseInstance& db, string_t host, int32_t port) {
53+
if (global_state.is_running) {
54+
throw IOException("HTTP server is already running");
55+
}
56+
57+
global_state.db_instance = &db;
58+
global_state.server.reset(new duckdb_httplib_openssl::Server());
59+
global_state.is_running = true;
60+
61+
// Handle GET requests
62+
global_state.server->Get("/query", [](const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
63+
if (!req.has_param("q")) {
64+
res.status = 400;
65+
res.set_content("Missing query parameter 'q'", "text/plain");
66+
return;
67+
}
68+
69+
auto query = req.get_param_value("q");
70+
HandleQuery(query, res);
71+
});
72+
73+
// Handle POST requests
74+
global_state.server->Post("/query", [](const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
75+
if (req.body.empty()) {
76+
res.status = 400;
77+
res.set_content("Empty query body", "text/plain");
78+
return;
79+
}
80+
HandleQuery(req.body, res);
81+
});
82+
83+
// Health check endpoint
84+
global_state.server->Get("/health", [](const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
85+
res.set_content("OK", "text/plain");
86+
});
87+
88+
string host_str = host.GetString();
89+
global_state.server_thread.reset(new std::thread([host_str, port]() {
90+
if (!global_state.server->listen(host_str.c_str(), port)) {
91+
global_state.is_running = false;
92+
throw IOException("Failed to start HTTP server on " + host_str + ":" + std::to_string(port));
93+
}
94+
}));
95+
}
96+
97+
void HttpServerStop() {
98+
if (global_state.is_running) {
99+
global_state.server->stop();
100+
if (global_state.server_thread && global_state.server_thread->joinable()) {
101+
global_state.server_thread->join();
102+
}
103+
global_state.server.reset();
104+
global_state.server_thread.reset();
105+
global_state.db_instance = nullptr;
106+
global_state.is_running = false;
107+
}
108+
}
109+
110+
static void LoadInternal(DatabaseInstance &instance) {
111+
auto httpserve_start = ScalarFunction("httpserve_start",
112+
{LogicalType::VARCHAR, LogicalType::INTEGER},
113+
LogicalType::VARCHAR,
114+
[&](DataChunk &args, ExpressionState &state, Vector &result) {
115+
auto &host_vector = args.data[0];
116+
auto &port_vector = args.data[1];
117+
118+
UnaryExecutor::Execute<string_t, string_t>(
119+
host_vector, result, args.size(),
120+
[&](string_t host) {
121+
auto port = ((int32_t*)port_vector.GetData())[0];
122+
HttpServerStart(instance, host, port);
123+
return StringVector::AddString(result, "HTTP server started on " + host.GetString() + ":" + std::to_string(port));
124+
});
125+
});
126+
127+
auto httpserve_stop = ScalarFunction("httpserve_stop",
128+
{},
129+
LogicalType::VARCHAR,
130+
[](DataChunk &args, ExpressionState &state, Vector &result) {
131+
HttpServerStop();
132+
result.SetValue(0, Value("HTTP server stopped"));
133+
});
134+
135+
ExtensionUtil::RegisterFunction(instance, httpserve_start);
136+
ExtensionUtil::RegisterFunction(instance, httpserve_stop);
137+
}
138+
139+
void HttpserverExtension::Load(DuckDB &db) {
140+
LoadInternal(*db.instance);
141+
}
142+
143+
std::string HttpserverExtension::Name() {
144+
return "httpserver";
145+
}
146+
147+
std::string HttpserverExtension::Version() const {
148+
#ifdef EXT_VERSION_HTTPSERVER
149+
return EXT_VERSION_HTTPSERVER;
150+
#else
151+
return "";
152+
#endif
153+
}
154+
155+
} // namespace duckdb
156+
157+
extern "C" {
158+
DUCKDB_EXTENSION_API void httpserver_init(duckdb::DatabaseInstance &db) {
159+
duckdb::DuckDB db_wrapper(db);
160+
db_wrapper.LoadExtension<duckdb::HttpserverExtension>();
161+
}
162+
163+
DUCKDB_EXTENSION_API const char *httpserver_version() {
164+
return duckdb::DuckDB::LibraryVersion();
165+
}
166+
}

src/include/httpserver_extension.hpp

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include "duckdb.hpp"
4+
#include "duckdb/common/file_system.hpp"
5+
6+
namespace duckdb {
7+
8+
class HttpserverExtension : public Extension {
9+
public:
10+
void Load(DuckDB &db) override;
11+
std::string Name() override;
12+
std::string Version() const override;
13+
};
14+
15+
// Static server state declarations
16+
struct HttpServerState;
17+
void HttpServerStart(DatabaseInstance& db, string_t host, int32_t port);
18+
void HttpServerStop();
19+
20+
} // namespace duckdb

src/include/quack_extension.hpp

-14
This file was deleted.

src/quack_extension.cpp

-78
This file was deleted.

0 commit comments

Comments
 (0)