Skip to content

Commit

Permalink
bootstrap httpserver
Browse files Browse the repository at this point in the history
  • Loading branch information
lmangani committed Oct 8, 2024
1 parent f551307 commit 77b634e
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 102 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/MainDistributionPipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ jobs:
with:
duckdb_version: main
ci_tools_version: main
extension_name: quack
extension_name: httpserver

duckdb-stable-build:
name: Build extension binaries
uses: duckdb/extension-ci-tools/.github/workflows/[email protected]
with:
duckdb_version: v1.1.1
ci_tools_version: v1.1.1
extension_name: quack
extension_name: httpserver

duckdb-stable-deploy:
name: Deploy extension binaries
Expand All @@ -35,5 +35,5 @@ jobs:
secrets: inherit
with:
duckdb_version: v1.1.1
extension_name: quack
extension_name: httpserver
deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' }}
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5)

# Set extension name here
set(TARGET_NAME quack)
set(TARGET_NAME httpserver)

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

project(${TARGET_NAME})
include_directories(src/include)
include_directories(src/include duckdb/third_party/httplib)

set(EXTENSION_SOURCES src/quack_extension.cpp)
set(EXTENSION_SOURCES src/httpserver_extension.cpp)

build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES})
build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES})
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

# Configuration of extension
EXT_NAME=quack
EXT_NAME=httpserver
EXT_CONFIG=${PROJ_DIR}extension_config.cmake

# Include the Makefile from extension-ci-tools
include extension-ci-tools/makefiles/duckdb_extension.Makefile
include extension-ci-tools/makefiles/duckdb_extension.Makefile
4 changes: 2 additions & 2 deletions extension_config.cmake
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# This file is included by DuckDB's build system. It specifies which extension to load

# Extension from this repo
duckdb_extension_load(quack
duckdb_extension_load(httpserver
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}
LOAD_TESTS
)

# Any extra extensions that should be built
# e.g.: duckdb_extension_load(json)
# e.g.: duckdb_extension_load(json)
166 changes: 166 additions & 0 deletions src/httpserver_extension.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#define DUCKDB_EXTENSION_MAIN
#include "httpserver_extension.hpp"
#include "duckdb.hpp"
#include "duckdb/common/exception.hpp"
#include "duckdb/common/string_util.hpp"
#include "duckdb/function/scalar_function.hpp"
#include "duckdb/main/extension_util.hpp"
#include "duckdb/common/atomic.hpp"
#include "duckdb/common/exception/http_exception.hpp"

#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.hpp"

#include <thread>
#include <memory>

namespace duckdb {

struct HttpServerState {
std::unique_ptr<duckdb_httplib_openssl::Server> server;
std::unique_ptr<std::thread> server_thread;
std::atomic<bool> is_running;
DatabaseInstance* db_instance;

HttpServerState() : is_running(false), db_instance(nullptr) {}
};

static HttpServerState global_state;

static void HandleQuery(const string& query, duckdb_httplib_openssl::Response& res) {
try {
if (!global_state.db_instance) {
throw IOException("Database instance not initialized");
}

Connection con(*global_state.db_instance);
auto result = con.Query(query);

if (result->HasError()) {
res.status = 400;
res.set_content(result->GetError(), "text/plain");
return;
}

res.set_content(result->ToString(), "text/plain");
} catch (const Exception& ex) {
res.status = 400;
res.set_content(ex.what(), "text/plain");
}
}

void HttpServerStart(DatabaseInstance& db, string_t host, int32_t port) {
if (global_state.is_running) {
throw IOException("HTTP server is already running");
}

global_state.db_instance = &db;
global_state.server.reset(new duckdb_httplib_openssl::Server());
global_state.is_running = true;

// Handle GET requests
global_state.server->Get("/query", [](const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
if (!req.has_param("q")) {
res.status = 400;
res.set_content("Missing query parameter 'q'", "text/plain");
return;
}

auto query = req.get_param_value("q");
HandleQuery(query, res);
});

// Handle POST requests
global_state.server->Post("/query", [](const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
if (req.body.empty()) {
res.status = 400;
res.set_content("Empty query body", "text/plain");
return;
}
HandleQuery(req.body, res);
});

// Health check endpoint
global_state.server->Get("/health", [](const duckdb_httplib_openssl::Request& req, duckdb_httplib_openssl::Response& res) {
res.set_content("OK", "text/plain");
});

string host_str = host.GetString();
global_state.server_thread.reset(new std::thread([host_str, port]() {
if (!global_state.server->listen(host_str.c_str(), port)) {
global_state.is_running = false;
throw IOException("Failed to start HTTP server on " + host_str + ":" + std::to_string(port));
}
}));
}

void HttpServerStop() {
if (global_state.is_running) {
global_state.server->stop();
if (global_state.server_thread && global_state.server_thread->joinable()) {
global_state.server_thread->join();
}
global_state.server.reset();
global_state.server_thread.reset();
global_state.db_instance = nullptr;
global_state.is_running = false;
}
}

static void LoadInternal(DatabaseInstance &instance) {
auto httpserve_start = ScalarFunction("httpserve_start",
{LogicalType::VARCHAR, LogicalType::INTEGER},
LogicalType::VARCHAR,
[&](DataChunk &args, ExpressionState &state, Vector &result) {
auto &host_vector = args.data[0];
auto &port_vector = args.data[1];

UnaryExecutor::Execute<string_t, string_t>(
host_vector, result, args.size(),
[&](string_t host) {
auto port = ((int32_t*)port_vector.GetData())[0];
HttpServerStart(instance, host, port);
return StringVector::AddString(result, "HTTP server started on " + host.GetString() + ":" + std::to_string(port));
});
});

auto httpserve_stop = ScalarFunction("httpserve_stop",
{},
LogicalType::VARCHAR,
[](DataChunk &args, ExpressionState &state, Vector &result) {
HttpServerStop();
result.SetValue(0, Value("HTTP server stopped"));
});

ExtensionUtil::RegisterFunction(instance, httpserve_start);
ExtensionUtil::RegisterFunction(instance, httpserve_stop);
}

void HttpserverExtension::Load(DuckDB &db) {
LoadInternal(*db.instance);
}

std::string HttpserverExtension::Name() {
return "httpserver";
}

std::string HttpserverExtension::Version() const {
#ifdef EXT_VERSION_HTTPSERVER
return EXT_VERSION_HTTPSERVER;
#else
return "";
#endif
}

} // namespace duckdb

extern "C" {
DUCKDB_EXTENSION_API void httpserver_init(duckdb::DatabaseInstance &db) {
duckdb::DuckDB db_wrapper(db);
db_wrapper.LoadExtension<duckdb::HttpserverExtension>();
}

DUCKDB_EXTENSION_API const char *httpserver_version() {
return duckdb::DuckDB::LibraryVersion();
}
}
20 changes: 20 additions & 0 deletions src/include/httpserver_extension.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include "duckdb.hpp"
#include "duckdb/common/file_system.hpp"

namespace duckdb {

class HttpserverExtension : public Extension {
public:
void Load(DuckDB &db) override;
std::string Name() override;
std::string Version() const override;
};

// Static server state declarations
struct HttpServerState;
void HttpServerStart(DatabaseInstance& db, string_t host, int32_t port);
void HttpServerStop();

} // namespace duckdb
14 changes: 0 additions & 14 deletions src/include/quack_extension.hpp

This file was deleted.

78 changes: 0 additions & 78 deletions src/quack_extension.cpp

This file was deleted.

0 comments on commit 77b634e

Please sign in to comment.