Line data Source code
1 : #ifndef THINGER_HTTP_APPLICATION_HPP
2 : #define THINGER_HTTP_APPLICATION_HPP
3 :
4 : #include "virtual_host.hpp"
5 : #include "error_format.hpp"
6 : #include "trusted_proxies.hpp"
7 : #include "openapi.hpp"
8 : #include "body_decoders.hpp"
9 : #include "response_sink.hpp"
10 : #include "response_observer.hpp"
11 : #include "request_context.hpp"
12 : #include "../../util/types.hpp"
13 : #include <boost/system/error_code.hpp>
14 : #include <chrono>
15 : #include <cstdint>
16 : #include <functional>
17 : #include <map>
18 : #include <memory>
19 : #include <optional>
20 : #include <string>
21 : #include <string_view>
22 : #include <vector>
23 :
24 : namespace thinger::http {
25 :
26 : class request;
27 : class response;
28 :
29 : // Options for requests dispatched in memory (see http_application::dispatch)
30 : struct dispatch_options {
31 : // Address of the peer, reported by request::get_peer_ip() and, unless it is a trusted
32 : // proxy (see set_trusted_proxies), by request::get_request_ip(). Empty by default:
33 : // never assume a trusted address such as 127.0.0.1 for internal calls. The scheme
34 : // reported by request::get_request_scheme() is the one of the request (https if its URL
35 : // is https://, see http_request::set_ssl()), unless this address is a trusted proxy.
36 : std::string remote_ip;
37 :
38 : // Maximum time to wait for the response, handler execution included; 504 Gateway
39 : // Timeout if exceeded. The scope of the request is then cancelled with
40 : // cancel_reason::timeout: its handler goes on, and sees it in request::cancelled(), unless
41 : // its route declared the timeout (see route::cancel_on), which interrupts it. A caller
42 : // cancelling the dispatch cancels the scope with cancel_reason::client_gone instead
43 1350 : std::chrono::milliseconds timeout{30000};
44 :
45 : // Initial values of the request context (see request::context()), seen by the
46 : // middlewares and the handler, e.g. a trace id; they may replace them. Empty by default:
47 : // nothing is carried over from the request being served by the caller.
48 : request_context context;
49 : };
50 :
51 : // Cross-origin requests (see http_application::enable_cors). The defaults are those of cors()
52 : // in Express: any origin, without credentials, reflecting the headers a preflight asks for.
53 : struct cors_options {
54 : // Origins allowed. {"*"} alone (the default) is any origin: every response says
55 : // "Access-Control-Allow-Origin: *", also to a request without Origin. Otherwise, the exact
56 : // origins allowed (ASCII case-insensitive; empty for none, "null" for that origin): the one
57 : // of a request listed is reflected as it came, and every response says "Vary: Origin",
58 : // allowed or not. "*" next to other origins is invalid.
59 : std::vector<std::string> origins{"*"};
60 :
61 : // Whether an origin is allowed, instead of the list, which is ignored then: reflected as
62 : // above if it returns true. Called once for each request with an Origin (repeated ones come
63 : // joined by ", "), on the thread of the request and before its middlewares, so it must not
64 : // block. If it throws, the request is answered 500, with no origin allowed.
65 : std::function<bool(std::string_view origin, const http_request& request)> origin_filter{};
66 :
67 : // Access-Control-Allow-Methods of a preflight
68 : std::vector<std::string> methods{"GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"};
69 :
70 : // Access-Control-Allow-Headers of a preflight: without a value (the default), what its
71 : // Access-Control-Request-Headers asks for, with "Vary: Access-Control-Request-Headers";
72 : // with a value, that list, whatever it asks for (not sent if empty). Being optional, it is
73 : // set as std::vector<std::string>{"Content-Type", "Authorization"}.
74 : std::optional<std::vector<std::string>> allowed_headers{};
75 :
76 : // Access-Control-Expose-Headers, on the responses to an origin allowed (not sent if empty)
77 : std::vector<std::string> exposed_headers{};
78 :
79 : // Access-Control-Max-Age of a preflight: not sent without a value (the default), and sent
80 : // when 0. Not negative.
81 : std::optional<std::chrono::seconds> max_age{};
82 :
83 : // Send "Access-Control-Allow-Credentials: true" to an origin allowed (cookies or
84 : // Authorization in a browser). Invalid with any origin ("*"), which browsers reject: use a
85 : // list or a filter.
86 : bool credentials = false;
87 :
88 : // Let a preflight go on as any request (route matching, middlewares and the OPTIONS route of
89 : // the application, or 404/405), with the preflight headers already on its response. By
90 : // default the application answers it with 204, before route matching and the middlewares.
91 : bool preflight_continue = false;
92 : };
93 :
94 : namespace detail {
95 : // What http_application and response share of CORS (cors.cpp): the options once checked and
96 : // computed (null, and logged, if invalid); what they mean for a request, which runs the
97 : // filter of the options and throws what it throws; and its headers, added to a message about
98 : // to be sent, unless the message has them
99 : struct cors_policy;
100 : struct cors_decision {
101 : std::string origin; // granted: "*", as it came, or empty for none
102 : bool preflight = false; // its response says what a preflight is told
103 : bool answer_preflight = false; // the application answers it, not its routes
104 : std::string requested_headers; // Access-Control-Request-Headers of a preflight, to reflect
105 : };
106 : std::shared_ptr<const cors_policy> make_cors_policy(cors_options options);
107 : cors_decision decide_cors(const cors_policy& policy, const http_request& request);
108 : void apply_cors(const cors_policy& policy, const cors_decision& decision, http_response& message);
109 : }
110 :
111 : // Middlewares run after route matching (request::get_matched_route() is set, or null
112 : // if no route matched) and before the request body is read, so they cannot see it.
113 : // They share the response object with the route handler.
114 :
115 : // Asynchronous middleware: co_return true to continue the chain, or respond through
116 : // the response and co_return false to stop it (the route handler is not called).
117 : using async_middleware_function = std::function<thinger::awaitable<bool>(request&, response&)>;
118 :
119 : // Synchronous middleware: call next() before returning to continue the chain, or
120 : // respond through the response to stop it. next() must not be called asynchronously.
121 : using middleware_function = sync_callback<void(request&, response&, std::function<void()>)>;
122 :
123 : // The same, as a coroutine (as an async Express middleware): call next() before it
124 : // completes to continue the chain
125 : using coroutine_middleware_function = std::function<thinger::awaitable<void>(request&, response&, std::function<void()>)>;
126 :
127 : // What a server serves: the virtual hosts and their routes, the middlewares, and how
128 : // requests are processed (error format, trusted proxies, limits). The application is the
129 : // default virtual host: the routes registered on it (see virtual_host for the registration
130 : // methods) answer every host without a virtual host of its own. A server (see
131 : // server_base) is an application served on listeners; an application can also be
132 : // used on its own, to dispatch requests in memory, without a connection.
133 : class http_application : public virtual_host {
134 : public:
135 : http_application();
136 :
137 : // Ends the requests dispatched in memory that are still in flight (see dispatch()), without
138 : // waiting for their handlers. It may be called from any thread, or from one of those handlers
139 : ~http_application();
140 :
141 : // Virtual hosts: the routes registered on the application itself answer any host,
142 : // unless the Host header (case-insensitive, port ignored) matches a host registered
143 : // here: exact names first, then patterns in registration order. Register before serving
144 : // (listen()): hosts are matched without locks.
145 : // name: exact ("api.example.com"), "*" (this default host), or a pattern where "*" is
146 : // one label and ":name" a label available as a request parameter (see virtual_host)
147 : virtual_host& host(const std::string& name);
148 :
149 : // Host matched by a regular expression on the whole host name (case-insensitive);
150 : // its capture groups are available through request::get_host_matches()
151 : virtual_host& host_regex(const std::string& pattern);
152 :
153 : // OpenAPI document generated from the routes of the default host (configure title,
154 : // hooks... here). For another host: openapi_generator(app.host(name).router()).
155 : openapi_generator& openapi();
156 :
157 : // Serve the OpenAPI document as JSON at `path` (not served unless called)
158 : route& serve_openapi(const std::string& path = "/openapi.json");
159 :
160 : // Dispatch a request in memory, without a connection, through the same steps as a
161 : // network request: route matching, middlewares, body reading and validation, handler,
162 : // and the same 404/405 handling. Useful for tests, internal calls and gateways.
163 : // The body is taken from the request content (set_content); chunked requests are not
164 : // supported. WebSocket, SSE and connection takeover answer 501.
165 : //
166 : // A dispatch shares only the serving state with the application, never the application
167 : // itself, so destroying it (from any thread, or from a handler) ends the dispatches in
168 : // flight without waiting for them. One without a complete response ends at once, and this
169 : // throws boost::system::system_error(operation_aborted); a complete one is delivered. One
170 : // not started yet (this awaitable, awaited later) runs no middleware or handler. A handler
171 : // running is interrupted as at the shutdown of a server: its next cancellable await ends
172 : // with operation_aborted, whatever its route declared and even if its scope was cancelled
173 : // already (request::cancelled() keeps the first reason, cancel_reason::shutdown if none).
174 : // Its current step, and the awaits it isolated from cancellation, end on their own, and what
175 : // it sends afterwards is dropped. What it captured itself is the caller's to keep alive.
176 : //
177 : // With a response hook (see on_response()), the dispatch ends once the hook has run, so what
178 : // the hook captures can be destroyed as soon as this returns. The wait is limited by what is
179 : // left of dispatch_options::timeout: a handler still running then, even one that answered
180 : // already, gets its response returned without waiting more, and the hook runs when the
181 : // handler ends, as on a connection: what the hook captures must live as long as the handler.
182 : // Neither a caller cancelling the dispatch, nor destroying the application, waits for the
183 : // hook. Without a hook nothing is waited for.
184 : awaitable<std::shared_ptr<http_response>> dispatch(std::shared_ptr<http_request> request,
185 : dispatch_options options = {});
186 :
187 : // Same, calling `callback` once with the response; runs on `executor`. With no error to
188 : // report, a dispatch ended by destroying the application gets 503 Service Unavailable
189 : void dispatch(const boost::asio::any_io_executor& executor,
190 : std::shared_ptr<http_request> request,
191 : sync_callback<void(std::shared_ptr<http_response>)> callback,
192 : dispatch_options options = {});
193 :
194 : // Handle a request received by a server, answering through `sink`
195 : awaitable<void> handle(std::shared_ptr<request> req, std::shared_ptr<response_sink> sink);
196 :
197 : // Middleware (register before serving, executed in registration order)
198 : void use(async_middleware_function middleware);
199 : void use(middleware_function middleware);
200 : void use(coroutine_middleware_function middleware);
201 :
202 : // Basic Auth helpers
203 : using auth_verify_function = std::function<bool(const std::string& username, const std::string& password)>;
204 :
205 : // Add basic auth for a specific path prefix
206 : void set_basic_auth(const std::string& path_prefix,
207 : const std::string& realm,
208 : auth_verify_function verify);
209 :
210 : // Add basic auth with simple username/password
211 : void set_basic_auth(const std::string& path_prefix,
212 : const std::string& realm,
213 : const std::string& username,
214 : const std::string& password);
215 :
216 : // Add basic auth with multiple users
217 : void set_basic_auth(const std::string& path_prefix,
218 : const std::string& realm,
219 : const std::map<std::string, std::string>& users);
220 :
221 : // Serve cross-origin requests from browsers (see cors_options; off by default). Its headers
222 : // go on every response of the application, whoever answers it (the handler, a middleware,
223 : // or the server itself: 400, 404, 405, 413, 415, 500, a route timeout...), on a connection or
224 : // dispatched in memory; a header set by the handler or a middleware stays instead. It is not
225 : // access control: a request from an origin not allowed still runs, only without the headers
226 : // a browser needs to read its response. A preflight (OPTIONS with a non-empty
227 : // Access-Control-Request-Method) is answered 204 by the application, without running the
228 : // middlewares, unless cors_options::preflight_continue. Returns `invalid_argument` for
229 : // options that cannot be applied, the reason in the log: nothing changes then. Set it before
230 : // serving (listen()): it is read without locks.
231 : boost::system::error_code enable_cors(cors_options options = {});
232 :
233 : // Back to no CORS headers, and OPTIONS routed as any other method
234 : void disable_cors();
235 :
236 : // Maximum request body size (8 MB by default), for the routes that do not set their own
237 : // (route::max_body_size) and the requests matching no route: every read of a body is
238 : // bounded by it (413 if larger), and an unread body larger than this is not discarded
239 : // after the response: the connection is closed instead
240 : void set_max_body_size(std::uint64_t size);
241 : std::uint64_t get_max_body_size() const;
242 :
243 : // Decoders of request bodies by media type, for the routes taking the body decoded:
244 : // JSON, MessagePack, CBOR, UBJSON, urlencoded forms, text and binary are built in (see
245 : // body_decoder_registry); remove or add decoders here. Set before serving.
246 : body_decoder_registry& body_decoders();
247 :
248 : // Body types accepted by the routes taking the body decoded that do not set their own
249 : // (route::accepts): names of decoders or media types; {"json"} by default. Others are
250 : // answered with 415 Unsupported Media Type. Set before serving.
251 : void accept_body_types(std::vector<std::string> types);
252 : const std::vector<std::string>& get_body_types() const;
253 :
254 : // Limits of decoded bodies (nesting depth), for the routes that do not set their own
255 : // (route::decode_limits); set before serving
256 : void set_body_limits(body_limits limits);
257 : const body_limits& get_body_limits() const;
258 :
259 : // Decoders, accepted types and limits together, e.g. for the openapi_generator of a
260 : // virtual host: openapi_generator(app.host(name).router(), &app.get_body_settings())
261 : const body_settings& get_body_settings() const;
262 :
263 : // Format of the error responses: those sent with response::error() and the ones the
264 : // server generates (400 invalid body or schema validation, 404, 405, 413, 415, 500...).
265 : // By default the message is sent as text/plain, and errors with details (schema
266 : // validation) as JSON {"error": {"message": ..., "context": [...]}}. Set it before
267 : // serving (listen()): it is read without locks, and may be called concurrently from the
268 : // worker threads.
269 : void set_error_formatter(error_formatter formatter);
270 :
271 : // Proxies (IPs or CIDR ranges: "10.0.0.1", "10.0.0.0/8", "fd00::/8") whose forwarding
272 : // header gives the client address reported by request::get_request_ip(), and the scheme
273 : // reported by request::get_request_scheme() (the X-Forwarded-Proto value aligned with the
274 : // X-Forwarded-For entry of the client, or its last value; the proto of the Forwarded
275 : // element of the client). The headers are ignored for any other peer, so only list proxies
276 : // that overwrite or append to them. None by default; set before serving (listen()): they
277 : // are read without locks. Returns false if an entry is invalid (then no proxy is trusted).
278 : bool set_trusted_proxies(const std::vector<std::string>& proxies,
279 : forwarded_header header = forwarded_header::x_forwarded_for);
280 :
281 : // Called once per request, with its response (see response_info: status, bytes, duration,
282 : // and whether it completed, handed the connection over, or was aborted), once both the
283 : // response ended and the application is done with the request (its handler returned, or
284 : // its route timed out), e.g. for access logs, metrics or audit records. Every request the
285 : // application receives is reported, whoever answered it: the handler, a middleware, or the
286 : // server itself (400 invalid framing or body, 404, 405, 413, 415, 417, 500, a route
287 : // timeout...), on a connection or dispatched in memory. Not the requests a connection
288 : // cannot parse (400, 431), which never reach the application.
289 : //
290 : // Streamed responses (start_chunked(), start_stream()) are reported once their end is
291 : // written. Upgrades (WebSocket, SSE, take_over()) are reported once the response handing
292 : // the connection over is written, as taken_over: what happens on the connection afterwards
293 : // is not (see websocket_connection::on_close(), sse_connection::on_close()).
294 : //
295 : // A request dispatched in memory returns after the hook has run, unless the timeout of the
296 : // dispatch ends first, or the dispatch is cancelled (see dispatch()).
297 : //
298 : // It runs on the thread of the connection (for in-memory dispatch, the one ending the
299 : // request or its response), so it must not block: hand anything slow to another thread.
300 : // It may run synchronously within the call that ends the response, when the application
301 : // was already done (e.g. a late end_chunked() or abort_stream()), and, for a response that
302 : // never ended, from the destructor of what held it, as the connection being released (also
303 : // at the teardown of the io_context). The request is read-only there, and its connection
304 : // may be gone (its peer address is kept). What it throws is logged. Without a hook nothing
305 : // is allocated or measured. Set it before serving (listen()): it is read without locks.
306 : void on_response(response_hook hook);
307 :
308 : protected:
309 : // What serves the requests of a connection: it keeps what they read (the hosts and their
310 : // routes, the middlewares and the settings) alive, not the application, so the requests
311 : // still running when a server is destroyed find it
312 : std::function<awaitable<void>(std::shared_ptr<request>)> connection_handler() const;
313 :
314 : private:
315 : class state;
316 : std::shared_ptr<state> state_;
317 : };
318 :
319 : } // namespace thinger::http
320 :
321 : #endif // THINGER_HTTP_APPLICATION_HPP
|