Imported Upstream version 1.0.0
[platform/upstream/nghttp2.git] / src / h2load_http2_session.cc
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2014 Tatsuhiro Tsujikawa
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_http2_session.h"
26
27 #include <cassert>
28 #include <cerrno>
29
30 #include "h2load.h"
31 #include "util.h"
32 #include "template.h"
33
34 using namespace nghttp2;
35
36 namespace h2load {
37
38 Http2Session::Http2Session(Client *client)
39     : client_(client), session_(nullptr) {}
40
41 Http2Session::~Http2Session() { nghttp2_session_del(session_); }
42
43 namespace {
44 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
45                        const uint8_t *name, size_t namelen,
46                        const uint8_t *value, size_t valuelen, uint8_t flags,
47                        void *user_data) {
48   auto client = static_cast<Client *>(user_data);
49   if (frame->hd.type != NGHTTP2_HEADERS ||
50       frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
51     return 0;
52   }
53   client->on_header(frame->hd.stream_id, name, namelen, value, valuelen);
54   return 0;
55 }
56 } // namespace
57
58 namespace {
59 int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
60                            void *user_data) {
61   auto client = static_cast<Client *>(user_data);
62   if (frame->hd.type != NGHTTP2_HEADERS ||
63       frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
64     return 0;
65   }
66   client->worker->stats.bytes_head += frame->hd.length;
67   return 0;
68 }
69 } // namespace
70
71 namespace {
72 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
73                                 int32_t stream_id, const uint8_t *data,
74                                 size_t len, void *user_data) {
75   auto client = static_cast<Client *>(user_data);
76   client->worker->stats.bytes_body += len;
77   return 0;
78 }
79 } // namespace
80
81 namespace {
82 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
83                              uint32_t error_code, void *user_data) {
84   auto client = static_cast<Client *>(user_data);
85   auto req_stat = static_cast<RequestStat *>(
86       nghttp2_session_get_stream_user_data(session, stream_id));
87   if (!req_stat) {
88     return 0;
89   }
90   client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR, req_stat);
91   return 0;
92 }
93 } // namespace
94
95 namespace {
96 int before_frame_send_callback(nghttp2_session *session,
97                                const nghttp2_frame *frame, void *user_data) {
98   if (frame->hd.type != NGHTTP2_HEADERS ||
99       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
100     return 0;
101   }
102
103   auto client = static_cast<Client *>(user_data);
104   client->on_request(frame->hd.stream_id);
105   auto req_stat = static_cast<RequestStat *>(
106       nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
107   assert(req_stat);
108   client->record_request_time(req_stat);
109
110   return 0;
111 }
112 } // namespace
113
114 namespace {
115 ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
116                            uint8_t *buf, size_t length, uint32_t *data_flags,
117                            nghttp2_data_source *source, void *user_data) {
118   auto client = static_cast<Client *>(user_data);
119   auto config = client->worker->config;
120   auto req_stat = static_cast<RequestStat *>(
121       nghttp2_session_get_stream_user_data(session, stream_id));
122   assert(req_stat);
123   ssize_t nread;
124   while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
125              -1 &&
126          errno == EINTR)
127     ;
128
129   if (nread == -1) {
130     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
131   }
132
133   req_stat->data_offset += nread;
134
135   if (nread == 0 || req_stat->data_offset == config->data_length) {
136     *data_flags |= NGHTTP2_DATA_FLAG_EOF;
137   }
138
139   return nread;
140 }
141
142 } // namespace
143
144 namespace {
145 ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
146                       size_t length, int flags, void *user_data) {
147   auto client = static_cast<Client *>(user_data);
148   auto &wb = client->wb;
149
150   if (wb.wleft() == 0) {
151     return NGHTTP2_ERR_WOULDBLOCK;
152   }
153
154   return wb.write(data, length);
155 }
156 } // namespace
157
158 void Http2Session::on_connect() {
159   int rv;
160
161   nghttp2_session_callbacks *callbacks;
162
163   nghttp2_session_callbacks_new(&callbacks);
164
165   auto callbacks_deleter = defer(nghttp2_session_callbacks_del, callbacks);
166
167   nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
168                                                        on_frame_recv_callback);
169
170   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
171       callbacks, on_data_chunk_recv_callback);
172
173   nghttp2_session_callbacks_set_on_stream_close_callback(
174       callbacks, on_stream_close_callback);
175
176   nghttp2_session_callbacks_set_on_header_callback(callbacks,
177                                                    on_header_callback);
178
179   nghttp2_session_callbacks_set_before_frame_send_callback(
180       callbacks, before_frame_send_callback);
181
182   nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
183
184   nghttp2_session_client_new(&session_, callbacks, client_);
185
186   std::array<nghttp2_settings_entry, 2> iv;
187   iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
188   iv[0].value = 0;
189   iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
190   iv[1].value = (1 << client_->worker->config->window_bits) - 1;
191
192   rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(),
193                                iv.size());
194
195   assert(rv == 0);
196
197   auto extra_connection_window =
198       (1 << client_->worker->config->connection_window_bits) - 1 -
199       NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
200   if (extra_connection_window != 0) {
201     nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0,
202                                  extra_connection_window);
203   }
204
205   client_->signal_write();
206 }
207
208 void Http2Session::submit_request(RequestStat *req_stat) {
209   auto config = client_->worker->config;
210   auto &nva = config->nva[client_->reqidx++];
211
212   if (client_->reqidx == config->nva.size()) {
213     client_->reqidx = 0;
214   }
215
216   nghttp2_data_provider prd{{0}, file_read_callback};
217
218   auto stream_id =
219       nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
220                              config->data_fd == -1 ? nullptr : &prd, req_stat);
221   assert(stream_id > 0);
222 }
223
224 int Http2Session::on_read(const uint8_t *data, size_t len) {
225   auto rv = nghttp2_session_mem_recv(session_, data, len);
226   if (rv < 0) {
227     return -1;
228   }
229
230   assert(static_cast<size_t>(rv) == len);
231
232   if (nghttp2_session_want_read(session_) == 0 &&
233       nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
234     return -1;
235   }
236
237   client_->signal_write();
238
239   return 0;
240 }
241
242 int Http2Session::on_write() {
243   auto rv = nghttp2_session_send(session_);
244   if (rv != 0) {
245     return -1;
246   }
247
248   if (nghttp2_session_want_read(session_) == 0 &&
249       nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
250     return -1;
251   }
252
253   return 0;
254 }
255
256 void Http2Session::terminate() {
257   nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
258 }
259
260 } // namespace h2load