Line data Source code
1 : #include "server_connection.hpp"
2 : #include "server_base.hpp"
3 : #include "../../asio/sockets/socket.hpp"
4 : #include "body_reader.hpp"
5 : #include "../../util/logger.hpp"
6 : #include <algorithm>
7 :
8 : // The deadlines of a server connection, on its one watchdog (timeout_timer_): no timer, spawn or
9 : // allocation of their own. The watchdog is armed at the earliest deadline that applies, and on
10 : // expiry the connection works out which one passed: it acts for it, or arms the watchdog again
11 : // for the next one (a head completed, activity moved the idle deadline, or a write went on,
12 : // meanwhile)
13 :
14 : namespace thinger::http {
15 :
16 159293 : void server_connection::arm_deadline() {
17 159293 : timeout_timer_.expires_at(next_deadline());
18 159293 : }
19 :
20 159293 : server_connection::clock::time_point server_connection::next_deadline() const {
21 159293 : auto deadline = idle_deadline();
22 159293 : if (clock::time_point head; head_deadline(head)) deadline = std::min(deadline, head);
23 159293 : if (clock::time_point body; body_deadline(body)) deadline = std::min(deadline, body);
24 159293 : if (read_waits_ && read_bounded_) deadline = std::min(deadline, read_deadline_);
25 159293 : return deadline;
26 : }
27 :
28 159409 : server_connection::clock::time_point server_connection::idle_deadline() const {
29 : // A response the client still takes is no idle connection, however long it lasts: only a
30 : // write without progress for its deadline is. Not floored by the hold, as the request ones
31 159409 : if (write_waits_) return write_progress_ + options_->write_timeout;
32 147223 : return std::max(last_activity_ + timeout_, hold_);
33 : }
34 :
35 7339 : void server_connection::write_started() {
36 7339 : writing_ = true;
37 7339 : if (options_->write_timeout.count() <= 0) return;
38 7336 : write_waits_ = true;
39 7336 : write_progress_ = clock::now();
40 7336 : arm_deadline();
41 : }
42 :
43 7339 : bool server_connection::write_in_slices(const std::vector<boost::asio::const_buffer>& buffers) const {
44 : // Without the deadline, or within one slice, written at once
45 7339 : return options_->write_timeout.count() > 0 && boost::asio::buffer_size(buffers) > asio::socket::WRITE_SLICE_SIZE;
46 : }
47 :
48 159535 : bool server_connection::head_deadline(clock::time_point& deadline) const {
49 : // (not once the connection stopped reading)
50 159535 : if (!reading_head_ || !running_) return false;
51 : // Capped by the request deadline, as Node does; zero disables either
52 46346 : auto limit = options_->header_timeout;
53 46346 : auto request = options_->request_timeout;
54 46346 : if (request.count() > 0 && (limit.count() <= 0 || request < limit)) limit = request;
55 46346 : if (limit.count() <= 0) return false;
56 46340 : deadline = request_start_ + limit;
57 46340 : return true;
58 : }
59 :
60 27239 : void server_connection::head_started(bool pipelined) {
61 27239 : if (reading_head_) return;
62 5132 : request_start_ = pipelined ? clock::now() : last_activity_;
63 : }
64 :
65 3355 : void server_connection::head_incomplete() {
66 3355 : if (reading_head_) return;
67 429 : reading_head_ = true;
68 429 : arm_deadline();
69 : }
70 :
71 242 : void server_connection::deadline_expired() {
72 242 : auto now = clock::now();
73 242 : if (clock::time_point head; head_deadline(head) && now >= head) {
74 18 : head_timed_out();
75 18 : return;
76 : }
77 224 : if (read_waits_ && read_bounded_ && now >= read_deadline_) [[unlikely]] {
78 75 : read_waits_ = false;
79 75 : read_expired_ = true;
80 75 : read_cancel_.emit(boost::asio::cancellation_type::terminal);
81 75 : arm_deadline();
82 75 : return;
83 : }
84 149 : if (clock::time_point body; body_deadline(body) && now >= body) {
85 33 : request_timed_out(*in_progress_);
86 33 : return;
87 : }
88 116 : if (now >= idle_deadline()) {
89 : // Nor is a request received whole whose handler runs idle, as in Node and Go: looked at
90 : // again an idle timeout later (the watchdog is never left unarmed, whatever ends the
91 : // handler). Checked as it expires, so that a request costs nothing more. A body still
92 : // arriving is bounded by it, as every byte moves it
93 86 : if (!write_waits_ && in_progress_ && in_progress_->handler_running() && !in_progress_->input_pending()) {
94 6 : timeout_timer_.expires_at(now + std::max(timeout_, std::chrono::seconds{1}));
95 6 : return;
96 : }
97 80 : if (write_waits_) {
98 28 : LOG_DEBUG("http server connection closed: the client took no byte of its response within its write deadline");
99 : } else {
100 52 : LOG_DEBUG("http server connection timed out after {} seconds", timeout_.count());
101 : }
102 80 : close();
103 80 : return;
104 : }
105 : // None passed: the earliest that applies now
106 30 : arm_deadline();
107 : }
108 :
109 18 : void server_connection::head_timed_out() {
110 18 : reading_head_ = false;
111 : // No byte of a request arrived (only the first request of a connection is timed so): closed
112 : // without a response, which the client could take for that of a request it just sent, as Go
113 : // and nginx do. Logged at debug level only, as the other expiries: the client decides how often
114 18 : if (request_parser_.idle()) {
115 9 : LOG_DEBUG("http server connection closed: no request arrived within its header deadline");
116 9 : close();
117 9 : return;
118 : }
119 9 : LOG_DEBUG("http request head not complete within its deadline: answered 408");
120 : // The read loop stops reading (whatever its pending read returns), and the connection closes
121 : // once the 408 is written, at once: what the client still sends is not waited for
122 9 : running_ = false;
123 9 : answer_and_close(http_response::status::timed_out, detail::close_reason::request_timeout, false);
124 : }
125 :
126 159442 : bool server_connection::body_deadline(clock::time_point& deadline) const {
127 : // Received once its body was read to its end: told by the body reader, checked here
128 159442 : if (request_deadline_.count() <= 0 || !in_progress_ || !in_progress_->input_pending() || !running_) return false;
129 45710 : deadline = request_start_ + request_deadline_;
130 45710 : return true;
131 : }
132 :
133 27 : void server_connection::set_request_deadline(const http_stream& stream, std::chrono::milliseconds deadline) {
134 : // Only while its body is read: a request without one was received with its head
135 27 : if (in_progress_ != &stream || !stream.input_pending()) return;
136 27 : request_deadline_ = deadline;
137 : // An earlier deadline arms the watchdog again (at once if it passed already)
138 27 : arm_deadline();
139 : }
140 :
141 515 : void server_connection::set_read_deadline(const http_stream* stream, clock::time_point deadline) {
142 515 : if (in_progress_ != stream) return;
143 : // It replaces the request deadline for the rest of the body, as in Go: the read deadline
144 : // itself applies only while a read waits for the socket (see body_read_started()), a
145 : // read already waiting included
146 515 : request_deadline_ = std::chrono::milliseconds{0};
147 515 : read_deadline_ = deadline;
148 : // (the end of time, as Go's zero time: none)
149 515 : read_bounded_ = deadline.time_since_epoch() != clock::duration::max();
150 515 : if (read_waits_) arm_deadline();
151 : }
152 :
153 43462 : bool server_connection::read_deadline_passed() const {
154 43462 : return read_bounded_ && clock::now() >= read_deadline_;
155 : }
156 :
157 43459 : void server_connection::body_read_started(boost::asio::cancellation_slot caller) {
158 43462 : if (caller.is_connected()) caller.assign([this](auto type) { read_cancel_.emit(type); });
159 43459 : read_waits_ = true;
160 43459 : if (read_bounded_) arm_deadline();
161 43459 : }
162 :
163 43007 : void server_connection::body_read_ended(boost::asio::cancellation_slot caller, io_result& result) {
164 : // No longer forwarded: the slot may be emitted after the connection is gone
165 43007 : if (caller.is_connected()) caller.clear();
166 43007 : read_waits_ = false;
167 43007 : if (read_expired_) [[unlikely]] {
168 75 : read_expired_ = false;
169 75 : auto& ec = std::get<boost::system::error_code>(result);
170 75 : if (ec == boost::asio::error::operation_aborted) ec = boost::system::errc::make_error_code(read_deadline_expired);
171 : }
172 43007 : }
173 :
174 33 : void server_connection::request_timed_out(http_stream& stream) {
175 33 : request_deadline_ = std::chrono::milliseconds{0};
176 33 : stream.cancel(cancel_reason::timeout);
177 : // Its response not started, nor another one being written (pipelined): answered 408 here,
178 : // and the connection closed after it, gracefully as the body may still come (as Node
179 : // does). Otherwise closed now: a write in progress is never cut short to make room for a
180 : // 408. Either way, what its handler reads or writes afterwards fails on the closed connection
181 63 : bool answerable = !stream.responded() && !stream.has_posted_frames() && !writing_ &&
182 63 : !request_queue_.empty() && request_queue_.front().get() == &stream;
183 33 : if (!answerable) {
184 3 : LOG_DEBUG("http request not received within its deadline, its response started: closing the connection");
185 3 : close();
186 3 : return;
187 : }
188 30 : LOG_DEBUG("http request not received within its deadline: answered 408");
189 : // The body read pending ends (nothing else uses the socket meanwhile)
190 30 : socket_->cancel();
191 30 : stream.must_close(detail::close_reason::request_timeout);
192 30 : handle_stock_error(request_queue_.front(), http_response::status::timed_out);
193 : }
194 :
195 381 : void server_connection::answer_and_close(http_response::status status, detail::close_reason reason,
196 : bool input_pending) {
197 : // Where the request ends is unknown: the connection closes after the response
198 381 : auto stream = std::make_shared<http_stream>(++request_id_, reason, socket_->get_io_context().get_executor());
199 381 : stream->set_input_pending(input_pending);
200 381 : request_queue_.push_back(stream);
201 381 : handle_stock_error(stream, status);
202 381 : }
203 :
204 : }
|