Skip to content

Commit 510b437

Browse files
committed
Calling DB server from HTTP server
1 parent 0120415 commit 510b437

14 files changed

+241
-17
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,20 @@ file [LICENSE.md](LICENSE.html).
2626
cmake --build build
2727

2828
This will create executable `build/src/acppsrv`.
29+
30+
## Run
31+
32+
1. Create a testing database by script `create_db.sh`.
33+
1. Start the server by command `build/src/acppsrv etc/default_cfg.json`. It
34+
will write log to its standard error.
35+
36+
1. Send a request by command, for example,
37+
38+
curl --data-binary @- -H 'Content-Type: application/json'-v http://localhost:8080/db
39+
40+
The request body in JSON format should be written to stdandard input of
41+
`curl`. Request URI paths and protobuf/JSON request and response formats
42+
have not been documented yet. See source code for available request types.
43+
44+
1. Stop the server by sending it signal `SIGTERM` or `SIGINT` (by `kill` or
45+
`Ctrl-C`.

create_db.sh

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
3+
if [ $# != 1 ]; then
4+
echo "usage: $0 database.sqlite" >&2
5+
exit 1
6+
fi
7+
8+
sqlite3 "$1" <<EOF
9+
create table data (key primary key, value);
10+
EOF

src/CMakeLists.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
protobuf_generate_cpp(
33
PROTO_CPP PROTO_HPP
44
configuration.proto
5+
http_hnd_db.proto
56
http_hnd_stat.proto
67
)
78
message("Protobuf generated headers: ${PROTO_HPP}")
@@ -17,9 +18,10 @@ add_executable(
1718
application.cpp
1819
configuration.cpp
1920
db_server.cpp
20-
http_server.cpp
21+
http_hnd_db.cpp
2122
http_hnd_echo.cpp
2223
http_hnd_stat.cpp
24+
http_server.cpp
2325
log.cpp
2426
sqlite3.cpp
2527
worker.cpp

src/application.cpp

+13-12
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@ bool application::run()
2121
thread_pool database_pool(tp && tp->has_database() ?
2222
&tp->database() : nullptr,
2323
std::string{pool_database_name});
24+
// Set up the database pool
25+
db_server db_srv(cfg.data().has_databases() ?
26+
&cfg.data().databases() : nullptr,
27+
database_pool);
28+
if (!db_srv.run()) {
29+
log_msg(log_level::crit) << "Cannot initialize database server";
30+
return false;
31+
}
2432
// Set up the control pool
2533
boost::asio::signal_set termsig{control_pool.ctx, SIGINT, SIGTERM};
2634
termsig.async_wait(
27-
[&main_pool, &database_pool](const boost::system::error_code& ec,
28-
int sig)
29-
{
35+
[&main_pool, &database_pool, &db_srv](
36+
const boost::system::error_code& ec, int sig
37+
) {
3038
if (ec)
3139
log_msg(log_level::warning) <<
3240
"Waiting for termination signal failed: " << ec.message();
@@ -47,21 +55,14 @@ bool application::run()
4755
}
4856
main_pool.stop();
4957
database_pool.stop();
58+
db_srv.interrupt();
5059
});
5160
// Set up the main pool
52-
http_server http_srv(cfg, main_pool, name, version);
61+
http_server http_srv(cfg, main_pool, name, version, db_srv);
5362
if (!http_srv.run()) {
5463
log_msg(log_level::crit) << "Cannot initialize HTTP server";
5564
return false;
5665
}
57-
// Set up the database pool
58-
db_server db_srv(cfg.data().has_databases() ?
59-
&cfg.data().databases() : nullptr,
60-
database_pool);
61-
if (!db_srv.run()) {
62-
log_msg(log_level::crit) << "Cannot initialize database server";
63-
return false;
64-
}
6566
// Start processing
6667
log_msg(log_level::notice) << "Initialized, starting worker threads";
6768
control_pool.run(false);

src/db_server.cpp

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "db_server.hpp"
22
#include "configuration.hpp"
3+
#include "http_hnd_db.pb.h"
34
#include "log.hpp"
45
#include "worker.hpp"
56
#include <tuple>
@@ -11,7 +12,17 @@ db_server::db_server(const proto::SQLite3* cfg, thread_pool& workers):
1112
cfg(cfg), workers(workers)
1213
{
1314
}
14-
15+
16+
void db_server::interrupt()
17+
{
18+
for (size_t tidx = 1; auto&& db_map: databases) {
19+
log_msg(log_level::debug) << "Interrupting database thread " <<
20+
tidx++ << '/' << workers.size();
21+
for (auto&& db: db_map)
22+
db.second.db.interrupt();
23+
}
24+
}
25+
1526
bool db_server::run()
1627
{
1728
try {
@@ -50,4 +61,16 @@ bool db_server::run()
5061
return true;
5162
}
5263

64+
http_hnd::proto::db::Response
65+
db_server::run_query(http_hnd::proto::db::Request& request)
66+
{
67+
http_hnd::proto::db::Response response;
68+
DEBUG() << "Running database=\"" << request.db() <<
69+
"\" query=\"" << request.query() << '"' <<
70+
" thread=" << thread_pool::this_thread() << '/' << workers.size();
71+
response.set_ok(true);
72+
response.set_msg("ok");
73+
return response;
74+
}
75+
5376
} // namespace acppsrv

src/db_server.hpp

+14
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,30 @@ class SQLite3;
1515

1616
} // namespace proto
1717

18+
namespace http_hnd::proto::db {
19+
20+
class Request;
21+
class Response;
22+
23+
} // namespace http_hnd::proto::db
24+
1825
class db_server {
1926
public:
2027
db_server(const proto::SQLite3* cfg, thread_pool& workers);
2128
bool run();
29+
// May be called from any thread
30+
template <std::invocable<http_hnd::proto::db::Response> Handler>
31+
void query(http_hnd::proto::db::Request&& request, Handler&& handler);
32+
// May be called from any thread
33+
void interrupt();
2234
private:
2335
struct db_def_t {
2436
explicit db_def_t(const std::string& file): db(file) {}
2537
sqlite::connection db;
2638
std::map<std::string, sqlite::query> queries;
2739
};
40+
http_hnd::proto::db::Response
41+
run_query(http_hnd::proto::db::Request& request);
2842
const proto::SQLite3* cfg;
2943
thread_pool& workers;
3044
std::vector<std::map<std::string, db_def_t>> databases;

src/db_server_impl.hpp

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include "db_server.hpp"
4+
#include "http_hnd_db.pb.h"
5+
#include "worker.hpp"
6+
7+
namespace acppsrv {
8+
9+
template <std::invocable<http_hnd::proto::db::Response> Handler>
10+
void db_server::query(http_hnd::proto::db::Request&& request, Handler&& handler)
11+
{
12+
boost::asio::post(workers.ctx,
13+
[this, request = std::move(request),
14+
handler = std::forward<Handler>(handler)]() mutable
15+
{
16+
auto response = run_query(request);
17+
handler(std::move(response));
18+
});
19+
}
20+
21+
} // namespace acppsrv

src/http_hnd_db.cpp

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "http_hnd_db.hpp"
2+
#include "http_hnd_db.pb.h"
3+
#include "db_server.hpp"
4+
#include "db_server_impl.hpp"
5+
#include "http_server_impl.hpp"
6+
#include "log.hpp"
7+
#include <boost/asio/use_awaitable.hpp>
8+
9+
namespace acppsrv::http_hnd {
10+
11+
template <class Executor,
12+
boost::asio::completion_token_for<void(proto::db::Response)> CT>
13+
auto db::call_query(Executor executor, proto::db::Request&& request, CT&& token)
14+
{
15+
auto init =
16+
[this, executor, &request]<class Handler>(Handler&& handler) mutable {
17+
db_srv.query(std::move(request),
18+
[executor, handler = std::forward<Handler>(handler)](
19+
proto::db::Response response
20+
) mutable {
21+
boost::asio::post(executor,
22+
[handler = std::move(handler),
23+
response = std::move(response)]() mutable {
24+
handler(std::move(response));
25+
});
26+
});
27+
};
28+
return boost::asio::async_initiate<CT, void(proto::db::Response)>(init,
29+
token);
30+
}
31+
32+
boost::asio::awaitable<http_handler::http_response_type>
33+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters)
34+
db::handle_async(const http_request_type& request, uint64_t sid, uint64_t req_n)
35+
{
36+
auto [in, response] = parse<proto::db::Request>(request, sid, req_n);
37+
if (!in)
38+
co_return response;
39+
auto executor = co_await boost::asio::this_coro::executor;
40+
proto::db::Response out = co_await call_query(executor, std::move(*in),
41+
boost::asio::use_awaitable);
42+
serialize(request, sid, req_n, out, response);
43+
co_return response;
44+
}
45+
46+
} // namespace acppsrv::http_hnd

src/http_hnd_db.hpp

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
3+
#include "http_server.hpp"
4+
5+
namespace acppsrv {
6+
7+
class db_server;
8+
9+
namespace http_hnd {
10+
11+
namespace proto::db {
12+
13+
class Request;
14+
class Response;
15+
16+
} // namespace proto::db
17+
18+
class db: public http_handler {
19+
public:
20+
explicit db(db_server& db_srv): db_srv(db_srv) {}
21+
bool async() override {
22+
return true;
23+
}
24+
boost::asio::awaitable<http_response_type>
25+
handle_async(const http_request_type& request,
26+
uint64_t sid, uint64_t req_n) override;
27+
private:
28+
template <class Executor,
29+
boost::asio::completion_token_for<void(proto::db::Response)> CT>
30+
auto call_query(Executor executor, proto::db::Request&& request,
31+
CT&& token);
32+
db_server& db_srv;
33+
};
34+
35+
} // namespace http_hnd
36+
37+
} // namespace acppsrv

src/http_hnd_db.proto

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
syntax = "proto3";
2+
3+
package acppsrv.http_hnd.proto.db;
4+
5+
enum Null {
6+
NULL = 0;
7+
}
8+
9+
message Value {
10+
oneof val {
11+
Null v_null = 1;
12+
int64 v_int64 = 2;
13+
double v_double = 3;
14+
string v_text = 4;
15+
}
16+
}
17+
18+
message Row {
19+
repeated Value columns = 1;
20+
}
21+
22+
message Request {
23+
string db = 1;
24+
string query = 2;
25+
repeated Value args = 3;
26+
}
27+
28+
message Response {
29+
bool ok = 1;
30+
string msg = 2;
31+
repeated Row rows = 3;
32+
}

src/http_server.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "finally.hpp"
44
#include "worker.hpp"
55

6+
#include "http_hnd_db.hpp"
67
#include "http_hnd_echo.hpp"
78
#include "http_hnd_stat.hpp"
89

@@ -64,7 +65,8 @@ auto http_server::conn_limit(CompletionToken&& token)
6465

6566
http_server::http_server(const configuration& cfg, thread_pool& workers,
6667
std::string_view app_name,
67-
std::string_view app_version):
68+
std::string_view app_version,
69+
db_server& db_srv):
6870
header_server(std::string(app_name) + "/" + std::string(app_version)),
6971
port(cfg.http_port()), workers(workers), acceptor(workers.ctx)
7072
{
@@ -90,6 +92,7 @@ http_server::http_server(const configuration& cfg, thread_pool& workers,
9092
max_request_body = v;
9193
}
9294
// Initialize handlers
95+
handlers["/db"] = std::make_unique<http_hnd::db>(db_srv);
9396
handlers["/echo"] = std::make_unique<http_hnd::echo>();
9497
auto p_stat = std::make_unique<http_hnd::stat>();
9598
stat = p_stat.get();

src/http_server.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace acppsrv {
1414

1515
class configuration;
16+
class db_server;
1617
class log_msg;
1718
class thread_pool;
1819
class http_handler;
@@ -36,7 +37,8 @@ class http_server {
3637
boost::beast::http::response<boost::beast::http::empty_body>;
3738
static_assert(std::is_same_v<endpoint_type, acceptor_type::endpoint_type>);
3839
http_server(const configuration& cfg, thread_pool& workers,
39-
std::string_view app_name, std::string_view app_version);
40+
std::string_view app_name, std::string_view app_version,
41+
db_server& db_srv);
4042
http_server(const http_server&) = delete;
4143
http_server(http_server&&) = delete;
4244
~http_server() = default;

src/sqlite3.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ connection::connection(std::string file):
5656

5757
connection::~connection() = default;
5858

59+
void connection::interrupt()
60+
{
61+
if (_impl->db) {
62+
sqlite3_interrupt(_impl->db);
63+
log_msg(log_level::debug) << "Interrupted database file \"" << _file <<
64+
'"';
65+
}
66+
}
67+
5968
/*** query::impl *************************************************************/
6069

6170
class query::impl {

0 commit comments

Comments
 (0)