Line data Source code
1 : #ifndef THINGER_HTTP_SERVER_RESPONSE_HPP
2 : #define THINGER_HTTP_SERVER_RESPONSE_HPP
3 :
4 : #include "../common/http_response.hpp"
5 : #include "../../util/logger.hpp"
6 : #include "upgrade.hpp"
7 : #include "error_format.hpp"
8 : #include "../../util/compression.hpp"
9 : #include <nlohmann/json.hpp>
10 : #include <memory>
11 : #include <functional>
12 : #include <filesystem>
13 : #include <chrono>
14 : #include <optional>
15 : #include <string>
16 : #include <vector>
17 :
18 : namespace thinger::http {
19 :
20 : // Forward declarations: the connection types are not brought in (include their headers, or
21 : // http_server.hpp, to use them)
22 : class http_request;
23 : class http_stream;
24 : class server_connection;
25 : class response_sink;
26 : enum class body_framing;
27 : class websocket_connection;
28 : class sse_connection;
29 : class http_application;
30 : class virtual_host;
31 : class response;
32 : struct file_info;
33 :
34 : namespace detail {
35 : struct cors_policy;
36 : struct cors_decision;
37 : }
38 :
39 : namespace file_server {
40 : struct representation;
41 : void send(response& res, const representation& file);
42 : }
43 :
44 : // Response to a request. It is a handle: copies share the response, so a handler may keep
45 : // a copy and answer later, from any thread. Only the first answer is sent: the others are
46 : // dropped, even when they run at the same time from several threads. Setting the status or
47 : // headers before answering (status(), header()) is not thread-safe: do it from the thread
48 : // running the handler, before any answer (a route timeout answers with the headers set so far).
49 : // The answer sent takes them, moved into its message: once it started, they are gone.
50 : // A response to a HEAD request is the one to GET without its body: every answer sends its
51 : // headers, Content-Length included (of the body uncompressed: HEAD is never compressed), and
52 : // drops the body; chunks and streamed data are dropped too, and SSE sends its headers alone.
53 : class response {
54 : public:
55 : response(const std::shared_ptr<server_connection>& connection,
56 : const std::shared_ptr<http_stream>& stream,
57 : const std::shared_ptr<http::http_request>& http_request);
58 :
59 : // Response written to `sink`, e.g. a memory_response for requests dispatched in memory
60 : response(std::shared_ptr<response_sink> sink,
61 : const std::shared_ptr<http::http_request>& http_request);
62 :
63 : // JSON response
64 : void json(const nlohmann::json& data, http::http_response::status status = http::http_response::status::ok);
65 :
66 : // The value as JSON, MessagePack, CBOR or UBJSON, as the Accept header of the request
67 : // prefers (by quality, then in that order); JSON if it accepts none of them or is missing.
68 : // The response has "Vary: Accept".
69 : void encode(const nlohmann::json& data, http::http_response::status status = http::http_response::status::ok);
70 :
71 : // Text response
72 : void send(const std::string& text, const std::string& content_type = "text/plain");
73 :
74 : // HTML response
75 4 : void html(const std::string& html) {
76 4 : send(html, "text/html");
77 4 : }
78 :
79 : // Error response, with the body produced by the error formatter (see
80 : // http_application::set_error_formatter): by default the message as text/plain, or a
81 : // JSON object for errors with details
82 : void error(http::http_response::status status, const std::string& message = "",
83 : const nlohmann::json& details = nullptr);
84 :
85 : // Formatter used by error() (set by the server)
86 : void set_error_formatter(std::shared_ptr<const error_formatter> formatter);
87 :
88 : // Set status code (for building custom responses: used by the answers that do not set
89 : // their own, such as send() and take_over(); not thread-safe)
90 : void status(http::http_response::status s);
91 :
92 : // Add a header to the response sent, whatever answer sends it (not thread-safe). The key
93 : // and value are moved into the response: pass an rvalue (e.g. std::move()) to save a copy.
94 : // An invalid header (a name that is not a token, or a value with CR, LF, NUL or another
95 : // control character but HTAB) is dropped, logged at debug level: the response is sent
96 : // without it
97 : void header(std::string key, std::string value);
98 :
99 : // Set a header to this value only, whatever was set before with its name (compared without
100 : // case), as setHeader() in Node and Header().Set() in Go; header() adds another field, as
101 : // appendHeader() and Header().Add(), which is what Set-Cookie needs. Same rules as header():
102 : // not thread-safe, and an invalid header is dropped, logged at debug level, leaving the
103 : // ones set before as they were
104 : void set_header(std::string key, std::string value);
105 :
106 : // Send raw http_response object (for advanced use cases). Its content is sent whole,
107 : // framed by a Content-Length of its size (any Content-Length or Transfer-Encoding it
108 : // carries is replaced, e.g. when copied from another response); 1xx, 204 and 304 are
109 : // sent without body, and 1xx and 204 without Content-Length. The headers set before
110 : // (header(), set_header()) are sent too, unless the message has its own of that name (as
111 : // the headers given to writeHead() in Node), but never Content-Length or Transfer-Encoding
112 : void send_response(const std::shared_ptr<http_response>& response);
113 :
114 : // WebSocket upgrade, with a message interface. With options.protocols, the first one the
115 : // client offers is accepted, and a client offering none of them is rejected with 400
116 : // (unless !protocol_required, then accepted without a subprotocol if it offers none); a
117 : // client offering only others, or any with no protocols, is rejected with 400. The
118 : // largest message and the keepalive are those of the options (16 MiB, ping after 60 s
119 : // without receiving anything, close after 120 s, by default). The handler is called on the
120 : // io_context of the connection, before the WebSocket starts reading (set its callbacks
121 : // there). A coroutine handler runs on that io_context too: the WebSocket starts reading
122 : // once it completes. Told of a shutdown through the connection (see
123 : // websocket_connection::on_shutdown()).
124 : void upgrade_websocket(const websocket_accept_options& options, websocket_handler handler);
125 : void upgrade_websocket(const websocket_accept_options& options, websocket_coroutine handler);
126 :
127 : // WebSocket upgrade handing out the WebSocket as a byte stream socket, for a protocol
128 : // running on it (MQTT, a custom one...) or a tunnel (socket_pipe): binary messages by
129 : // default, read as one continuous stream (see asio::websocket), with no keepalive unless
130 : // the options set one. The handler is called on the io_context of the socket once the
131 : // handshake response is written, with the upgraded_websocket: the WebSocket (holding any
132 : // frames the client sent right after its request), the subprotocol negotiated (see
133 : // websocket_accept_options) and its scope (see server_scope, e.g. to be told of a
134 : // shutdown). Rejected with 426 if the request is not a WebSocket upgrade, or 400 if its
135 : // key is missing or no subprotocol matches. The handler can be a coroutine, run on that
136 : // io_context.
137 : void upgrade_websocket_stream(const websocket_accept_options& options, websocket_stream_handler handler);
138 : void upgrade_websocket_stream(const websocket_accept_options& options, websocket_stream_coroutine handler);
139 :
140 : // Server-Sent Events (for a HEAD request, the headers alone: the handler is not called),
141 : // with the keep-alive of the options. The handler is called on the io_context of the
142 : // connection once the event stream started; a coroutine handler runs on that io_context.
143 : // Told of a shutdown through the connection (see sse_connection::on_shutdown()).
144 : void start_sse(sse_handler handler, const sse_options& options = {});
145 : void start_sse(sse_coroutine handler, const sse_options& options = {});
146 :
147 : // Take over the connection (e.g. for a custom protocol upgrade or a tunnel): sends
148 : // this response (status and headers set so far, 200 by default), then the server
149 : // stops handling the connection and passes it to `handler`: the socket, any bytes
150 : // already read past this request, and its scope (see server_scope, e.g. to be told of a
151 : // shutdown). The handler fully owns the socket. If the connection was closed before
152 : // (e.g. the client reset it while the response was pending; a client that only closed
153 : // its sending side has not left), the handler is not called, only released. A coroutine
154 : // handler runs on the io_context of the socket. Unless it switches protocols (101, or
155 : // 2xx to CONNECT) or has its own framing (Content-Length or Transfer-Encoding set with
156 : // header()), the response says "Connection: close": what the handler writes is read as
157 : // its body, until the connection closes.
158 : void take_over(takeover_handler handler);
159 : void take_over(takeover_coroutine handler);
160 :
161 : // File response, with the media type of its extension (application/octet-stream if it is
162 : // unknown or missing: nothing is guessed from its content), unless a Content-Type was set
163 : // before (header(), set_header()), as Node's send does. It carries a weak ETag and a
164 : // Last-Modified from the size and modification time of the file, and Cache-Control:
165 : // no-cache unless one was set before (header()): caches keep the file, but check it on
166 : // every use. GET and HEAD requests with If-None-Match (or else If-Modified-Since) are
167 : // answered with 304 Not Modified while it does not change; a GET with a single byte range
168 : // (Range, and If-Range with the Last-Modified date) with 206 Partial Content, or 416 if
169 : // it starts past the end. 404 if the file does not exist, 403 if it is not a regular file.
170 : // With force_download, Content-Disposition: attachment. A body (the file or its range) over
171 : // 1 MiB is streamed in pieces of 64 KiB at the pace of the client, unless it is a text file
172 : // compressed on the fly for the client; files over 4 MiB are never compressed on the fly.
173 : void send_file(const std::filesystem::path& path, bool force_download = false);
174 :
175 : // Redirect response. Each byte of `url` not allowed in a header value (CR, LF, NUL and the
176 : // other control characters but HTAB) is percent-encoded in Location; the rest is kept
177 : void redirect(const std::string& url, http::http_response::status redirect_type = http::http_response::status::moved_temporarily);
178 :
179 : // Chunked response: start_chunked() sends the headers (false if another answer was sent
180 : // first, or the connection is gone), then write_chunk() each chunk and end_chunked() the
181 : // last one. Chunks are only written on a response started with start_chunked(), once it
182 : // returned true (from any copy, any thread) and until end_chunked(): otherwise they
183 : // return false and write nothing. For a HEAD request, start_chunked() sends the headers
184 : // (without Transfer-Encoding) as a whole response, and the chunks are dropped.
185 : bool start_chunked(const std::string& content_type, http::http_response::status status = http::http_response::status::ok);
186 : bool write_chunk(const std::string& data);
187 : bool end_chunked();
188 :
189 : // Streamed response, with flow control (e.g. to relay a body as it arrives from
190 : // elsewhere). start_stream() sends the head: the headers set so far (header()) and those
191 : // of `head`, which replace the ones with the same name except Vary, whose fields add up,
192 : // with the status of `head`. Its framing is set here (Content-Length,
193 : // Transfer-Encoding and Connection headers in `head` are ignored): with `content_length`
194 : // the body has that length; without it, it is chunked, or, for HTTP/1.0 clients, ended
195 : // by closing the connection. Responses to HEAD requests, and with status 1xx, 204 or
196 : // 304, have no body (`content_length` is only advertised, except for 1xx and 204).
197 : // Then write_stream() each part of the body: it completes once the part is written to
198 : // the connection, so the body goes at the pace of the client. end_stream() completes
199 : // the response (false if the body is shorter than its Content-Length, then closing the
200 : // connection), or abort_stream() leaves it incomplete for the client (closing the
201 : // connection) if the body cannot be produced. They return false if the response cannot
202 : // be written: another answer was sent first, the connection is gone, more data than
203 : // its Content-Length or data for a response without body. Use one writer at a time.
204 : bool start_stream(const http_response& head, std::optional<size_t> content_length = std::nullopt);
205 : awaitable<bool> write_stream(std::string data);
206 : awaitable<bool> end_stream();
207 : void abort_stream();
208 :
209 : // Check if response has been sent
210 : bool has_responded() const;
211 :
212 : // Get the underlying connection (for advanced use cases); null for requests
213 : // dispatched in memory
214 : std::shared_ptr<server_connection> get_connection() const;
215 :
216 : private:
217 : struct state;
218 :
219 : // The request timeout of the route (see route::timeout) is run by the application
220 : friend class http_application;
221 : // Files are answered by the file server, with the messages of this response
222 : friend void file_server::send(response& res, const file_server::representation& file);
223 :
224 : // The request answered, and the formatter of its errors (null for the default one)
225 : const http_request& request_message() const;
226 : const error_formatter* formatter() const;
227 :
228 : // The CORS policy of the application and what it means for this request, set before the
229 : // response is handed to the handler. Every answer applies them to its message, reading
230 : // nothing that may change: it may run on any thread
231 : void set_cors(std::shared_ptr<const detail::cors_policy> policy, detail::cors_decision decision);
232 :
233 : // Call `hook` once the response starts (the first answer), from the thread answering.
234 : // Set before the response is handed to the handler.
235 : void on_start(std::function<void()> hook);
236 :
237 : // Answer the request as timed out, with an error of `status`, unless another answer
238 : // started first (false then). Later answers are dropped quietly.
239 : // `with_headers`: with the status and headers set so far (false when another thread may
240 : // still be setting them, as for a request dispatched in memory)
241 : bool answer_timeout(http::http_response::status status, bool with_headers = true);
242 :
243 : // Whether the response was answered by answer_timeout()
244 : bool timed_out() const;
245 :
246 : // An answer arriving once the response was sent (or chunks out of place): logged as an
247 : // error, unless the request timed out (then the handler is just late)
248 : void log_late_answer(const char* message) const;
249 :
250 : bool ensure_not_responded() const;
251 : // Status and headers set before answering, created on first use
252 : http_response& draft();
253 : // Start answering: the message to send, with the status and headers set so far (the
254 : // draft, moved into it), if this is the first answer; otherwise null, and nothing must be
255 : // sent. What could fail building the answer (e.g. encoding its body) is done before
256 : std::shared_ptr<http_response> new_response();
257 : // The message of the answer started, from the draft (once), or without it
258 : std::shared_ptr<http_response> take_draft(bool with_draft = true);
259 : // Whether the request is HEAD: its response has no body
260 : bool head_request() const;
261 : // Body of a message about to be sent: framed (see frame_message), dropped for HEAD
262 : // (keeping its Content-Length), else compressed if `compress` and the client accepts it
263 : // (see compression::compress)
264 : void prepare_body(http_response& message, bool compress) const;
265 : // Framing of a message sent whole: its content is its body, with its Content-Length
266 : [[gnu::noinline, gnu::cold]] void frame_message(http_response& message) const;
267 : // Framing of the response of a connection taken over (see take_over): close if what the
268 : // handler writes is its body, ending when the connection closes, else none
269 : body_framing frame_takeover(http_response& message) const;
270 : // Mark the response as sent; false (and nothing must be sent) if it already was
271 : bool mark_responded();
272 : // Send the message of the answer started (see new_response(), prepare_body)
273 : void send_message(std::shared_ptr<http_response> message, bool compress = true);
274 : // Send the message of the answer started and hand the connection over to `handler`
275 : void hand_over(std::shared_ptr<http_response> message, takeover_handler handler);
276 : // Answers 501 if the connection cannot be taken over (in-memory requests)
277 : bool reject_takeover(const char* feature);
278 : // Upgrade to a WebSocket with a message interface, and to an event stream: `deliver`
279 : // receives the connection, and the socket it runs on (whose executor runs `deliver`)
280 : void accept_websocket(const websocket_accept_options& options,
281 : std::function<void(std::shared_ptr<websocket_connection>, asio::websocket&)> deliver);
282 : void accept_sse(const sse_options& options,
283 : std::function<void(std::shared_ptr<sse_connection>, asio::socket&)> deliver);
284 : // Checks of a WebSocket upgrade request and the subprotocol negotiated: the 101 response
285 : // to send, or null if the request was answered with an error
286 : std::shared_ptr<http_response> websocket_handshake(const std::vector<std::string>& protocols,
287 : bool protocol_required, bool reject_unknown_protocol,
288 : std::string& protocol);
289 :
290 : std::shared_ptr<state> state_;
291 : };
292 :
293 : } // namespace thinger::http
294 :
295 : #endif // THINGER_HTTP_SERVER_RESPONSE_HPP
|