Firmware  0.4.1
Loading...
Searching...
No Matches
endpoints.hpp
Go to the documentation of this file.
1// Copyright (C) 2025 Vincent Hamp
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
21
22#pragma once
23
24#include <esp_http_server.h>
25#include <cassert>
26#include <concepts>
27#include <functional>
28#include <map>
29#include <memory>
30#include <string>
31#include <vector>
32#include <ztl/fail.hpp>
33#include <ztl/type_traits.hpp>
34#include "log.h"
35#include "message.hpp"
36#include "request.hpp"
37#include "response.hpp"
38#include "utility.hpp"
39
40namespace intf::http {
41
43class Endpoints {
44 using key_type = httpd_uri_t;
45
47 using sync_mapped_type = std::vector<std::function<Response(Request const&)>>;
48
50 using async_mapped_type = std::vector<std::function<esp_err_t(Message&)>>;
51
52public:
54 template<typename T, typename F>
55 void subscribe(key_type const& key, std::shared_ptr<T> t, F&& f) {
56 assert(key.uri && strlen(key.uri) && key.uri[strlen(key.uri) - 1uz] == '/');
57 if constexpr (std::invocable<typename ztl::signature<F>::type,
58 T*,
59 Request const&>)
60 _sync_map[key].push_back([t, f](auto&&... args) {
61 return std::invoke(f, *t, std::forward<decltype(args)>(args)...);
62 });
63 else if constexpr (std::invocable<typename ztl::signature<F>::type,
64 T*,
65 Message&>)
66 _async_map[key].push_back([t, f](auto&&... args) {
67 return std::invoke(f, *t, std::forward<decltype(args)>(args)...);
68 });
69 else ztl::fail();
70 }
71
72protected:
74 Response syncResponse(httpd_req_t* req) {
75 static constexpr auto chunk_size{16384uz};
76
77 auto const key{req2key(req)};
78 auto const it{_sync_map.find(key)};
79 if (it == cend(_sync_map))
80 return std::unexpected<std::string>{"501 Not Implemented"};
81
82 // Request body can be red in chunks which avoids triggering the servers
83 // receive timeout
84 Request r{.uri = std::string(req->uri),
85 .body = std::string(req->content_len, '\0')};
86 int bytes_red{};
87 while (bytes_red < req->content_len) {
88 if (auto const tmp{
89 httpd_req_recv(req, data(r.body) + bytes_red, chunk_size)};
90 tmp > 0)
91 bytes_red += tmp;
92 else return std::unexpected<std::string>{"500 Internal Server Error"};
93 }
94
96 return it->second[0uz](r);
97 }
98
100 esp_err_t asyncResponse(httpd_req_t* req) {
101 auto const key{req2key(req)};
102 auto const it{_async_map.find(key)};
103 if (it == cend(_async_map)) return ESP_FAIL;
104 httpd_ws_frame_t frame{};
105 if (httpd_ws_recv_frame(req, &frame, 0uz)) return ESP_FAIL;
106
107 // WebSocket frame must be red in one go
108 Message msg{.sock_fd = httpd_req_to_sockfd(req),
109 .type = frame.type,
110 .payload = std::vector<uint8_t>(frame.len)};
111 if (frame.len) {
112 frame.payload = data(msg.payload);
113 if (httpd_ws_recv_frame(req, &frame, frame.len)) return ESP_FAIL;
114 }
115
116 return it->second[0uz](msg);
117 }
118
119private:
121 httpd_uri_t req2key(httpd_req_t* req) const {
122 return {.uri = req->uri,
123 .method = static_cast<httpd_method_t>(req->method)};
124 }
125
127 struct key_compare {
128 bool operator()(key_type const& lhs, key_type const& rhs) const {
129 // Different method
130 if (lhs.method != rhs.method) return lhs.method < rhs.method;
131 // Compare URI up to prefix length
132 else if (auto const lhs_prefix_len{strrchr(lhs.uri, '/') - lhs.uri + 1},
133 rhs_prefix_len{strrchr(rhs.uri, '/') - rhs.uri + 1};
134 lhs_prefix_len == rhs_prefix_len)
135 return strncmp(lhs.uri, rhs.uri, lhs_prefix_len) < 0;
136 // Compare URI as is
137 else return strcmp(lhs.uri, rhs.uri) < 0;
138 }
139 };
140
141 std::map<key_type, sync_mapped_type, key_compare> _sync_map;
142 std::map<key_type, async_mapped_type, key_compare> _async_map;
143};
144
145} // namespace intf::http
Definition endpoints.hpp:43
esp_err_t asyncResponse(httpd_req_t *req)
Definition endpoints.hpp:100
httpd_uri_t req2key(httpd_req_t *req) const
Definition endpoints.hpp:121
std::map< key_type, async_mapped_type, key_compare > _async_map
Definition endpoints.hpp:142
httpd_uri_t key_type
Definition endpoints.hpp:44
std::map< key_type, sync_mapped_type, key_compare > _sync_map
Definition endpoints.hpp:141
std::vector< std::function< Response(Request const &)> > sync_mapped_type
Mapped type for HTTP requests.
Definition endpoints.hpp:47
void subscribe(key_type const &key, std::shared_ptr< T > t, F &&f)
Definition endpoints.hpp:55
Response syncResponse(httpd_req_t *req)
Definition endpoints.hpp:74
std::vector< std::function< esp_err_t(Message &)> > async_mapped_type
Mapped type for WebSockets.
Definition endpoints.hpp:50
Log macros.
HTTP websocket message.
Definition config.hpp:442
std::expected< std::string, std::string > Response
Definition response.hpp:29
HTTP request.
HTTP response.
Definition endpoints.hpp:127
bool operator()(key_type const &lhs, key_type const &rhs) const
Definition endpoints.hpp:128
Definition message.hpp:29
Definition request.hpp:28