11#include <promeki/config.h>
12#if PROMEKI_ENABLE_HTTP
33PROMEKI_NAMESPACE_BEGIN
82class HttpServer :
public ObjectBase {
83 PROMEKI_OBJECT(HttpServer, ObjectBase)
92 explicit HttpServer(ObjectBase *parent =
nullptr);
95 ~HttpServer()
override;
110 Error listen(
const SocketAddress &address,
int backlog = 50);
115 Error listen(uint16_t port,
int backlog = 50);
121 bool isListening()
const;
124 SocketAddress serverAddress()
const;
131 HttpRouter &router() {
return _router; }
134 const HttpRouter &router()
const {
return _router; }
137 void route(
const String &pattern,
const HttpMethod &method, HttpHandlerFunc handler);
140 void route(
const String &pattern,
const HttpMethod &method, HttpHandler::Ptr handler);
143 void any(
const String &pattern, HttpHandlerFunc handler);
146 void use(HttpMiddleware middleware);
165 using WebSocketHandler = Function<void(WebSocket *socket,
const HttpRequest &request)>;
185 void routeWebSocket(
const String &pattern, WebSocketHandler handler);
213 template <CompiledString N>
214 void exposeDatabase(
const String &mountPath, VariantDatabase<N> &db,
bool readOnly =
false);
232 template <
typename T>
void exposeLookup(
const String &mountPath, T &target);
239 void setIdleTimeoutMs(
unsigned int ms) { _idleTimeoutMs = ms; }
242 void setMaxBodyBytes(int64_t bytes) { _maxBodyBytes = bytes; }
251 int connectionCount()
const;
270 void setSslContext(SslContext ctx) { _sslContext = std::move(ctx); }
273 SslContext sslContext()
const {
return _sslContext; }
280 static bool hasTlsSupport() {
return SslContext::hasTlsSupport(); }
283 PROMEKI_SIGNAL(requestReceived, HttpRequest);
286 PROMEKI_SIGNAL(responseSent, HttpRequest, HttpResponse);
289 PROMEKI_SIGNAL(errorOccurred, Error);
292 void onNewConnection();
293 void dispatchRequest(HttpRequest &request, HttpResponse &response);
294 void reapClosedConnection(HttpConnection *conn);
296 EventLoop *_loop =
nullptr;
297 UniquePtr<TcpServer> _tcpServer;
298 int _acceptHandle = -1;
300 HttpConnection::List _connections;
302 unsigned int _idleTimeoutMs = HttpConnection::DefaultIdleTimeoutMs;
303 int64_t _maxBodyBytes = HttpConnection::DefaultMaxBodyBytes;
304 SslContext _sslContext;
309 static JsonObject specToJson(
const VariantSpec &spec);
310 static String lookupPathToKey(
const String &slashPath);
317template <CompiledString N>
318void HttpServer::exposeDatabase(
const String &mountPath, VariantDatabase<N> &db,
bool readOnly) {
319 using DB = VariantDatabase<N>;
320 using ID =
typename DB::ID;
323 route(mountPath, HttpMethod::Get, [&db](
const HttpRequest &, HttpResponse &res) { res.setJson(db.toJson()); });
328 const String schemaPath = mountPath +
"/_schema";
329 route(schemaPath, HttpMethod::Get, [](
const HttpRequest &, HttpResponse &res) {
331 const auto specs = DB::registeredSpecs();
332 for (
auto it = specs.cbegin(); it != specs.cend(); ++it) {
333 const String name = ID::fromId(it->first).name();
334 out.set(name, HttpServer::specToJson(it->second));
340 const String keyPath = mountPath +
"/{key}";
341 route(keyPath, HttpMethod::Get, [&db](
const HttpRequest &req, HttpResponse &res) {
342 const String name = req.pathParam(
"key");
343 ID
id = ID::find(name);
344 if (!
id.isValid() || !db.contains(
id)) {
345 res = HttpResponse::notFound(String(
"Unknown key: ") + name);
349 out.setFromVariant(name, db.get(
id));
350 const VariantSpec *sp = DB::spec(
id);
352 out.set(
"_spec", HttpServer::specToJson(*sp));
357 if (readOnly)
return;
360 route(keyPath, HttpMethod::Put, [&db](
const HttpRequest &req, HttpResponse &res) {
361 const String name = req.pathParam(
"key");
363 JsonObject body = req.bodyAsJson(&perr);
364 if (perr.isError() || !body.contains(
"value")) {
365 res = HttpResponse::badRequest(
"Body must be JSON object with a \"value\" field");
375 body.forEach([&](
const String &k,
const Variant &val) {
376 if (k ==
"value") captured = val;
378 Error serr = db.setFromJson(
id, captured);
379 if (serr.isError()) {
380 res = HttpResponse::badRequest(String(
"Validation failed for key '") + name +
381 "': " + serr.desc());
385 out.setFromVariant(name, db.get(
id));
390 route(keyPath, HttpMethod::Delete, [&db](
const HttpRequest &req, HttpResponse &res) {
391 const String name = req.pathParam(
"key");
392 ID
id = ID::find(name);
393 if (!
id.isValid() || !db.contains(
id)) {
394 res = HttpResponse::notFound(String(
"Unknown key: ") + name);
398 res = HttpResponse::noContent();
402template <
typename T>
void HttpServer::exposeLookup(
const String &mountPath, T &target) {
403 const String greedy = mountPath +
"/{path:*}";
404 route(greedy, HttpMethod::Get, [&target](
const HttpRequest &req, HttpResponse &res) {
405 const String key = HttpServer::lookupPathToKey(req.pathParam(
"path"));
407 res = HttpResponse::badRequest(
"Empty lookup key");
411 auto v = VariantLookup<T>::resolve(target, key, &err);
412 if (!v.hasValue() || err.isError()) {
413 res = HttpResponse::notFound(String(
"Unknown lookup: ") + key);
417 out.setFromVariant(
"value", *v);