• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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