diff --git a/src/common/bnsql_http_routes.hpp b/src/common/bnsql_http_routes.hpp index d18bfcf..6a07cd8 100644 --- a/src/common/bnsql_http_routes.hpp +++ b/src/common/bnsql_http_routes.hpp @@ -209,200 +209,6 @@ inline std::string settings_to_json(const RuntimeSettingsSnapshot& snap) { return json.str(); } -// Query callback type: takes SQL string, returns QueryResult -using HttpQueryFn = std::function; - -/** - * Set up standard BNSQL HTTP routes on an httplib::Server. - * - * @param svr The httplib server to add routes to - * @param query_fn Query callback (thread-safe — caller must serialize if needed) - * @param auth_token Optional auth token (empty = no auth) - * @param port Port number (for display in welcome message) - */ -inline void setup_http_routes( - httplib::Server& svr, - HttpQueryFn query_fn, - const std::string& auth_token, - int port) -{ - // Shared state for queue management - auto query_mutex = std::make_shared(); - auto queue_count = std::make_shared>(0); - auto query_fn_ptr = std::make_shared(std::move(query_fn)); - auto auth = std::make_shared(auth_token); - - auto check_auth = [auth](const httplib::Request& req, httplib::Response& res) -> bool { - if (auth->empty()) return true; - std::string token; - if (req.has_header("X-XSQL-Token")) { - token = req.get_header_value("X-XSQL-Token"); - } else if (req.has_header("Authorization")) { - const std::string a = req.get_header_value("Authorization"); - if (a.rfind("Bearer ", 0) == 0) token = a.substr(7); - } - if (token == *auth) return true; - res.status = 401; - res.set_content("{\"success\":false,\"error\":\"Unauthorized\"}", "application/json"); - return false; - }; - - // GET / - Welcome message - svr.Get("/", [port](const httplib::Request&, httplib::Response& res) { - std::string welcome = "BNSQL HTTP Server\n\n" - "Endpoints:\n" - " GET /help - API documentation\n" - " POST /query - Execute SQL query\n" - " GET /status - Health check\n" - " GET /settings - Runtime settings\n" - " POST /shutdown - Stop server\n\n" - "Example:\n" - " curl http://localhost:" + std::to_string(port) + "/help\n" - " curl -X POST http://localhost:" + std::to_string(port) + "/query -d \"SELECT name FROM funcs LIMIT 5\"\n"; - res.set_content(welcome, "text/plain"); - }); - - // GET /help - API documentation (public, no auth) - svr.Get("/help", [](const httplib::Request&, httplib::Response& res) { - res.set_content(http_help_text(), "text/plain"); - }); - - // POST /query - Execute SQL with queue management - svr.Post("/query", [query_fn_ptr, query_mutex, queue_count, check_auth]( - const httplib::Request& req, httplib::Response& res) - { - if (!check_auth(req, res)) return; - - std::string sql = req.body; - if (sql.empty()) { - res.status = 400; - res.set_content("{\"success\":false,\"error\":\"Empty query\"}", "application/json"); - return; - } - - auto& settings = runtime_settings(); - - // Atomic queue admission via compare-exchange - size_t max_queue = settings.max_queue(); - size_t current = queue_count->load(); - while (true) { - if (max_queue > 0 && current >= max_queue) { - res.status = 503; - res.set_content("{\"success\":false,\"error\":\"Queue full\"}", "application/json"); - return; - } - if (queue_count->compare_exchange_weak(current, current + 1)) - break; - } - - // Try to acquire lock with admission timeout - int admission_ms = settings.queue_admission_timeout_ms(); - bool acquired = false; - if (admission_ms <= 0) { - query_mutex->lock(); - acquired = true; - } else { - acquired = query_mutex->try_lock_for(std::chrono::milliseconds(admission_ms)); - } - - if (!acquired) { - queue_count->fetch_sub(1); - res.status = 408; - res.set_content("{\"success\":false,\"error\":\"Queue admission timeout\"}", "application/json"); - return; - } - - // Multi-statement: split via xsql::run_script and dispatch each - // statement through the supplied single-statement executor. Single - // statement is just array-of-one in the canonical envelope. - xsql::ScriptOptions opts; - auto cont_it = req.params.find("continue_on_error"); - if (cont_it != req.params.end() && cont_it->second == "1") { - opts.continue_on_error = true; - } - auto incl_it = req.params.find("include_sql"); - if (incl_it != req.params.end() && incl_it->second == "1") { - opts.include_sql = true; - } - // Optional output format (default json). text/csv/tsv are for direct - // terminal/curl use; json stays the canonical machine format. - std::string format = "json"; - auto fmt_it = req.params.find("format"); - if (fmt_it != req.params.end() && !fmt_it->second.empty()) { - format = fmt_it->second; - } - - auto script = xsql::run_script(sql, opts, - [&](const std::string& stmt, xsql::ScriptStatementResult& out) { - auto r = (*query_fn_ptr)(stmt); - out.columns = r.columns; - out.rows.reserve(r.rows.size()); - for (const auto& row : r.rows) { - out.rows.push_back(row.values); - } - out.elapsed_ms = r.elapsed_ms; - out.success = r.success; - out.error = r.error; - }); - query_mutex->unlock(); - queue_count->fetch_sub(1); - - if (format == "text") { - res.set_content(xsql::script_result_to_text(script), "text/plain"); - } else if (format == "csv") { - res.set_content(xsql::script_result_to_csv(script), "text/csv"); - } else if (format == "tsv") { - res.set_content(xsql::script_result_to_tsv(script), - "text/tab-separated-values"); - } else { - res.set_content(xsql::script_result_to_json(script, opts.include_sql), - "application/json"); - } - }); - - // GET /status - Health check with runtime settings - svr.Get("/status", [query_fn_ptr, query_mutex, check_auth](const httplib::Request& req, httplib::Response& res) { - if (!check_auth(req, res)) return; - - // Use try_lock to avoid blocking status checks - std::unique_lock lock(*query_mutex, std::defer_lock); - std::string func_count = "?"; - if (lock.try_lock_for(std::chrono::seconds(2))) { - auto result = (*query_fn_ptr)("SELECT COUNT(*) FROM funcs"); - if (result.success && !result.empty()) { - func_count = result.rows[0][0]; - } - } - - auto snap = runtime_settings().snapshot(); - std::ostringstream json; - json << "{" - << "\"success\":true" - << ",\"status\":\"ok\"" - << ",\"tool\":\"bnsql\"" - << ",\"functions\":" << (func_count == "?" ? "null" : func_count) - << ",\"settings\":" << settings_to_json(snap) - << "}"; - res.set_content(json.str(), "application/json"); - }); - - // GET /settings - Runtime settings - svr.Get("/settings", [check_auth](const httplib::Request& req, httplib::Response& res) { - if (!check_auth(req, res)) return; - auto snap = runtime_settings().snapshot(); - res.set_content(settings_to_json(snap), "application/json"); - }); - - // POST /shutdown - Graceful shutdown - svr.Post("/shutdown", [&svr, check_auth](const httplib::Request& req, httplib::Response& res) { - if (!check_auth(req, res)) return; - res.set_content("{\"success\":true,\"message\":\"Shutting down\"}", "application/json"); - std::thread([&svr] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - svr.stop(); - }).detach(); - }); -} } // namespace bnsql