/src/h2o/lib/http2/stream.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2014 DeNA Co., Ltd. |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include "h2o.h" |
23 | | #include "h2o/http2.h" |
24 | | #include "h2o/http2_internal.h" |
25 | | #include "h2o/absprio.h" |
26 | | #include "../probes_.h" |
27 | | |
28 | | static void finalostream_send(h2o_ostream_t *self, h2o_req_t *req, h2o_sendvec_t *bufs, size_t bufcnt, h2o_send_state_t state); |
29 | | static void finalostream_send_informational(h2o_ostream_t *_self, h2o_req_t *req); |
30 | | |
31 | | static size_t sz_min(size_t x, size_t y) |
32 | 7.72k | { |
33 | 7.72k | return x < y ? x : y; |
34 | 7.72k | } |
35 | | |
36 | | h2o_http2_stream_t *h2o_http2_stream_open(h2o_http2_conn_t *conn, uint32_t stream_id, h2o_req_t *src_req, |
37 | | const h2o_http2_priority_t *received_priority) |
38 | 22.5k | { |
39 | 22.5k | h2o_http2_stream_t *stream = h2o_mem_alloc(sizeof(*stream)); |
40 | | |
41 | | /* init properties (other than req) */ |
42 | 22.5k | memset(stream, 0, offsetof(h2o_http2_stream_t, req)); |
43 | 22.5k | stream->stream_id = stream_id; |
44 | 22.5k | stream->_ostr_final.do_send = finalostream_send; |
45 | 22.5k | stream->_ostr_final.send_informational = |
46 | 22.5k | conn->super.ctx->globalconf->send_informational_mode == H2O_SEND_INFORMATIONAL_MODE_NONE ? NULL |
47 | 22.5k | : finalostream_send_informational; |
48 | 22.5k | stream->state = H2O_HTTP2_STREAM_STATE_IDLE; |
49 | 22.5k | h2o_http2_window_init(&stream->output_window, conn->peer_settings.initial_window_size); |
50 | 22.5k | h2o_http2_window_init(&stream->input_window.window, H2O_HTTP2_SETTINGS_HOST_STREAM_INITIAL_WINDOW_SIZE); |
51 | 22.5k | stream->received_priority = *received_priority; |
52 | | |
53 | | /* init request */ |
54 | 22.5k | h2o_init_request(&stream->req, &conn->super, src_req); |
55 | 22.5k | stream->req.version = 0x200; |
56 | 22.5k | if (src_req != NULL) |
57 | 0 | memset(&stream->req.upgrade, 0, sizeof(stream->req.upgrade)); |
58 | 22.5k | stream->req._ostr_top = &stream->_ostr_final; |
59 | | |
60 | 22.5k | h2o_http2_conn_register_stream(conn, stream); |
61 | | |
62 | 22.5k | ++conn->num_streams.priority.open; |
63 | 22.5k | stream->_num_streams_slot = &conn->num_streams.priority; |
64 | | |
65 | 22.5k | return stream; |
66 | 22.5k | } |
67 | | |
68 | | void h2o_http2_stream_close(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
69 | 22.5k | { |
70 | 22.5k | h2o_http2_conn_unregister_stream(conn, stream); |
71 | 22.5k | if (stream->cache_digests != NULL) |
72 | 165 | h2o_cache_digests_destroy(stream->cache_digests); |
73 | 22.5k | if (stream->req_body.buf != NULL) |
74 | 4.19k | h2o_buffer_dispose(&stream->req_body.buf); |
75 | 22.5k | h2o_dispose_request(&stream->req); |
76 | 22.5k | if (stream->stream_id == 1 && conn->_http1_req_input != NULL) |
77 | 0 | h2o_buffer_dispose(&conn->_http1_req_input); |
78 | 22.5k | free(stream); |
79 | 22.5k | } |
80 | | |
81 | | void h2o_http2_stream_reset(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
82 | 10.5k | { |
83 | 10.5k | switch (stream->state) { |
84 | 7.80k | case H2O_HTTP2_STREAM_STATE_IDLE: |
85 | 10.4k | case H2O_HTTP2_STREAM_STATE_RECV_HEADERS: |
86 | 10.4k | case H2O_HTTP2_STREAM_STATE_RECV_BODY: |
87 | 10.4k | case H2O_HTTP2_STREAM_STATE_REQ_PENDING: |
88 | 10.4k | h2o_http2_stream_close(conn, stream); |
89 | 10.4k | break; |
90 | 4 | case H2O_HTTP2_STREAM_STATE_SEND_HEADERS: |
91 | 21 | case H2O_HTTP2_STREAM_STATE_SEND_BODY: |
92 | 62 | case H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL: |
93 | 62 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
94 | | /* continues */ |
95 | 90 | case H2O_HTTP2_STREAM_STATE_END_STREAM: |
96 | | /* clear all the queued bufs, and close the connection in the callback */ |
97 | 90 | stream->_data.size = 0; |
98 | 90 | if (h2o_linklist_is_linked(&stream->_link)) { |
99 | | /* will be closed in the callback */ |
100 | 47 | } else { |
101 | 47 | h2o_http2_stream_close(conn, stream); |
102 | 47 | } |
103 | 90 | break; |
104 | 10.5k | } |
105 | 10.5k | } |
106 | | |
107 | | static size_t calc_max_payload_size(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
108 | 2.68k | { |
109 | 2.68k | ssize_t conn_max, stream_max; |
110 | | |
111 | 2.68k | if ((conn_max = h2o_http2_conn_get_buffer_window(conn)) <= 0) |
112 | 0 | return 0; |
113 | 2.68k | if ((stream_max = h2o_http2_window_get_avail(&stream->output_window)) <= 0) |
114 | 0 | return 0; |
115 | 2.68k | return sz_min(sz_min(conn_max, stream_max), conn->peer_settings.max_frame_size); |
116 | 2.68k | } |
117 | | |
118 | | static void commit_data_header(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream, h2o_buffer_t **outbuf, size_t length, |
119 | | h2o_send_state_t send_state) |
120 | 2.68k | { |
121 | 2.68k | assert(outbuf != NULL); |
122 | | /* send a DATA frame if there's data or the END_STREAM flag to send */ |
123 | 2.68k | int is_end_stream = send_state == H2O_SEND_STATE_FINAL && !stream->req.send_server_timing && stream->req.res.trailers.size == 0; |
124 | 2.68k | if (length != 0 || is_end_stream) { |
125 | 2.68k | h2o_http2_encode_frame_header((void *)((*outbuf)->bytes + (*outbuf)->size), length, H2O_HTTP2_FRAME_TYPE_DATA, |
126 | 2.68k | is_end_stream ? H2O_HTTP2_FRAME_FLAG_END_STREAM : 0, stream->stream_id); |
127 | 2.68k | h2o_http2_window_consume_window(&conn->_write.window, length); |
128 | 2.68k | h2o_http2_window_consume_window(&stream->output_window, length); |
129 | 2.68k | (*outbuf)->size += length + H2O_HTTP2_FRAME_HEADER_SIZE; |
130 | 2.68k | stream->req.bytes_sent += length; |
131 | 2.68k | } |
132 | | /* send a RST_STREAM if there's an error */ |
133 | 2.68k | if (send_state == H2O_SEND_STATE_ERROR) { |
134 | 5 | stream->reset_by_peer_action = 1; |
135 | 5 | h2o_http2_encode_rst_stream_frame( |
136 | 5 | outbuf, stream->stream_id, -(stream->req.upstream_refused ? H2O_HTTP2_ERROR_REFUSED_STREAM : H2O_HTTP2_ERROR_PROTOCOL)); |
137 | 5 | } |
138 | 2.68k | } |
139 | | |
140 | | static h2o_sendvec_t *send_data(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream, h2o_sendvec_t *bufs, size_t bufcnt, |
141 | | h2o_send_state_t send_state) |
142 | 2.68k | { |
143 | 2.68k | h2o_iovec_t dst; |
144 | 2.68k | size_t max_payload_size; |
145 | | |
146 | 2.68k | if ((max_payload_size = calc_max_payload_size(conn, stream)) == 0) |
147 | 0 | goto Exit; |
148 | | |
149 | | /* reserve buffer and point dst to the payload */ |
150 | 2.68k | dst.base = |
151 | 2.68k | h2o_buffer_reserve(&conn->_write.buf, H2O_HTTP2_FRAME_HEADER_SIZE + max_payload_size).base + H2O_HTTP2_FRAME_HEADER_SIZE; |
152 | 2.68k | dst.len = max_payload_size; |
153 | | |
154 | | /* emit data */ |
155 | 2.68k | while (bufcnt != 0) { |
156 | 2.35k | size_t fill_size = sz_min(dst.len, bufs->len); |
157 | 2.35k | if (!(*bufs->callbacks->read_)(bufs, dst.base, fill_size)) { |
158 | 0 | h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_INTERNAL); |
159 | 0 | return NULL; |
160 | 0 | } |
161 | 2.35k | dst.base += fill_size; |
162 | 2.35k | dst.len -= fill_size; |
163 | 2.35k | if (bufs->len == 0) { |
164 | 2.07k | ++bufs; |
165 | 2.07k | --bufcnt; |
166 | 2.07k | if (bufcnt == 0) |
167 | 2.07k | break; |
168 | 2.07k | } |
169 | 276 | if (dst.len == 0) |
170 | 276 | break; |
171 | 276 | } |
172 | | |
173 | | /* commit the DATA frame if we have actually emitted payload */ |
174 | 2.68k | if (dst.len != max_payload_size || !h2o_send_state_is_in_progress(send_state)) { |
175 | 2.68k | size_t payload_len = max_payload_size - dst.len; |
176 | 2.68k | if (bufcnt != 0) { |
177 | 276 | send_state = H2O_SEND_STATE_IN_PROGRESS; |
178 | 276 | } |
179 | 2.68k | commit_data_header(conn, stream, &conn->_write.buf, payload_len, send_state); |
180 | 2.68k | } |
181 | | |
182 | 2.68k | Exit: |
183 | 2.68k | return bufs; |
184 | 2.68k | } |
185 | | |
186 | | static int is_blocking_asset(h2o_req_t *req) |
187 | 0 | { |
188 | 0 | if (req->res.mime_attr == NULL) |
189 | 0 | h2o_req_fill_mime_attributes(req); |
190 | 0 | return req->res.mime_attr->priority == H2O_MIME_ATTRIBUTE_PRIORITY_HIGHEST; |
191 | 0 | } |
192 | | |
193 | | static void request_write_and_close(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
194 | 2 | { |
195 | 2 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
196 | 2 | h2o_http2_scheduler_deactivate(&stream->_scheduler); |
197 | 2 | if (!h2o_linklist_is_linked(&stream->_link)) |
198 | 2 | h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_link); |
199 | 2 | h2o_http2_conn_request_write(conn); |
200 | 2 | } |
201 | | |
202 | | static void send_refused_stream(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
203 | 0 | { |
204 | 0 | stream->reset_by_peer_action = 1; |
205 | 0 | h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_REFUSED_STREAM); |
206 | 0 | request_write_and_close(conn, stream); |
207 | 0 | } |
208 | | |
209 | | static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream, int is_end_stream) |
210 | 3.43k | { |
211 | 3.43k | stream->req.timestamps.response_start_at = h2o_gettimeofday(conn->super.ctx->loop); |
212 | | |
213 | | /* cancel push with an error response */ |
214 | 3.43k | if (h2o_http2_stream_is_push(stream->stream_id)) { |
215 | 0 | if (400 <= stream->req.res.status) |
216 | 0 | goto CancelPush; |
217 | 0 | if (stream->cache_digests != NULL) { |
218 | 0 | ssize_t etag_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_ETAG, -1); |
219 | 0 | if (etag_index != -1) { |
220 | 0 | h2o_iovec_t url = h2o_concat(&stream->req.pool, stream->req.input.scheme->name, h2o_iovec_init(H2O_STRLIT("://")), |
221 | 0 | stream->req.input.authority, stream->req.input.path); |
222 | 0 | h2o_iovec_t *etag = &stream->req.headers.entries[etag_index].value; |
223 | 0 | if (h2o_cache_digests_lookup_by_url_and_etag(stream->cache_digests, url.base, url.len, etag->base, etag->len) == |
224 | 0 | H2O_CACHE_DIGESTS_STATE_FRESH) |
225 | 0 | goto CancelPush; |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | | /* reset casper cookie in case cache-digests exist */ |
231 | 3.43k | if (stream->cache_digests != NULL && stream->req.hostconf->http2.casper.capacity_bits != 0) { |
232 | 0 | h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, NULL, |
233 | 0 | H2O_STRLIT("h2o_casper=; Path=/; Expires=Sat, 01 Jan 2000 00:00:00 GMT")); |
234 | 0 | } |
235 | | |
236 | | /* CASPER */ |
237 | 3.43k | if (conn->casper != NULL) { |
238 | | /* update casper if necessary */ |
239 | 0 | if (stream->req.hostconf->http2.casper.track_all_types || is_blocking_asset(&stream->req)) { |
240 | 0 | if (h2o_http2_casper_lookup(conn->casper, stream->req.path.base, stream->req.path.len, 1)) { |
241 | | /* cancel if the pushed resource is already marked as cached */ |
242 | 0 | if (h2o_http2_stream_is_push(stream->stream_id)) |
243 | 0 | goto CancelPush; |
244 | 0 | } |
245 | 0 | } |
246 | 0 | if (stream->cache_digests != NULL) |
247 | 0 | goto SkipCookie; |
248 | | /* browsers might ignore push responses, or they may process the responses in a different order than they were pushed. |
249 | | * Therefore H2O tries to include casper cookie only in the last stream that may be received by the client, or when the |
250 | | * value become stable; see also: https://github.com/h2o/h2o/issues/421 |
251 | | */ |
252 | 0 | if (h2o_http2_stream_is_push(stream->stream_id)) { |
253 | 0 | if (!(conn->num_streams.pull.open == 0 && (conn->num_streams.push.half_closed - conn->num_streams.push.send_body) == 1)) |
254 | 0 | goto SkipCookie; |
255 | 0 | } else { |
256 | 0 | if (conn->num_streams.push.half_closed - conn->num_streams.push.send_body != 0) |
257 | 0 | goto SkipCookie; |
258 | 0 | } |
259 | 0 | h2o_iovec_t cookie = h2o_http2_casper_get_cookie(conn->casper); |
260 | 0 | h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, NULL, cookie.base, cookie.len); |
261 | 0 | SkipCookie:; |
262 | 0 | } |
263 | | |
264 | 3.43k | if (h2o_http2_stream_is_push(stream->stream_id)) { |
265 | | /* for push, send the push promise */ |
266 | 0 | if (!stream->push.promise_sent) |
267 | 0 | h2o_http2_stream_send_push_promise(conn, stream); |
268 | | /* send ASAP if it is a blocking asset (even in case of Firefox we can't wait 1RTT for it to reprioritize the asset) */ |
269 | 0 | if (is_blocking_asset(&stream->req)) |
270 | 0 | h2o_http2_scheduler_rebind(&stream->_scheduler, &conn->scheduler, 257, 0); |
271 | 3.43k | } else { |
272 | | /* Handle absolute priority header */ |
273 | 3.43k | ssize_t absprio_cursor = h2o_find_header(&stream->req.res.headers, H2O_TOKEN_PRIORITY, -1); |
274 | 3.43k | if (absprio_cursor != -1 && conn->is_chromium_dependency_tree) { |
275 | | /* Found absolute priority header in the response header */ |
276 | 0 | h2o_absprio_t prio = h2o_absprio_default; |
277 | 0 | h2o_iovec_t *header_value = &stream->req.res.headers.entries[absprio_cursor].value; |
278 | 0 | h2o_absprio_parse_priority(header_value->base, header_value->len, &prio); |
279 | 0 | uint16_t new_weight = h2o_absprio_urgency_to_chromium_weight(prio.urgency); |
280 | 0 | h2o_http2_scheduler_node_t *new_parent = h2o_http2_scheduler_find_parent_by_weight(&conn->scheduler, new_weight); |
281 | 0 | if (new_parent == &stream->_scheduler.node) { |
282 | | /* find_new_parent might return `stream` itself. In this case re-specify the current |
283 | | * parent as a new parent */ |
284 | 0 | new_parent = h2o_http2_scheduler_get_parent(&stream->_scheduler); |
285 | 0 | } |
286 | 0 | if (new_parent != h2o_http2_scheduler_get_parent(&stream->_scheduler) || |
287 | 0 | new_weight != h2o_http2_scheduler_get_weight(&stream->_scheduler)) { |
288 | | /* Reprioritize the stream based on priority header information */ |
289 | | |
290 | | /* First, preserve the current (client-given) priority information so that subsequent |
291 | | * streams from the client can correctly refer to the original priority. */ |
292 | 0 | h2o_http2_conn_preserve_stream_scheduler(conn, stream); |
293 | | /* Open a new scheduler for the modified priority information for this stream */ |
294 | 0 | h2o_http2_scheduler_open(&stream->_scheduler, new_parent, new_weight, 1); |
295 | 0 | } |
296 | 3.43k | } else if (conn->num_streams.priority.open == 0 && stream->req.hostconf->http2.reprioritize_blocking_assets && |
297 | 3.43k | h2o_http2_scheduler_get_parent(&stream->_scheduler) == &conn->scheduler && is_blocking_asset(&stream->req)) { |
298 | | /* raise the priority of asset files that block rendering to highest if the user-agent is _not_ using dependency-based |
299 | | * prioritization (e.g. that of Firefox) |
300 | | */ |
301 | 0 | h2o_http2_scheduler_rebind(&stream->_scheduler, &conn->scheduler, 257, 0); |
302 | 0 | } |
303 | 3.43k | } |
304 | | |
305 | | /* send HEADERS, as well as start sending body */ |
306 | 3.43k | if (h2o_http2_stream_is_push(stream->stream_id)) |
307 | 0 | h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, NULL, |
308 | 0 | H2O_STRLIT("pushed")); |
309 | 3.43k | if (stream->req.send_server_timing) |
310 | 0 | h2o_add_server_timing_header(&stream->req, 1); |
311 | 3.43k | stream->req.header_bytes_sent += h2o_hpack_flatten_response( |
312 | 3.43k | &conn->_write.buf, &conn->_output_header_table, conn->peer_settings.header_table_size, stream->stream_id, |
313 | 3.43k | conn->peer_settings.max_frame_size, stream->req.res.status, stream->req.res.headers.entries, stream->req.res.headers.size, |
314 | 3.43k | &conn->super.ctx->globalconf->server_name, stream->req.res.content_length, is_end_stream); |
315 | 3.43k | h2o_http2_conn_request_write(conn); |
316 | 3.43k | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY); |
317 | | |
318 | 3.43k | return 0; |
319 | | |
320 | 0 | CancelPush: |
321 | 0 | h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, NULL, |
322 | 0 | H2O_STRLIT("cancelled")); |
323 | 0 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
324 | 0 | h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_link); |
325 | 0 | if (stream->push.promise_sent) { |
326 | 0 | stream->reset_by_peer_action = 1; |
327 | 0 | h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_INTERNAL); |
328 | 0 | h2o_http2_conn_request_write(conn); |
329 | 0 | } |
330 | 0 | return -1; |
331 | 3.43k | } |
332 | | |
333 | | void finalostream_send(h2o_ostream_t *self, h2o_req_t *req, h2o_sendvec_t *bufs, size_t bufcnt, h2o_send_state_t state) |
334 | 3.82k | { |
335 | 3.82k | h2o_http2_stream_t *stream = H2O_STRUCT_FROM_MEMBER(h2o_http2_stream_t, _ostr_final, self); |
336 | 3.82k | h2o_http2_conn_t *conn = (h2o_http2_conn_t *)req->conn; |
337 | | |
338 | 3.82k | assert(h2o_send_state_is_in_progress(stream->send_state)); |
339 | 3.82k | assert(stream->_data.size == 0); |
340 | | |
341 | 3.82k | if (stream->blocked_by_server) |
342 | 1.91k | h2o_http2_stream_set_blocked_by_server(conn, stream, 0); |
343 | | |
344 | 3.82k | if (stream->req.res.status == 425 && stream->req.reprocess_if_too_early) { |
345 | 0 | assert(stream->state <= H2O_HTTP2_STREAM_STATE_SEND_HEADERS); |
346 | 0 | h2o_http2_conn_register_for_replay(conn, stream); |
347 | 0 | return; |
348 | 0 | } |
349 | | |
350 | 3.82k | stream->send_state = state; |
351 | 3.82k | int is_end_stream = state == H2O_SEND_STATE_FINAL && bufcnt == 0, |
352 | 3.82k | empty_payload_allowed = stream->state < H2O_HTTP2_STREAM_STATE_SEND_BODY || state != H2O_SEND_STATE_IN_PROGRESS; |
353 | | |
354 | | /* send headers */ |
355 | 3.82k | switch (stream->state) { |
356 | 199 | case H2O_HTTP2_STREAM_STATE_RECV_BODY: |
357 | 199 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_REQ_PENDING); |
358 | | /* fallthru */ |
359 | 199 | case H2O_HTTP2_STREAM_STATE_REQ_PENDING: |
360 | 199 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_HEADERS); |
361 | | /* fallthru */ |
362 | 3.43k | case H2O_HTTP2_STREAM_STATE_SEND_HEADERS: |
363 | 3.43k | if (stream->req.upstream_refused) { |
364 | 0 | send_refused_stream(conn, stream); |
365 | 0 | return; |
366 | 0 | } |
367 | 3.43k | h2o_probe_log_response(&stream->req, stream->stream_id); |
368 | 3.43k | if (send_headers(conn, stream, is_end_stream) != 0) |
369 | 0 | return; |
370 | 3.43k | if (is_end_stream) { |
371 | 2 | request_write_and_close(conn, stream); |
372 | 2 | return; |
373 | 2 | } |
374 | | /* fallthru */ |
375 | 3.80k | case H2O_HTTP2_STREAM_STATE_SEND_BODY: |
376 | 3.80k | if (state != H2O_SEND_STATE_IN_PROGRESS) { |
377 | 3.35k | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL); |
378 | 3.35k | } |
379 | 3.80k | break; |
380 | 12 | case H2O_HTTP2_STREAM_STATE_END_STREAM: |
381 | | /* might get set by h2o_http2_stream_reset */ |
382 | 12 | return; |
383 | 0 | default: |
384 | 0 | assert(!"cannot be in a receiving state"); |
385 | 3.82k | } |
386 | | |
387 | | /* save the contents in queue */ |
388 | 3.80k | h2o_vector_reserve(&req->pool, &stream->_data, bufcnt); |
389 | 7.24k | for (size_t i = 0; i < bufcnt; ++i) { |
390 | 3.43k | if (bufs[i].len == 0) |
391 | 0 | continue; |
392 | 3.43k | stream->_data.entries[stream->_data.size++] = bufs[i]; |
393 | 3.43k | } |
394 | 3.80k | assert(empty_payload_allowed || stream->_data.size != 0 || !"h2o_data must only be called when there is progress"); |
395 | | |
396 | 3.80k | h2o_http2_conn_register_for_proceed_callback(conn, stream); |
397 | 3.80k | } |
398 | | |
399 | | static void finalostream_send_informational(h2o_ostream_t *self, h2o_req_t *req) |
400 | 0 | { |
401 | 0 | h2o_http2_stream_t *stream = H2O_STRUCT_FROM_MEMBER(h2o_http2_stream_t, _ostr_final, self); |
402 | 0 | h2o_http2_conn_t *conn = (h2o_http2_conn_t *)req->conn; |
403 | |
|
404 | 0 | stream->req.header_bytes_sent += h2o_hpack_flatten_response( |
405 | 0 | &conn->_write.buf, &conn->_output_header_table, conn->peer_settings.header_table_size, stream->stream_id, |
406 | 0 | conn->peer_settings.max_frame_size, req->res.status, req->res.headers.entries, req->res.headers.size, NULL, SIZE_MAX, 0); |
407 | 0 | h2o_http2_conn_request_write(conn); |
408 | 0 | } |
409 | | |
410 | | void h2o_http2_stream_send_pending_data(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
411 | 2.87k | { |
412 | 2.87k | if (h2o_http2_window_get_avail(&stream->output_window) <= 0) |
413 | 188 | return; |
414 | | |
415 | 2.68k | h2o_sendvec_t *nextbuf = send_data(conn, stream, stream->_data.entries, stream->_data.size, stream->send_state); |
416 | 2.68k | if (nextbuf == NULL && stream->_data.entries != NULL) { |
417 | | /* error */ |
418 | 0 | stream->_data.size = 0; |
419 | 0 | stream->send_state = H2O_SEND_STATE_ERROR; |
420 | 0 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
421 | 2.68k | } else if (nextbuf == stream->_data.entries + stream->_data.size) { |
422 | | /* sent all data */ |
423 | 2.41k | stream->_data.size = 0; |
424 | 2.41k | if (stream->state == H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL) |
425 | 2.02k | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
426 | 2.41k | } else if (nextbuf != stream->_data.entries) { |
427 | | /* adjust the buffer */ |
428 | 0 | size_t newsize = stream->_data.size - (nextbuf - stream->_data.entries); |
429 | 0 | memmove(stream->_data.entries, nextbuf, sizeof(stream->_data.entries[0]) * newsize); |
430 | 0 | stream->_data.size = newsize; |
431 | 0 | } |
432 | | |
433 | | /* if the stream entered error state, suppress sending trailers */ |
434 | 2.68k | if (stream->send_state == H2O_SEND_STATE_ERROR) |
435 | 5 | stream->req.send_server_timing = 0; |
436 | 2.68k | } |
437 | | |
438 | | void h2o_http2_stream_proceed(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
439 | 2.45k | { |
440 | 2.45k | if (stream->state == H2O_HTTP2_STREAM_STATE_END_STREAM) { |
441 | 2.07k | switch (stream->req_body.state) { |
442 | 1.77k | case H2O_HTTP2_REQ_BODY_NONE: |
443 | 1.93k | case H2O_HTTP2_REQ_BODY_CLOSE_DELIVERED: |
444 | 1.93k | h2o_http2_stream_close(conn, stream); |
445 | 1.93k | break; |
446 | 136 | default: |
447 | 136 | break; /* the stream will be closed when the read side is done */ |
448 | 2.07k | } |
449 | 2.07k | } else { |
450 | 382 | if (!stream->blocked_by_server) |
451 | 365 | h2o_http2_stream_set_blocked_by_server(conn, stream, 1); |
452 | 382 | h2o_proceed_response(&stream->req); |
453 | 382 | } |
454 | 2.45k | } |