17 changed files with 525 additions and 0 deletions
@ -0,0 +1,53 @@ |
|||||||
|
variables: |
||||||
|
GIT_SUBMODULE_STRATEGY: recursive |
||||||
|
|
||||||
|
stages: |
||||||
|
- lint |
||||||
|
- test |
||||||
|
|
||||||
|
.script: &compile |
||||||
|
stage: test |
||||||
|
script: |
||||||
|
- mkdir build && cd build |
||||||
|
- CXXFLAGS=-Werror cmake .. |
||||||
|
- make -j$(nproc) |
||||||
|
|
||||||
|
arch: |
||||||
|
image: registry.gitlab.com/eidheim/docker-images:arch |
||||||
|
<<: *compile |
||||||
|
|
||||||
|
static-analysis: |
||||||
|
image: registry.gitlab.com/eidheim/docker-images:arch |
||||||
|
stage: test |
||||||
|
script: |
||||||
|
- mkdir build && cd build |
||||||
|
- scan-build cmake .. |
||||||
|
- scan-build --status-bugs make -j$(nproc) |
||||||
|
|
||||||
|
thread-safety-analysis: |
||||||
|
image: registry.gitlab.com/eidheim/docker-images:arch |
||||||
|
stage: test |
||||||
|
script: |
||||||
|
- mkdir build && cd build |
||||||
|
- CXX=clang++ CXXFLAGS=-Werror cmake .. |
||||||
|
- make -j$(nproc) |
||||||
|
|
||||||
|
address-sanitizer: |
||||||
|
image: registry.gitlab.com/eidheim/docker-images:arch |
||||||
|
stage: test |
||||||
|
script: |
||||||
|
- mkdir build && cd build |
||||||
|
- CXXFLAGS="-fsanitize=address" cmake .. |
||||||
|
- make -j$(nproc) |
||||||
|
|
||||||
|
check-format: |
||||||
|
image: cppit/jucipp:arch |
||||||
|
stage: lint |
||||||
|
script: |
||||||
|
- 'find src -name "*.cpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' |
||||||
|
- 'find include -name "*.hpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' |
||||||
|
- 'find tests -name "*.cpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' |
||||||
|
- 'find tests -name "*.hpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' |
||||||
|
- 'HAS_ERRORS=$(cat lint-errors.txt | wc -l)' |
||||||
|
- '[ "$HAS_ERRORS" == "0" ] || cat lint-errors.txt' |
||||||
|
- '[ "$HAS_ERRORS" == "0" ]' |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
cmake_minimum_required(VERSION 2.8) |
||||||
|
|
||||||
|
project(store) |
||||||
|
|
||||||
|
find_program(CCACHE_FOUND ccache) |
||||||
|
if(CCACHE_FOUND) |
||||||
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) |
||||||
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) |
||||||
|
else() |
||||||
|
message(STATUS "ccache was not found.") |
||||||
|
endif(CCACHE_FOUND) |
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17) |
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wunused-parameter") |
||||||
|
|
||||||
|
set(BUILD_TESTING OFF CACHE INTERNAL "") |
||||||
|
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/Simple-Web-Server") |
||||||
|
|
||||||
|
set(JSON_BuildTests OFF CACHE INTERNAL "") |
||||||
|
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/json") |
||||||
|
|
||||||
|
add_subdirectory("${CMAKE_SOURCE_DIR}/src") |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2021 Jørgen Sverre Lien Sellæg |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
|
||||||
@ -0,0 +1,107 @@ |
|||||||
|
# Subscribe |
||||||
|
|
||||||
|
## About |
||||||
|
Simple JSON store written in C++. |
||||||
|
|
||||||
|
## Dependencies |
||||||
|
* [simple-web-server](http://gitlab.com/eidheim/Simple-Web-Server/) |
||||||
|
* [json](https://github.com/nlohmann/json) |
||||||
|
|
||||||
|
## Build |
||||||
|
```sh |
||||||
|
git clone https://gitlab.com/zalox/store --recursive |
||||||
|
cd store |
||||||
|
mkdir build |
||||||
|
cd build |
||||||
|
cmake .. |
||||||
|
make -j$(nproc) |
||||||
|
``` |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
## Configuration |
||||||
|
NOTE: The default value will be used if a field is omitted. |
||||||
|
Headers are on the format `["Accept", "*"]`; |
||||||
|
The configuration is passed directly to [the webserver](https://gitlab.com/eidheim/Simple-Web-Server). |
||||||
|
|
||||||
|
```javascript |
||||||
|
{ |
||||||
|
"http": { |
||||||
|
"port": 80, |
||||||
|
// If io_service is not set, number of threads that the server will use when start() is called. |
||||||
|
// Defaults to 1 thread. |
||||||
|
"thread_pool_size": 1, |
||||||
|
// Timeout on request completion. Defaults to 5 seconds. |
||||||
|
"timeout_request": 5, |
||||||
|
// Timeout on request/response content completion. Defaults to 300 seconds. |
||||||
|
"timeout_content": 300, |
||||||
|
// Maximum size of request stream buffer. Defaults to architecture maximum. |
||||||
|
// Reaching this limit will result in a message_size error code. |
||||||
|
// default: (std::numeric_limits<std::size_t>::max)() |
||||||
|
"max_request_streambuf_size": 18446744073709551615, |
||||||
|
// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation. |
||||||
|
// If empty, the address will be any address. |
||||||
|
"address": "", |
||||||
|
// Set to false to avoid binding the socket to an address that is already in use. Defaults to true. |
||||||
|
"reuse_address": true, |
||||||
|
// Make use of RFC 7413 or TCP Fast Open (TFO) |
||||||
|
"fast_open": false |
||||||
|
}, |
||||||
|
{ |
||||||
|
"https": { |
||||||
|
"port": 443, |
||||||
|
// If io_service is not set, number of threads that the server will use when start() is called. |
||||||
|
// Defaults to 1 thread. |
||||||
|
"thread_pool_size": 1, |
||||||
|
// Timeout on request completion. Defaults to 5 seconds. |
||||||
|
"timeout_request": 5, |
||||||
|
// Timeout on request/response content completion. Defaults to 300 seconds. |
||||||
|
"timeout_content": 300, |
||||||
|
// Maximum size of request stream buffer. Defaults to architecture maximum. |
||||||
|
// Reaching this limit will result in a message_size error code. |
||||||
|
// default: (std::numeric_limits<std::size_t>::max)() |
||||||
|
"max_request_streambuf_size": 18446744073709551615, |
||||||
|
// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation. |
||||||
|
// If empty, the address will be any address. |
||||||
|
"address": "", |
||||||
|
// Set to false to avoid binding the socket to an address that is already in use. Defaults to true. |
||||||
|
"reuse_address": true, |
||||||
|
// Make use of RFC 7413 or TCP Fast Open (TFO) |
||||||
|
"fast_open": false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
``` |
||||||
|
### Example configuration |
||||||
|
A very simple https configuration (recommended): |
||||||
|
``` javascript |
||||||
|
{ |
||||||
|
"https": { |
||||||
|
"cert": "/etc/ssl/cert.crt", |
||||||
|
"key": "/etc/ssl/key.pem" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
``` |
||||||
|
A very simple http configuration (default): |
||||||
|
``` javascript |
||||||
|
{} |
||||||
|
``` |
||||||
|
|
||||||
|
# Troubleshooting |
||||||
|
|
||||||
|
## Errors |
||||||
|
### Client errors |
||||||
|
```yaml |
||||||
|
;; The server will return this error message if the server is configured with |
||||||
|
;; https and the client attempts to access it over http. |
||||||
|
100: Your server is configured to use https, but a request on http was recived. |
||||||
|
|
||||||
|
;; The server will return this error message if the body of your request is |
||||||
|
;; invalid json. Consider using a validator to find your mistake. |
||||||
|
101: Your request was not a valid JSON document. |
||||||
|
|
||||||
|
;; The server will return this error message if the root of your JSON request |
||||||
|
;; is not {}. |
||||||
|
102: Your document was not a valid JSON object. |
||||||
|
``` |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
#pragma once |
||||||
|
#include <experimental/filesystem> |
||||||
|
#include <json.hpp> |
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem; |
||||||
|
|
||||||
|
class config : public json { |
||||||
|
static fs::path &get_config_path(); |
||||||
|
void create_config_directory(); |
||||||
|
void create_config_file(const fs::path &config_file_path); |
||||||
|
void load_config_file(const fs::path &config_file_path); |
||||||
|
|
||||||
|
public: |
||||||
|
config(); |
||||||
|
}; |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
#pragma once |
||||||
|
#include <web_server.hpp> |
||||||
|
|
||||||
|
const auto header_application_data = |
||||||
|
std::make_pair("Content-Type", "application/json"); |
||||||
|
const auto header_access_control = |
||||||
|
std::make_pair("Access-Control-Allow-Origin", "*"); |
||||||
|
|
||||||
|
class response { |
||||||
|
public: |
||||||
|
static void https_required(std::shared_ptr<HttpServer::Response> response); |
||||||
|
static void https_required(std::shared_ptr<HttpsServer::Response> response); |
||||||
|
static void bad_json(std::shared_ptr<HttpServer::Response> response); |
||||||
|
static void bad_json(std::shared_ptr<HttpsServer::Response> response); |
||||||
|
static void bad_object(std::shared_ptr<HttpServer::Response> response); |
||||||
|
static void bad_object(std::shared_ptr<HttpsServer::Response> response); |
||||||
|
}; |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
#pragma once |
||||||
|
#include <nlohmann/json.hpp> |
||||||
|
|
||||||
|
using nlohmann::json; |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
#pragma once |
||||||
|
#include <json.hpp> |
||||||
|
#include <web_server.hpp> |
||||||
|
|
||||||
|
class convert { |
||||||
|
public: |
||||||
|
static void from_json(const json &, HttpServer::Config &); |
||||||
|
static void from_json(const json &, HttpsServer::Config &); |
||||||
|
}; |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
#pragma once |
||||||
|
#include <json.hpp> |
||||||
|
#include <web_server.hpp> |
||||||
|
|
||||||
|
class Application { |
||||||
|
HttpServer http_server; |
||||||
|
|
||||||
|
static void web_server_started(std::size_t port); |
||||||
|
static void secure_web_server_started(std::size_t port); |
||||||
|
Application() = delete; |
||||||
|
Application(const json &cfg); |
||||||
|
|
||||||
|
public: |
||||||
|
std::shared_ptr<HttpsServer> https_server = nullptr; |
||||||
|
|
||||||
|
static Application &get(const json &config); |
||||||
|
int run(); |
||||||
|
}; |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <server_http.hpp> |
||||||
|
#include <server_https.hpp> |
||||||
|
|
||||||
|
using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>; |
||||||
|
using Status = SimpleWeb::StatusCode; |
||||||
|
using HttpsServer = SimpleWeb::Server<SimpleWeb::HTTPS>; |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
file(GLOB source_files "*.cpp") |
||||||
|
|
||||||
|
add_executable(store ${source_files}) |
||||||
|
target_link_libraries(store |
||||||
|
PRIVATE |
||||||
|
nlohmann_json::nlohmann_json |
||||||
|
PUBLIC |
||||||
|
stdc++fs |
||||||
|
simple-web-server) |
||||||
|
target_include_directories(store PRIVATE "${CMAKE_SOURCE_DIR}/lib/json/include") |
||||||
|
target_include_directories(store PRIVATE "${CMAKE_SOURCE_DIR}/include") |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
#include <config.hpp> |
||||||
|
#include <fstream> |
||||||
|
#include <iostream> |
||||||
|
#include <unistd.h> |
||||||
|
|
||||||
|
void config::create_config_directory() { |
||||||
|
std::error_code ec; |
||||||
|
fs::create_directories(get_config_path(), ec); |
||||||
|
if (ec) { |
||||||
|
std::cerr << "Unable to create configuration directory:\n" << ec.message(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fs::path &config::get_config_path() { |
||||||
|
static fs::path p(""); |
||||||
|
if (p.native().length() != 0) { |
||||||
|
return p; |
||||||
|
} |
||||||
|
const int uid = getuid(); |
||||||
|
if (uid == 0) { |
||||||
|
p = fs::path("/") / "etc" / "store"; |
||||||
|
} else if (const auto ptr = std::getenv("HOME")) { |
||||||
|
p = fs::path(ptr) / ".config" / "store"; |
||||||
|
} |
||||||
|
return p; |
||||||
|
} |
||||||
|
|
||||||
|
void config::create_config_file(const fs::path &config_file_path) { |
||||||
|
try { |
||||||
|
std::ofstream f(config_file_path); |
||||||
|
f << json::object(); |
||||||
|
} catch (const std::exception &e) { |
||||||
|
std::cerr << "Could not create config file:\n" << e.what() << std::endl; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void config::load_config_file(const fs::path &config_file_path) { |
||||||
|
std::ifstream i(config_file_path.c_str()); |
||||||
|
i >> *this; |
||||||
|
} |
||||||
|
|
||||||
|
config::config() : json(json::object()) { |
||||||
|
if (!fs::exists(get_config_path())) { |
||||||
|
create_config_directory(); |
||||||
|
} |
||||||
|
const auto config_file_path = get_config_path() / "config.json"; |
||||||
|
if (!fs::exists(config_file_path)) { |
||||||
|
create_config_file(config_file_path); |
||||||
|
} else { |
||||||
|
load_config_file(config_file_path); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
#include <http.hpp> |
||||||
|
#include <json.hpp> |
||||||
|
|
||||||
|
const auto https_required_msg = json::object({ |
||||||
|
{"message", "100: Your server is configured to use https, but a " |
||||||
|
"request on http was received."}, |
||||||
|
}); |
||||||
|
|
||||||
|
const auto bad_json_msg = json::object({ |
||||||
|
{"message", "101: Your request was not a valid JSON document."}, |
||||||
|
}); |
||||||
|
|
||||||
|
const auto bad_object_msg = json::object({ |
||||||
|
{"message", "102: Your request was not a valid store."}, |
||||||
|
}); |
||||||
|
|
||||||
|
void response::https_required(std::shared_ptr<HttpServer::Response> response) { |
||||||
|
return response->write(Status::client_error_bad_request, |
||||||
|
https_required_msg.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
} |
||||||
|
|
||||||
|
void response::https_required(std::shared_ptr<HttpsServer::Response> response) { |
||||||
|
return response->write(Status::client_error_bad_request, |
||||||
|
https_required_msg.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
} |
||||||
|
|
||||||
|
void response::bad_json(std::shared_ptr<HttpServer::Response> response) { |
||||||
|
return response->write(Status::client_error_bad_request, bad_json_msg.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
} |
||||||
|
|
||||||
|
void response::bad_json(std::shared_ptr<HttpsServer::Response> response) { |
||||||
|
return response->write(Status::client_error_bad_request, bad_json_msg.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void response::bad_object(std::shared_ptr<HttpServer::Response> response) { |
||||||
|
return response->write(Status::client_error_bad_request, |
||||||
|
bad_object_msg.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
} |
||||||
|
|
||||||
|
void response::bad_object(std::shared_ptr<HttpsServer::Response> response) { |
||||||
|
return response->write(Status::client_error_bad_request, |
||||||
|
bad_object_msg.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
#include <json_converters.hpp> |
||||||
|
|
||||||
|
void convert::from_json(const json &json, HttpServer::Config &config) { |
||||||
|
config.port = json.value("port", config.port); |
||||||
|
config.timeout_request = |
||||||
|
json.value("timeout_request", config.timeout_request); |
||||||
|
config.timeout_content = |
||||||
|
json.value("timeout_content", config.timeout_content); |
||||||
|
config.max_request_streambuf_size = json.value( |
||||||
|
"max_request_streambuf_size", config.max_request_streambuf_size); |
||||||
|
config.address = json.value("address", config.address); |
||||||
|
config.reuse_address = json.value("reuse_address", config.reuse_address); |
||||||
|
config.fast_open = json.value("fast_open", config.fast_open); |
||||||
|
}; |
||||||
|
|
||||||
|
void convert::from_json(const json &json, HttpsServer::Config &config) { |
||||||
|
config.port = json.value("port", config.port); |
||||||
|
config.timeout_request = |
||||||
|
json.value("timeout_request", config.timeout_request); |
||||||
|
config.timeout_content = |
||||||
|
json.value("timeout_content", config.timeout_content); |
||||||
|
config.max_request_streambuf_size = json.value( |
||||||
|
"max_request_streambuf_size", config.max_request_streambuf_size); |
||||||
|
config.address = json.value("address", config.address); |
||||||
|
config.reuse_address = json.value("reuse_address", config.reuse_address); |
||||||
|
config.fast_open = json.value("fast_open", config.fast_open); |
||||||
|
}; |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
#include <config.hpp> |
||||||
|
#include <store.hpp> |
||||||
|
|
||||||
|
int main() { |
||||||
|
config config; |
||||||
|
return Application::get(config).run(); |
||||||
|
} |
||||||
@ -0,0 +1,102 @@ |
|||||||
|
#include <http.hpp> |
||||||
|
#include <json_converters.hpp> |
||||||
|
#include <store.hpp> |
||||||
|
|
||||||
|
void Application::web_server_started(std::size_t port) { |
||||||
|
std::cout << "Web server started and listening on " << port << ".\n"; |
||||||
|
}; |
||||||
|
|
||||||
|
void Application::secure_web_server_started(std::size_t port) { |
||||||
|
std::cout << "Secure web server started and listening on " << port << ".\n"; |
||||||
|
}; |
||||||
|
|
||||||
|
Application::Application(const nlohmann::json &cfg) { |
||||||
|
convert::from_json(cfg.value("http", json::object()), http_server.config); |
||||||
|
if (cfg.contains("https")) { |
||||||
|
const auto https = cfg["https"]; |
||||||
|
if (https.contains("cert") && https.contains("key")) { |
||||||
|
https_server = std::make_shared<HttpsServer>(https["cert"], https["key"]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (https_server) { |
||||||
|
convert::from_json(cfg.value("https", json::object()), |
||||||
|
https_server->config); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int Application::run() { |
||||||
|
auto store = json::object(); |
||||||
|
std::vector<std::thread> servers; |
||||||
|
|
||||||
|
if (https_server) { |
||||||
|
https_server->default_resource["GET"] = |
||||||
|
[&](std::shared_ptr<HttpsServer::Response> response, |
||||||
|
std::shared_ptr<HttpsServer::Request>) { |
||||||
|
response->write(Status::success_ok, store.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
}; |
||||||
|
|
||||||
|
https_server->default_resource["POST"] = |
||||||
|
[&](std::shared_ptr<HttpsServer::Response> response, |
||||||
|
std::shared_ptr<HttpsServer::Request> request) { |
||||||
|
json data; |
||||||
|
try { |
||||||
|
request->content >> data; |
||||||
|
} catch (...) { |
||||||
|
return response::bad_json(response); |
||||||
|
} |
||||||
|
if (data.is_object()) { |
||||||
|
store.update(data); |
||||||
|
return response->write(Status::success_no_content); |
||||||
|
} |
||||||
|
return response->write(Status::client_error_bad_request); |
||||||
|
}; |
||||||
|
|
||||||
|
servers.emplace_back( |
||||||
|
[&]() { https_server->start(secure_web_server_started); }); |
||||||
|
} |
||||||
|
|
||||||
|
http_server.default_resource["GET"] = |
||||||
|
[&](std::shared_ptr<HttpServer::Response> response, ...) { |
||||||
|
if (https_server) { |
||||||
|
return response::https_required(response); |
||||||
|
} |
||||||
|
response->write(Status::success_ok, store.dump(), |
||||||
|
{header_access_control, header_application_data}); |
||||||
|
}; |
||||||
|
|
||||||
|
http_server.default_resource["POST"] = |
||||||
|
[&](std::shared_ptr<HttpServer::Response> response, |
||||||
|
std::shared_ptr<HttpServer::Request> request) { |
||||||
|
if (https_server) { |
||||||
|
return response::https_required(response); |
||||||
|
} |
||||||
|
|
||||||
|
json data; |
||||||
|
try { |
||||||
|
request->content >> data; |
||||||
|
} catch (...) { |
||||||
|
return response::bad_json(response); |
||||||
|
} |
||||||
|
if (data.is_object()) { |
||||||
|
store.update(data); |
||||||
|
return response->write(Status::success_no_content); |
||||||
|
} |
||||||
|
return response->write(Status::client_error_bad_request); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
servers.emplace_back([&]() { http_server.start(web_server_started); }); |
||||||
|
|
||||||
|
for (auto &server : servers) { |
||||||
|
server.join(); |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
Application &Application::get(const json &config) { |
||||||
|
static Application app(config); |
||||||
|
return app; |
||||||
|
} |
||||||
Loading…
Reference in new issue