Line data Source code
1 : #ifndef THINGER_HTTP_SERVER_BASE_HPP
2 : #define THINGER_HTTP_SERVER_BASE_HPP
3 :
4 : #include "http_application.hpp"
5 : #include "connection_tracker.hpp"
6 : #include "../common/body_logging.hpp"
7 : #include "../../asio/listener.hpp"
8 : #include "../../util/types.hpp"
9 : #include <boost/system/error_code.hpp>
10 : #include <memory>
11 : #include <string>
12 : #include <functional>
13 : #include <chrono>
14 : #include <vector>
15 : #include <mutex>
16 : #include <optional>
17 :
18 : namespace thinger::http {
19 :
20 : // How the connections of a server are served (see server_base::set_connection_options())
21 : struct connection_options {
22 : // A connection idle for this long (no request arriving, nor a response being written, nor
23 : // the handler of a request received whole running) is closed
24 18 : std::chrono::seconds idle_timeout{120};
25 :
26 : // Deadlines of a request, whatever the pace of its bytes (Node's headersTimeout and
27 : // requestTimeout), from its start: its first byte, or the start of serving the connection
28 : // for its first request. A head not complete within `header_timeout` (capped by
29 : // `request_timeout`) is answered 408 Request Timeout and the connection closed, at once;
30 : // a connection that sent no byte of it is closed without a response. A request whose body
31 : // was not read to its end within `request_timeout` (which a route, a group or a virtual
32 : // host may change, see route::request_timeout()) is answered 408, or the connection closed
33 : // if its response already started; its handler is cancelled (cancel_reason::timeout). Zero
34 : // disables either
35 18 : std::chrono::milliseconds header_timeout{60000};
36 18 : std::chrono::milliseconds request_timeout{300000};
37 :
38 : // A response write of which the socket accepts no 64 KiB slice (the whole write, if
39 : // smaller) for this long closes the connection, as a client that stopped reading, whether
40 : // it was to be kept alive or not (nginx's send_timeout); `idle_timeout` never cuts a
41 : // response being written. The client taking bytes from the socket is not checked, as SSE
42 : // and WebSocket writes do: a slow client with a large send buffer may be cut while it
43 : // still reads, if the socket accepts no slice for this long. Zero disables it
44 18 : std::chrono::milliseconds write_timeout{60000};
45 :
46 : // Lingering close (RFC 9112 section 9.6): a connection closed after a response while the
47 : // client may still be sending its request (e.g. a body over the limit, answered 413 before
48 : // it was read) stops sending, then reads and drops what arrives, until the client closes,
49 : // `linger_max_bytes` were dropped or `linger_timeout` passed, and closes. Closed at once
50 : // instead, the unread data makes the system reset the connection, and a client still
51 : // sending may never read the response. Zero for either closes at once (also on sockets that
52 : // cannot half-close, such as custom ones). A graceful shutdown of the server closes the
53 : // connections lingering after at most 500 ms
54 : size_t linger_max_bytes = 8 << 20;
55 18 : std::chrono::milliseconds linger_timeout{5000};
56 :
57 : // Log the header values and the start of the bodies of the requests and responses: an
58 : // opt-in for debugging, off by default (see body_logging)
59 : body_logging logging;
60 : };
61 :
62 : // A server is an application (see http_application: routes, virtual hosts, middlewares and
63 : // request processing settings) served on listeners: TCP, with or without TLS, or Unix sockets.
64 : // It only adds the listeners and the connection settings. Every listener serves the same
65 : // application: a route (or middleware, virtual host, hook) answers on all of them. Add them
66 : // before any request arrives (before listen(), or after it but before a request comes): every
67 : // request reads them without locks, so changing them while serving is a data race. An
68 : // http_application can also be used on its own, e.g. for in-memory dispatch.
69 : class server_base : public http_application {
70 : public:
71 10605 : server_base() = default;
72 : // Stops the server as server_base::stop() does (the subclasses in the library call their
73 : // own stop() first; one whose listeners use something it owns, e.g. an io_context, must
74 : // call stop() in its destructor too): once it returns, its connections are closing and its
75 : // handlers cancelled, none waited for, and no code of the library touches the server. A handler still running keeps the routes, hosts,
76 : // middlewares, hooks and settings it reaches (shared with its connection), so it may
77 : // finish its current step against them; what it captured itself (the server included) is
78 : // the application's to keep alive. It may be destroyed from one of its own handlers, on
79 : // any worker thread: that handler goes on, and must not touch the server afterwards
80 : ~server_base() override;
81 :
82 : // --- Server configuration ---
83 : // How its connections are served: copied once, and read by every connection. Set it before
84 : // listen(): a later call applies to the connections accepted afterwards only, never to
85 : // those open
86 : void set_connection_options(connection_options options);
87 :
88 : // --- Server control ---
89 : // Listen with `options` (a TCP endpoint, with TLS if `options.tls` holds a certificate
90 : // manager, or a Unix socket; see asio::listener_options), and serve the connections it
91 : // accepts. May be called several times, each call opening a listener of its own: all serve
92 : // the application. An error, with the listeners open left as they are, if:
93 : // - the listener cannot open (see asio::listener::open());
94 : // - the server listens on that endpoint already, or another listen() is opening it
95 : // (`address_in_use`; the same host and port, or the same Unix socket path);
96 : // - a shutdown is in progress (`shut_down`): it works again once the shutdown ended;
97 : // - stop_listening(), stop() or a shutdown stopped the listener while it opened
98 : // (`operation_aborted`), e.g. while it waited to bind again.
99 : // The listener is in listening_on() from the moment it accepts connections, so whatever
100 : // stops the listeners, on any thread, stops it too.
101 : boost::system::error_code listen(asio::listener_options options);
102 : // Listen on plain TCP: the host and the port (0 for one chosen by the system, see
103 : // listening_on())
104 : boost::system::error_code listen(std::string host, uint16_t port);
105 :
106 : // Listen on plain TCP and wait() until the server stops: false if it cannot listen.
107 : // `on_listening` (if any) is called once it listens, before waiting
108 : bool start(uint16_t port);
109 : bool start(uint16_t port, const sync_callback<void()> &on_listening);
110 : bool start(const std::string& host, uint16_t port);
111 : bool start(const std::string& host, uint16_t port, const sync_callback<void()> &on_listening);
112 :
113 : // The three steps of stopping a server: stop_listening() (quiesce), shutdown() (drain, until
114 : // a deadline) and stop() (release). Each may be called from any thread, more than once.
115 :
116 : // Stop listening right away, on every listener: new connections are refused. Those open are
117 : // left as they are: they go on serving, and count in stats(). Later calls do nothing
118 : void stop_listening();
119 :
120 : // Graceful shutdown, with `by` as its whole budget (nothing waits past it):
121 : // 1. it stops listening, if that was not done already (see stop_listening());
122 : // 2. it calls on_shutdown(), tells the connections taken over (WebSockets, event streams,
123 : // tunnels...) to finish (see websocket_connection::on_shutdown(),
124 : // sse_connection::on_shutdown(), server_scope::on_shutdown()), closes the idle connections, and
125 : // lets those handling a request finish it: its response tells the client the connection
126 : // closes (Connection: close), and then it closes;
127 : // 3. at `by`, it closes whatever is still open.
128 : // Then the server stops (wait() returns). The result is true if every
129 : // connection finished before `by`; false if some were closed, or stop() ended the shutdown
130 : // first. The handlers still running at `by` are not waited for: they go on on their threads.
131 : //
132 : // It may be awaited from any executor, on a thread of the server too (a handler, a worker of
133 : // a pool_server), which it does not block. Every listener drains at once, under the same
134 : // `by`. Several calls, also concurrent ones, share the same end and result: the first one
135 : // sets `by`. From an asio::signal_set handler too, but not from a raw signal handler: it is
136 : // not async-signal-safe (it locks and allocates). A server that is not running (wait() not
137 : // called, or stopped) ends it at once: true if no connection is open.
138 : awaitable<bool> shutdown(std::chrono::steady_clock::time_point by);
139 : // The same, with `by` `grace` from now, blocking until it ends: for a thread that does not
140 : // serve the server (e.g. main()). On one that does (a handler, a worker of a pool_server) it
141 : // would block the drain it waits for, so it does not wait there: it logs an error, starts
142 : // the shutdown, and returns false at once (co_await shutdown(by) there instead)
143 : bool shutdown(std::chrono::milliseconds grace = std::chrono::seconds(30));
144 :
145 : // Stop the server right away: stop listening, end a shutdown in progress (its result is
146 : // false), close the connections still open (idle keep-alive, handling a request, or taken
147 : // over: WebSockets, event streams, tunnels...), and release wait(). The handlers still
148 : // running are cancelled with cancel_reason::shutdown, whatever their route declared (their
149 : // next cancellable await ends with operation_aborted), and are not waited for: each
150 : // connection is closed on its own thread, after its return, and what a handler sends
151 : // afterwards is dropped. A handler still running keeps what the server serves with (routes,
152 : // hosts, middlewares, hooks, settings), so it may finish its current step against it; what
153 : // it captured itself is the application's (see ~server_base()). It does not block, so it may
154 : // be called from one of the server's handlers, on any thread. The connections it closed are
155 : // never served again, even if the server listens again (a server with its own io_context
156 : // stops running it too).
157 : //
158 : // The end of a shutdown does not call it: it may end on a thread of the server while the
159 : // server is being destroyed, so it stops the server without a virtual call (see
160 : // set_shutdown_stop()). An override (in a subclass of pool_server or server) runs only when
161 : // stop() is called, never at the end of a shutdown: what it releases after a shutdown, it
162 : // releases once shutdown() or wait() returned, or in its destructor
163 : virtual void stop();
164 :
165 : // Called once when a graceful shutdown starts, on the thread starting it, once the server
166 : // stopped listening: for the application to finish what it runs on connections taken over
167 : // (or anything else). What it throws is logged, and the shutdown goes on
168 : void on_shutdown(sync_callback<void()> callback);
169 :
170 : // Connections open by what they serve, and served since the server was created, over all
171 : // its listeners. It locks each list the connections are kept in (by io_context): meant to be
172 : // read now and then (e.g. by a monitoring endpoint), as counting costs nothing per request
173 : server_stats stats() const;
174 :
175 : // Whether it listens on any listener
176 : bool is_listening() const;
177 :
178 : // Where it listens, one entry per listener open, in the order they opened:
179 : // "http://0.0.0.0:8080", "https://[::1]:8443" or "unix:/run/x.sock", with the port the
180 : // system chose for a port 0. Empty while it does not listen
181 : std::vector<std::string> listening_on() const;
182 :
183 : // Wait until the server stops
184 : virtual void wait() = 0;
185 :
186 : protected:
187 : // The listener of `options`, on the executors of the server: not opened yet
188 : virtual std::unique_ptr<asio::listener> create_listener(asio::listener_options options) = 0;
189 :
190 : // A listener opened and started: the server serves (e.g. its io_context may run)
191 12192 : virtual void listening() {}
192 :
193 : // Where a shutdown waits for the connections: an io_context running while the server
194 : // runs, or none if nothing runs the connections
195 : virtual boost::asio::io_context* shutdown_context() = 0;
196 : // Whether the calling thread serves connections of the server (shutdown(grace) cannot block
197 : // there)
198 : virtual bool in_server_thread() = 0;
199 :
200 : // A shutdown in progress (if any) ends: stop() calls it first. Waits for one ending on
201 : // another thread
202 : void end_shutdown();
203 :
204 : // What the end of a shutdown runs, once it stopped listening, to stop the server as stop()
205 : // does (e.g. releasing wait()), instead of calling stop(): on the thread it ends on, where
206 : // a subclass may be in its destructor already. pool_server and server set it in their
207 : // constructors, and call stop() in their destructors, which waits for a shutdown ending on
208 : // another thread (so it never runs once their members are gone). None: the end of a
209 : // shutdown only stops listening
210 : void set_shutdown_stop(sync_callback<void()> stop);
211 :
212 : private:
213 : struct shutdown_state;
214 : // A listener of listen(), there from before it opens, so that stopping the listeners stops
215 : // it too, and another listen() on its endpoint is refused
216 : struct open_listener {
217 : std::shared_ptr<asio::listener> listener; // (opened by listen() out of the lock)
218 : decltype(asio::listener_options::endpoint) endpoint; // as asked for
219 : uint16_t port = 0; // the TCP port asked for, then the one it listens on
220 : std::string url; // once it listens (not listed before)
221 : };
222 :
223 : void serve(std::shared_ptr<asio::socket> socket);
224 : // The server, for the logs: "server on http://0.0.0.0:8080" ("server" while not listening)
225 : std::string log_name() const;
226 : // The shutdown in progress, or the one that ended (until the server listens again); else
227 : // one started, until `by`
228 : std::shared_ptr<shutdown_state> start_shutdown(std::chrono::steady_clock::time_point by);
229 : // With shutdown_mutex_ held: false while a shutdown is in progress; else the server serves
230 : // (again, after a shutdown that ended)
231 : bool reset_shutdown();
232 :
233 : // The connections served, for the statistics and the shutdown
234 : std::shared_ptr<connection_tracker> connections_ = std::make_shared<connection_tracker>();
235 :
236 : // The listeners open and opening, and the connection options (guarded by listeners_mutex_:
237 : // listen() and stop() may be called from any thread, and connections are accepted on the
238 : // server's). Taken after shutdown_mutex_ when both are
239 : mutable std::mutex listeners_mutex_;
240 : std::vector<open_listener> listeners_;
241 : std::shared_ptr<const connection_options> options_ = std::make_shared<const connection_options>();
242 :
243 : sync_callback<void()> on_shutdown_;
244 : // Set once, when the server is constructed (see set_shutdown_stop())
245 : sync_callback<void()> shutdown_stop_;
246 : // The last shutdown (guarded by its mutex)
247 : mutable std::mutex shutdown_mutex_;
248 : std::shared_ptr<shutdown_state> shutdown_;
249 : };
250 :
251 : } // namespace thinger::http
252 :
253 : #endif // THINGER_HTTP_SERVER_BASE_HPP
|