1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2015 British Broadcasting Corporation
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25 #include "h2load_http1_session.h"
26
27 #include <cassert>
28 #include <cerrno>
29
30 #include "h2load.h"
31 #include "util.h"
32 #include "template.h"
33
34 #include <iostream>
35 #include <fstream>
36
37 using namespace nghttp2;
38
39 namespace h2load {
40
41 namespace {
42 // HTTP response message begin
htp_msg_begincb(llhttp_t * htp)43 int htp_msg_begincb(llhttp_t *htp) {
44 auto session = static_cast<Http1Session *>(htp->data);
45
46 if (session->stream_resp_counter_ > session->stream_req_counter_) {
47 return -1;
48 }
49
50 return 0;
51 }
52 } // namespace
53
54 namespace {
55 // HTTP response status code
htp_statuscb(llhttp_t * htp,const char * at,size_t length)56 int htp_statuscb(llhttp_t *htp, const char *at, size_t length) {
57 auto session = static_cast<Http1Session *>(htp->data);
58 auto client = session->get_client();
59
60 if (htp->status_code / 100 == 1) {
61 return 0;
62 }
63
64 client->on_status_code(session->stream_resp_counter_, htp->status_code);
65
66 return 0;
67 }
68 } // namespace
69
70 namespace {
71 // HTTP response message complete
htp_msg_completecb(llhttp_t * htp)72 int htp_msg_completecb(llhttp_t *htp) {
73 auto session = static_cast<Http1Session *>(htp->data);
74 auto client = session->get_client();
75
76 if (htp->status_code / 100 == 1) {
77 return 0;
78 }
79
80 client->final = llhttp_should_keep_alive(htp) == 0;
81 auto req_stat = client->get_req_stat(session->stream_resp_counter_);
82
83 assert(req_stat);
84
85 auto config = client->worker->config;
86 if (req_stat->data_offset >= config->data_length) {
87 client->on_stream_close(session->stream_resp_counter_, true, client->final);
88 }
89
90 session->stream_resp_counter_ += 2;
91
92 if (client->final) {
93 session->stream_req_counter_ = session->stream_resp_counter_;
94
95 // Connection is going down. If we have still request to do,
96 // create new connection and keep on doing the job.
97 if (client->req_left) {
98 client->try_new_connection();
99 }
100
101 return HPE_PAUSED;
102 }
103
104 return 0;
105 }
106 } // namespace
107
108 namespace {
htp_hdr_keycb(llhttp_t * htp,const char * data,size_t len)109 int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
110 auto session = static_cast<Http1Session *>(htp->data);
111 auto client = session->get_client();
112
113 client->worker->stats.bytes_head += len;
114 client->worker->stats.bytes_head_decomp += len;
115 return 0;
116 }
117 } // namespace
118
119 namespace {
htp_hdr_valcb(llhttp_t * htp,const char * data,size_t len)120 int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
121 auto session = static_cast<Http1Session *>(htp->data);
122 auto client = session->get_client();
123
124 client->worker->stats.bytes_head += len;
125 client->worker->stats.bytes_head_decomp += len;
126 return 0;
127 }
128 } // namespace
129
130 namespace {
htp_hdrs_completecb(llhttp_t * htp)131 int htp_hdrs_completecb(llhttp_t *htp) {
132 return !http2::expect_response_body(htp->status_code);
133 }
134 } // namespace
135
136 namespace {
htp_body_cb(llhttp_t * htp,const char * data,size_t len)137 int htp_body_cb(llhttp_t *htp, const char *data, size_t len) {
138 auto session = static_cast<Http1Session *>(htp->data);
139 auto client = session->get_client();
140
141 client->record_ttfb();
142 client->worker->stats.bytes_body += len;
143
144 return 0;
145 }
146 } // namespace
147
148 namespace {
149 constexpr llhttp_settings_t htp_hooks = {
150 htp_msg_begincb, // llhttp_cb on_message_begin;
151 nullptr, // llhttp_data_cb on_url;
152 htp_statuscb, // llhttp_data_cb on_status;
153 nullptr, // llhttp_data_cb on_method;
154 nullptr, // llhttp_data_cb on_version;
155 htp_hdr_keycb, // llhttp_data_cb on_header_field;
156 htp_hdr_valcb, // llhttp_data_cb on_header_value;
157 nullptr, // llhttp_data_cb on_chunk_extension_name;
158 nullptr, // llhttp_data_cb on_chunk_extension_value;
159 htp_hdrs_completecb, // llhttp_cb on_headers_complete;
160 htp_body_cb, // llhttp_data_cb on_body;
161 htp_msg_completecb, // llhttp_cb on_message_complete;
162 nullptr, // llhttp_cb on_url_complete;
163 nullptr, // llhttp_cb on_status_complete;
164 nullptr, // llhttp_cb on_method_complete;
165 nullptr, // llhttp_cb on_version_complete;
166 nullptr, // llhttp_cb on_header_field_complete;
167 nullptr, // llhttp_cb on_header_value_complete;
168 nullptr, // llhttp_cb on_chunk_extension_name_complete;
169 nullptr, // llhttp_cb on_chunk_extension_value_complete;
170 nullptr, // llhttp_cb on_chunk_header;
171 nullptr, // llhttp_cb on_chunk_complete;
172 nullptr, // llhttp_cb on_reset;
173 };
174 } // namespace
175
Http1Session(Client * client)176 Http1Session::Http1Session(Client *client)
177 : stream_req_counter_(1),
178 stream_resp_counter_(1),
179 client_(client),
180 htp_(),
181 complete_(false) {
182 llhttp_init(&htp_, HTTP_RESPONSE, &htp_hooks);
183 htp_.data = this;
184 }
185
~Http1Session()186 Http1Session::~Http1Session() {}
187
on_connect()188 void Http1Session::on_connect() { client_->signal_write(); }
189
submit_request()190 int Http1Session::submit_request() {
191 auto config = client_->worker->config;
192 const auto &req = config->h1reqs[client_->reqidx];
193 client_->reqidx++;
194
195 if (client_->reqidx == config->h1reqs.size()) {
196 client_->reqidx = 0;
197 }
198
199 client_->on_request(stream_req_counter_);
200
201 auto req_stat = client_->get_req_stat(stream_req_counter_);
202
203 client_->record_request_time(req_stat);
204 client_->wb.append(req);
205
206 if (config->data_fd == -1 || config->data_length == 0) {
207 // increment for next request
208 stream_req_counter_ += 2;
209
210 return 0;
211 }
212
213 return on_write();
214 }
215
on_read(const uint8_t * data,size_t len)216 int Http1Session::on_read(const uint8_t *data, size_t len) {
217 auto htperr =
218 llhttp_execute(&htp_, reinterpret_cast<const char *>(data), len);
219 auto nread = htperr == HPE_OK
220 ? len
221 : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
222 llhttp_get_error_pos(&htp_)) -
223 data);
224
225 if (client_->worker->config->verbose) {
226 std::cout.write(reinterpret_cast<const char *>(data), nread);
227 }
228
229 if (htperr == HPE_PAUSED) {
230 // pause is done only when connection: close is requested
231 return -1;
232 }
233
234 if (htperr != HPE_OK) {
235 std::cerr << "[ERROR] HTTP parse error: "
236 << "(" << llhttp_errno_name(htperr) << ") "
237 << llhttp_get_error_reason(&htp_) << std::endl;
238 return -1;
239 }
240
241 return 0;
242 }
243
on_write()244 int Http1Session::on_write() {
245 if (complete_) {
246 return -1;
247 }
248
249 auto config = client_->worker->config;
250 auto req_stat = client_->get_req_stat(stream_req_counter_);
251 if (!req_stat) {
252 return 0;
253 }
254
255 if (req_stat->data_offset < config->data_length) {
256 auto req_stat = client_->get_req_stat(stream_req_counter_);
257 auto &wb = client_->wb;
258
259 // TODO unfortunately, wb has no interface to use with read(2)
260 // family functions.
261 std::array<uint8_t, 16_k> buf;
262
263 ssize_t nread;
264 while ((nread = pread(config->data_fd, buf.data(), buf.size(),
265 req_stat->data_offset)) == -1 &&
266 errno == EINTR)
267 ;
268
269 if (nread == -1) {
270 return -1;
271 }
272
273 req_stat->data_offset += nread;
274
275 wb.append(buf.data(), nread);
276
277 if (client_->worker->config->verbose) {
278 std::cout << "[send " << nread << " byte(s)]" << std::endl;
279 }
280
281 if (req_stat->data_offset == config->data_length) {
282 // increment for next request
283 stream_req_counter_ += 2;
284
285 if (stream_resp_counter_ == stream_req_counter_) {
286 // Response has already been received
287 client_->on_stream_close(stream_resp_counter_ - 2, true,
288 client_->final);
289 }
290 }
291 }
292
293 return 0;
294 }
295
terminate()296 void Http1Session::terminate() { complete_ = true; }
297
get_client()298 Client *Http1Session::get_client() { return client_; }
299
max_concurrent_streams()300 size_t Http1Session::max_concurrent_streams() {
301 auto config = client_->worker->config;
302
303 return config->data_fd == -1 ? config->max_concurrent_streams : 1;
304 }
305
306 } // namespace h2load
307