Line data Source code
1 : #ifndef THINGER_HTTP_SERVER_RESPONSE_OBSERVER_HPP
2 : #define THINGER_HTTP_SERVER_RESPONSE_OBSERVER_HPP
3 :
4 : #include "../common/http_response.hpp"
5 : #include "../../util/types.hpp"
6 : #include <atomic>
7 : #include <chrono>
8 : #include <cstdint>
9 : #include <functional>
10 : #include <memory>
11 :
12 : namespace thinger::http {
13 :
14 : class request;
15 :
16 : // How a response ended (see response_info)
17 : enum class response_outcome : uint8_t {
18 : // Written whole (for a request dispatched in memory, collected whole)
19 : completed,
20 : // Its head written, the connection was handed over: a WebSocket upgrade, an event stream
21 : // (start_sse()) or take_over(). What happens on the connection afterwards is not reported
22 : // (see websocket_connection::on_close() and sse_connection::on_close())
23 : taken_over,
24 : // Not completed: the connection was closed or failed first (the client left, the connection
25 : // timed out, the server shut down...), the response was aborted (abort_stream(), or a
26 : // handler failing once it started), or nothing answered the request
27 : aborted
28 : };
29 :
30 : // A response, as http_application::on_response() reports it
31 : struct response_info {
32 : // Status of the response head (0 if nothing was answered)
33 : http_response::status status{};
34 : response_outcome outcome = response_outcome::completed;
35 : // The response that ended had its body sent in parts as it was produced: start_chunked()
36 : // or start_stream() (not for HEAD, answered with the head alone, nor for a response that
37 : // replaced it, as the 502 of a stream aborted in memory)
38 : bool streamed = false;
39 : // Bytes of this response written to the connection, head included (and an interim
40 : // 100 Continue), before any TLS; for a request dispatched in memory, the size of the body
41 : uint64_t bytes = 0;
42 : // From the moment the application received the request (its headers read) until the
43 : // response ended: written, handed over, or aborted
44 : std::chrono::steady_clock::duration duration{};
45 : };
46 :
47 : // See http_application::on_response()
48 : using response_hook = sync_callback<void(const request&, const response_info&)>;
49 :
50 : // The end of a request, for the response hook: its response ended (as told by the sink writing
51 : // it) and the application is done with the request, in either order. Created only while a hook
52 : // is set: the server costs nothing without one.
53 : class response_observer {
54 : public:
55 : // The peer address of the request is kept, for a hook called once its connection is gone
56 : response_observer(std::shared_ptr<request> req, std::shared_ptr<const response_hook> hook);
57 :
58 : // Reports the response if it was not yet: as aborted, unless it ended
59 : ~response_observer();
60 :
61 : response_observer(const response_observer&) = delete;
62 : response_observer& operator=(const response_observer&) = delete;
63 :
64 : // The response, from the sink writing it (one thread at a time): its head, with whether
65 : // its body follows in parts, the bytes written, and its end (once, later ones are ignored)
66 : void head(http_response::status status, bool streamed);
67 1247 : void add_bytes(uint64_t bytes) { info_.bytes += bytes; }
68 : void ended(response_outcome outcome);
69 :
70 : // From any thread: the response is aborted (it ends as such)
71 24 : void abort() { aborted_.store(true, std::memory_order_relaxed); }
72 :
73 : // The application is done with the request
74 : void handled();
75 :
76 : // `callback` runs once the hook has returned (for an in-memory dispatch, which waits for
77 : // it). Set before the response can end
78 759 : void on_reported(std::function<void()> callback) { on_reported_ = std::move(callback); }
79 :
80 : private:
81 : // One side is done: the last one reports
82 : void done();
83 : void report() noexcept;
84 :
85 : std::shared_ptr<request> request_;
86 : std::shared_ptr<const response_hook> hook_;
87 : std::function<void()> on_reported_;
88 : std::chrono::steady_clock::time_point start_ = std::chrono::steady_clock::now();
89 : response_info info_;
90 : bool ended_ = false;
91 : bool handled_ = false;
92 : bool reported_ = false;
93 : std::atomic<bool> aborted_{false};
94 : // The sides not done yet: the response and the application
95 : std::atomic<uint8_t> pending_{2};
96 : };
97 :
98 : } // namespace thinger::http
99 :
100 : #endif // THINGER_HTTP_SERVER_RESPONSE_OBSERVER_HPP
|