idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
server.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
21#include <idfxx/cpu>
22#include <idfxx/error>
23#include <idfxx/http/types>
24
25#include <chrono>
26#include <cstdint>
27#include <functional>
28#include <list>
29#include <optional>
30#include <span>
31#include <string>
32#include <string_view>
33#include <system_error>
34#include <utility>
35#include <vector>
36
37typedef void* httpd_handle_t;
38struct httpd_config;
39struct httpd_req;
40typedef struct httpd_req httpd_req_t;
41typedef int esp_err_t;
42
43namespace idfxx::http {
44
45class server;
46
58class request {
59public:
60 request(const request&) = delete;
61 request& operator=(const request&) = delete;
62 request(request&&) = delete;
64
65 // =========================================================================
66 // Request Properties
67 // =========================================================================
68
73 [[nodiscard]] enum method method() const;
74
79 [[nodiscard]] std::string_view uri() const;
80
85 [[nodiscard]] size_t content_length() const;
86
91 [[nodiscard]] int socket_fd() const;
92
93 // =========================================================================
94 // Headers
95 // =========================================================================
96
103 [[nodiscard]] std::optional<std::string> header(std::string_view field) const;
104
105 // =========================================================================
106 // Query String
107 // =========================================================================
108
113 [[nodiscard]] std::optional<std::string> query_string() const;
114
121 [[nodiscard]] std::optional<std::string> query_param(std::string_view key) const;
122
123 // =========================================================================
124 // Request Body
125 // =========================================================================
126
127#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
136 [[nodiscard]] size_t recv(std::span<uint8_t> buf) { return unwrap(try_recv(buf)); }
137
146 [[nodiscard]] size_t recv(std::span<char> buf) { return unwrap(try_recv(buf)); }
147
157 [[nodiscard]] std::string recv_body() { return unwrap(try_recv_body()); }
158#endif
159
166 [[nodiscard]] result<size_t> try_recv(std::span<uint8_t> buf);
167
174 [[nodiscard]] result<size_t> try_recv(std::span<char> buf);
175
184
185 // =========================================================================
186 // Response Setup
187 // =========================================================================
188
197 void set_status(std::string_view status);
198
207 void set_status(int code);
208
216 void set_content_type(std::string_view content_type);
217
227 void set_header(std::string_view field, std::string_view value);
228
229 // =========================================================================
230 // Response Sending
231 // =========================================================================
232
233#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
249 void send(std::string_view body) { unwrap(try_send(body)); }
250
258 void send(std::span<const uint8_t> body) { unwrap(try_send(body)); }
259
270 void send_chunk(std::string_view chunk) { unwrap(try_send_chunk(chunk)); }
271
281
290 void send_error(int code, std::string_view message = {}) { unwrap(try_send_error(code, message)); }
291#endif
292
299 [[nodiscard]] result<void> try_send(std::string_view body);
300
307 [[nodiscard]] result<void> try_send(std::span<const uint8_t> body);
308
319
328
336 [[nodiscard]] result<void> try_send_error(int code, std::string_view message = {});
337
338 // =========================================================================
339 // WebSocket
340 // =========================================================================
341
342#ifdef CONFIG_HTTPD_WS_SUPPORT
347 enum class ws_frame_type : int {
348 // clang-format off
349 continuation = 0x0,
350 text = 0x1,
351 binary = 0x2,
352 close = 0x8,
353 ping = 0x9,
354 pong = 0xA,
355 // clang-format on
356 };
357
362 struct ws_frame {
363 ws_frame_type type;
364 bool final = true;
365 std::span<const uint8_t> data;
366 };
367
368#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
379 [[nodiscard]] ws_frame ws_recv(std::span<uint8_t> buf) { return unwrap(try_ws_recv(buf)); }
380
388 void ws_send_text(std::string_view text) { unwrap(try_ws_send_text(text)); }
389
397 void ws_send_binary(std::span<const uint8_t> data) { unwrap(try_ws_send_binary(data)); }
398
406 void ws_send(const ws_frame& frame) { unwrap(try_ws_send(frame)); }
407#endif
408
417 [[nodiscard]] result<ws_frame> try_ws_recv(std::span<uint8_t> buf);
418
425 [[nodiscard]] result<void> try_ws_send_text(std::string_view text);
426
433 [[nodiscard]] result<void> try_ws_send_binary(std::span<const uint8_t> data);
434
442#endif // CONFIG_HTTPD_WS_SUPPORT
443
444 // =========================================================================
445 // Escape Hatch
446 // =========================================================================
447
453
454private:
455 friend class server;
456 explicit request(httpd_req_t* req)
457 : _req(req) {}
458
459 httpd_req_t* _req;
460 std::string _status_str;
461 std::string _content_type;
462 std::vector<std::pair<std::string, std::string>> _resp_headers;
463 mutable bool _query_fetched = false;
464 mutable std::optional<std::string> _query_cache;
465};
466
478class server {
479public:
481 using handler_type = std::move_only_function<result<void>(request&) const>;
482
486 enum class errc : esp_err_t {
487 // clang-format off
488 handlers_full = 0xb001,
489 handler_exists = 0xb002,
490 invalid_request = 0xb003,
491 result_truncated = 0xb004,
492 resp_header = 0xb005,
493 resp_send = 0xb006,
494 alloc_mem = 0xb007,
495 task = 0xb008,
496 // clang-format on
497 };
498
502 class error_category : public std::error_category {
503 public:
506
508 [[nodiscard]] std::string message(int ec) const override final;
509 };
510
519 // Task
520 task_priority priority = 5;
521 size_t stack_size = 4096;
522 std::optional<core_id> core_affinity = std::nullopt;
523
524 // Network
525 uint16_t server_port = 80;
526 uint16_t ctrl_port = 32768;
527 uint16_t backlog_conn = 5;
528
529 // Limits
530 uint16_t max_open_sockets = 7;
531 uint16_t max_uri_handlers = 8;
532 uint16_t max_resp_headers = 8;
533 size_t max_req_hdr_len = 0;
534 size_t max_uri_len = 0;
535
536 // Timeouts
537 std::chrono::seconds recv_wait_timeout{5};
538 std::chrono::seconds send_wait_timeout{5};
539
540 // LRU
541 bool lru_purge_enable = false;
542
543 // SO_LINGER
544 bool enable_so_linger = false;
545 std::chrono::seconds linger_timeout{0};
546
547 // Keep-alive
548 bool keep_alive_enable = false;
549 std::chrono::seconds keep_alive_idle{0};
550 std::chrono::seconds keep_alive_interval{0};
551 int keep_alive_count = 0;
552
553 // URI matching
554 bool wildcard_uri_match = false;
555
556 // Session callbacks
558 std::move_only_function<result<void>(int) const> on_session_open = {};
560 std::move_only_function<void(int) const> on_session_close = {};
561 };
562
563#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
571 [[nodiscard]] explicit server(config cfg);
572#endif
573
581
585 virtual ~server();
586
587 server(const server&) = delete;
588 server& operator=(const server&) = delete;
589
592
595
596 // =========================================================================
597 // Handler Registration
598 // =========================================================================
599
600#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
619 void on(enum method m, std::string uri, handler_type handler) {
620 unwrap(try_on(m, std::move(uri), std::move(handler)));
621 }
622
631 void on_any(std::string uri, handler_type handler) { unwrap(try_on_any(std::move(uri), std::move(handler))); }
632
641 void on_get(std::string uri, handler_type handler) { unwrap(try_on_get(std::move(uri), std::move(handler))); }
642
651 void on_post(std::string uri, handler_type handler) { unwrap(try_on_post(std::move(uri), std::move(handler))); }
652
661 void on_put(std::string uri, handler_type handler) { unwrap(try_on_put(std::move(uri), std::move(handler))); }
662
671 void on_patch(std::string uri, handler_type handler) { unwrap(try_on_patch(std::move(uri), std::move(handler))); }
672
681 void on_delete(std::string uri, handler_type handler) { unwrap(try_on_delete(std::move(uri), std::move(handler))); }
682
691 void on_head(std::string uri, handler_type handler) { unwrap(try_on_head(std::move(uri), std::move(handler))); }
692#endif
693
702 [[nodiscard]] result<void> try_on(enum method m, std::string uri, handler_type handler);
703
711 [[nodiscard]] result<void> try_on_any(std::string uri, handler_type handler);
712
720 [[nodiscard]] result<void> try_on_get(std::string uri, handler_type handler) {
721 return try_on(method::get, std::move(uri), std::move(handler));
722 }
723
731 [[nodiscard]] result<void> try_on_post(std::string uri, handler_type handler) {
732 return try_on(method::post, std::move(uri), std::move(handler));
733 }
734
742 [[nodiscard]] result<void> try_on_put(std::string uri, handler_type handler) {
743 return try_on(method::put, std::move(uri), std::move(handler));
744 }
745
753 [[nodiscard]] result<void> try_on_patch(std::string uri, handler_type handler) {
754 return try_on(method::patch, std::move(uri), std::move(handler));
755 }
756
764 [[nodiscard]] result<void> try_on_delete(std::string uri, handler_type handler) {
765 return try_on(method::delete_, std::move(uri), std::move(handler));
766 }
767
775 [[nodiscard]] result<void> try_on_head(std::string uri, handler_type handler) {
776 return try_on(method::head, std::move(uri), std::move(handler));
777 }
778
779 // =========================================================================
780 // Handler Unregistration
781 // =========================================================================
782
783#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
792 void unregister_handler(enum method m, std::string_view uri) { unwrap(try_unregister_handler(m, uri)); }
793#endif
794
802 [[nodiscard]] result<void> try_unregister_handler(enum method m, std::string_view uri);
803
804 // =========================================================================
805 // WebSocket
806 // =========================================================================
807
808#ifdef CONFIG_HTTPD_WS_SUPPORT
812 struct ws_config {
813 bool handle_control_frames = false;
814 std::string supported_subprotocol = {};
815 };
816
817#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
830 void on_ws(std::string uri, handler_type handler, ws_config ws_cfg = {}) {
831 unwrap(try_on_ws(std::move(uri), std::move(handler), std::move(ws_cfg)));
832 }
833#endif
834
846 [[nodiscard]] result<void> try_on_ws(std::string uri, handler_type handler, ws_config ws_cfg = {});
847#endif // CONFIG_HTTPD_WS_SUPPORT
848
849 // =========================================================================
850 // Session Control
851 // =========================================================================
852
853#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
862#endif
863
871
872 // =========================================================================
873 // Escape Hatch
874 // =========================================================================
875
881
882protected:
885
886 struct server_ctx {
887 server* self;
888 };
889
890 server(
891 httpd_handle_t handle,
892 std::unique_ptr<server_ctx> ctx,
893 std::move_only_function<result<void>(int) const> on_session_open,
894 std::move_only_function<void(int) const> on_session_close,
896 );
897
900
901 template<typename Config>
902 static void _populate_httpd_config(
903 struct httpd_config& httpd_cfg,
904 const Config& cfg,
906 bool has_session_open,
908 );
909
910private:
911 struct handler_record {
912 std::string uri;
913 unsigned httpd_method; // httpd_method_t value (from http_parser enum)
914 handler_type handler;
915#ifdef CONFIG_HTTPD_WS_SUPPORT
916 bool is_websocket = false;
917 bool handle_ws_control_frames = false;
918 std::string supported_subprotocol;
919#endif
920 };
921
923 [[nodiscard]] result<void> _try_register(unsigned httpd_method, std::string uri, handler_type handler);
926
928
929 httpd_handle_t _handle;
931 std::unique_ptr<server_ctx> _ctx;
932 std::list<handler_record> _handlers;
933 std::move_only_function<result<void>(int) const> _on_session_open;
934 std::move_only_function<void(int) const> _on_session_close;
936};
937
945
951 return {std::to_underlying(e), http_server_category()};
952}
953
965[[nodiscard]] std::unexpected<std::error_code> http_server_error(esp_err_t e);
966
967} // namespace idfxx::http
968
970namespace std {
971template<>
973} // namespace std
// end of idfxx_http_server
Non-owning view of an HTTP request.
Definition server.hpp:58
int socket_fd() const
Returns the socket file descriptor for this request's connection.
result< size_t > try_recv(std::span< char > buf)
Receives raw request body data into a character buffer.
enum method method() const
Returns the HTTP method of this request.
request & operator=(request &&)=delete
void set_content_type(std::string_view content_type)
Sets the Content-Type response header.
request(request &&)=delete
void set_status(int code)
Sets the HTTP response status code.
void send(std::string_view body)
Sends a complete response with string body.
Definition server.hpp:249
result< void > try_end_chunked()
Finalizes a chunked response.
void send_chunk(std::string_view chunk)
Sends a chunk of a chunked response.
Definition server.hpp:270
result< void > try_send_chunk(std::string_view chunk)
Sends a chunk of a chunked response.
void send(std::span< const uint8_t > body)
Sends a complete response with binary body.
Definition server.hpp:258
request & operator=(const request &)=delete
size_t content_length() const
Returns the content length of the request body.
size_t recv(std::span< char > buf)
Receives raw request body data into a character buffer.
Definition server.hpp:146
request(const request &)=delete
void set_header(std::string_view field, std::string_view value)
Adds a response header.
void end_chunked()
Finalizes a chunked response.
Definition server.hpp:280
result< std::string > try_recv_body()
Receives the entire request body as a string.
std::string recv_body()
Receives the entire request body as a string.
Definition server.hpp:157
result< void > try_send(std::span< const uint8_t > body)
Sends a complete response with binary body.
result< void > try_send_error(int code, std::string_view message={})
Sends an HTTP error response.
void set_status(std::string_view status)
Sets the HTTP response status line.
std::optional< std::string > header(std::string_view field) const
Retrieves a request header value by name.
void send_error(int code, std::string_view message={})
Sends an HTTP error response.
Definition server.hpp:290
size_t recv(std::span< uint8_t > buf)
Receives raw request body data into a buffer.
Definition server.hpp:136
httpd_req_t * idf_handle() const noexcept
Returns the underlying ESP-IDF request handle.
Definition server.hpp:452
std::string_view uri() const
Returns the URI of this request.
std::optional< std::string > query_param(std::string_view key) const
Retrieves a single query parameter value by key.
std::optional< std::string > query_string() const
Returns the full query string from the request URI.
result< size_t > try_recv(std::span< uint8_t > buf)
Receives raw request body data into a buffer.
result< void > try_send(std::string_view body)
Sends a complete response with string body.
Error category for HTTP server errors.
Definition server.hpp:502
const char * name() const noexcept override final
Returns the name of the error category.
std::string message(int ec) const override final
Returns a human-readable message for the given error code.
HTTP server with URI handler registration and RAII lifecycle.
Definition server.hpp:478
httpd_handle_t idf_handle() const noexcept
Returns the underlying ESP-IDF HTTP server handle.
Definition server.hpp:880
void on_delete(std::string uri, handler_type handler)
Registers a DELETE handler.
Definition server.hpp:681
void on_put(std::string uri, handler_type handler)
Registers a PUT handler.
Definition server.hpp:661
result< void > try_on_any(std::string uri, handler_type handler)
Registers a URI handler that matches any HTTP method.
static result< server > make(config cfg)
Creates and starts an HTTP server.
std::move_only_function< result< void >(request &) const > handler_type
Handler function type for URI handlers.
Definition server.hpp:481
result< void > try_close_session(int sockfd)
Closes a client session by socket file descriptor.
result< void > try_on_head(std::string uri, handler_type handler)
Registers a HEAD handler.
Definition server.hpp:775
result< void > try_on_get(std::string uri, handler_type handler)
Registers a GET handler.
Definition server.hpp:720
void on_head(std::string uri, handler_type handler)
Registers a HEAD handler.
Definition server.hpp:691
server(const server &)=delete
virtual ~server()
Stops the server and releases all resources.
server(server &&) noexcept
Move constructor.
void on_any(std::string uri, handler_type handler)
Registers a URI handler that matches any HTTP method.
Definition server.hpp:631
errc
Error codes for HTTP server operations.
Definition server.hpp:486
@ resp_send
Error sending response.
@ resp_header
Response header field too large.
@ handler_exists
URI handler already registered for this method and URI.
@ invalid_request
Invalid request pointer.
@ result_truncated
Result string was truncated.
@ handlers_full
All URI handler slots are full.
@ alloc_mem
Memory allocation failed.
result< void > try_on(enum method m, std::string uri, handler_type handler)
Registers a URI handler for a specific HTTP method.
void on_patch(std::string uri, handler_type handler)
Registers a PATCH handler.
Definition server.hpp:671
void on_get(std::string uri, handler_type handler)
Registers a GET handler.
Definition server.hpp:641
void unregister_handler(enum method m, std::string_view uri)
Unregisters a URI handler.
Definition server.hpp:792
result< void > try_on_delete(std::string uri, handler_type handler)
Registers a DELETE handler.
Definition server.hpp:764
result< void > try_on_put(std::string uri, handler_type handler)
Registers a PUT handler.
Definition server.hpp:742
void close_session(int sockfd)
Closes a client session by socket file descriptor.
Definition server.hpp:861
server & operator=(const server &)=delete
result< void > try_on_patch(std::string uri, handler_type handler)
Registers a PATCH handler.
Definition server.hpp:753
void on(enum method m, std::string uri, handler_type handler)
Registers a URI handler for a specific HTTP method.
Definition server.hpp:619
void on_post(std::string uri, handler_type handler)
Registers a POST handler.
Definition server.hpp:651
result< void > try_unregister_handler(enum method m, std::string_view uri)
Unregisters a URI handler.
result< void > try_on_post(std::string uri, handler_type handler)
Registers a POST handler.
Definition server.hpp:731
server(config cfg)
Creates and starts an HTTP server.
Type-safe wrapper for FreeRTOS task priority values.
Definition cpu.hpp:84
Task lifecycle management.
Definition task.hpp:47
int esp_err_t
Definition error.hpp:35
struct httpd_req httpd_req_t
Definition server.hpp:40
void * httpd_handle_t
Definition server.hpp:37
int esp_err_t
Definition server.hpp:41
method
HTTP request methods.
Definition types.hpp:29
@ delete_
DELETE (trailing underscore — C++ keyword)
const server::error_category & http_server_category() noexcept
Returns a reference to the HTTP server error category singleton.
std::error_code make_error_code(client::errc e) noexcept
Creates an error code from an idfxx::http::client::errc value.
Definition client.hpp:616
std::unexpected< std::error_code > http_server_error(esp_err_t e)
Creates an unexpected error from an ESP-IDF error code, mapping to HTTP server error codes where poss...
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:307
errc
IDFXX error codes.
Definition error.hpp:46
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:120
HTTP server configuration.
Definition server.hpp:518