/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 | 17.8k | { |
33 | 17.8k | return x < y ? x : y; |
34 | 17.8k | } |
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 | 44.8k | { |
39 | 44.8k | h2o_http2_stream_t *stream = h2o_mem_alloc(sizeof(*stream)); |
40 | | |
41 | | /* init properties (other than req) */ |
42 | 44.8k | memset(stream, 0, offsetof(h2o_http2_stream_t, req)); |
43 | 44.8k | stream->stream_id = stream_id; |
44 | 44.8k | stream->_ostr_final.do_send = finalostream_send; |
45 | 44.8k | stream->_ostr_final.send_informational = |
46 | 44.8k | conn->super.ctx->globalconf->send_informational_mode == H2O_SEND_INFORMATIONAL_MODE_NONE ? NULL |
47 | 44.8k | : finalostream_send_informational; |
48 | 44.8k | stream->state = H2O_HTTP2_STREAM_STATE_IDLE; |
49 | 44.8k | h2o_http2_window_init(&stream->output_window, conn->peer_settings.initial_window_size); |
50 | 44.8k | h2o_http2_window_init(&stream->input_window.window, H2O_HTTP2_SETTINGS_HOST_STREAM_INITIAL_WINDOW_SIZE); |
51 | 44.8k | stream->received_priority = *received_priority; |
52 | | |
53 | | /* init request */ |
54 | 44.8k | h2o_init_request(&stream->req, &conn->super, src_req); |
55 | 44.8k | stream->req.version = 0x200; |
56 | 44.8k | if (src_req != NULL) |
57 | 104 | memset(&stream->req.upgrade, 0, sizeof(stream->req.upgrade)); |
58 | 44.8k | stream->req._ostr_top = &stream->_ostr_final; |
59 | | |
60 | 44.8k | h2o_http2_conn_register_stream(conn, stream); |
61 | | |
62 | 44.8k | ++conn->num_streams.priority.open; |
63 | 44.8k | stream->_num_streams_slot = &conn->num_streams.priority; |
64 | | |
65 | 44.8k | return stream; |
66 | 44.8k | } |
67 | | |
68 | | void h2o_http2_stream_close(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
69 | 44.8k | { |
70 | 44.8k | h2o_http2_conn_unregister_stream(conn, stream); |
71 | 44.8k | if (stream->cache_digests != NULL) |
72 | 320 | h2o_cache_digests_destroy(stream->cache_digests); |
73 | 44.8k | if (stream->req_body.buf != NULL) |
74 | 7.21k | h2o_buffer_dispose(&stream->req_body.buf); |
75 | 44.8k | h2o_dispose_request(&stream->req); |
76 | 44.8k | if (stream->stream_id == 1 && conn->_http1_req_input != NULL) |
77 | 104 | h2o_buffer_dispose(&conn->_http1_req_input); |
78 | 44.8k | free(stream); |
79 | 44.8k | } |
80 | | |
81 | | void h2o_http2_stream_reset(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
82 | 21.4k | { |
83 | 21.4k | switch (stream->state) { |
84 | 16.0k | case H2O_HTTP2_STREAM_STATE_IDLE: |
85 | 21.1k | case H2O_HTTP2_STREAM_STATE_RECV_HEADERS: |
86 | 21.2k | case H2O_HTTP2_STREAM_STATE_RECV_BODY: |
87 | 21.2k | case H2O_HTTP2_STREAM_STATE_REQ_PENDING: |
88 | 21.2k | h2o_http2_stream_close(conn, stream); |
89 | 21.2k | break; |
90 | 7 | case H2O_HTTP2_STREAM_STATE_SEND_HEADERS: |
91 | 88 | case H2O_HTTP2_STREAM_STATE_SEND_BODY: |
92 | 147 | case H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL: |
93 | 147 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
94 | | /* continues */ |
95 | 178 | case H2O_HTTP2_STREAM_STATE_END_STREAM: |
96 | | /* clear all the queued bufs, and close the connection in the callback */ |
97 | 178 | stream->_data.size = 0; |
98 | 178 | if (h2o_linklist_is_linked(&stream->_link)) { |
99 | | /* will be closed in the callback */ |
100 | 108 | } else { |
101 | 70 | h2o_http2_stream_close(conn, stream); |
102 | 70 | } |
103 | 178 | break; |
104 | 21.4k | } |
105 | 21.4k | } |
106 | | |
107 | | static size_t calc_max_payload_size(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
108 | 6.06k | { |
109 | 6.06k | ssize_t conn_max, stream_max; |
110 | | |
111 | 6.06k | if ((conn_max = h2o_http2_conn_get_buffer_window(conn)) <= 0) |
112 | 0 | return 0; |
113 | 6.06k | if ((stream_max = h2o_http2_window_get_avail(&stream->output_window)) <= 0) |
114 | 0 | return 0; |
115 | 6.06k | return sz_min(sz_min(conn_max, stream_max), conn->peer_settings.max_frame_size); |
116 | 6.06k | } |
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 | 6.06k | { |
121 | 6.06k | assert(outbuf != NULL); |
122 | | /* send a DATA frame if there's data or the END_STREAM flag to send */ |
123 | 6.06k | int is_end_stream = send_state == H2O_SEND_STATE_FINAL && !stream->req.send_server_timing && stream->req.res.trailers.size == 0; |
124 | 6.06k | if (length != 0 || is_end_stream) { |
125 | 6.04k | h2o_http2_encode_frame_header((void *)((*outbuf)->bytes + (*outbuf)->size), length, H2O_HTTP2_FRAME_TYPE_DATA, |
126 | 6.04k | is_end_stream ? H2O_HTTP2_FRAME_FLAG_END_STREAM : 0, stream->stream_id); |
127 | 6.04k | h2o_http2_window_consume_window(&conn->_write.window, length); |
128 | 6.04k | h2o_http2_window_consume_window(&stream->output_window, length); |
129 | 6.04k | (*outbuf)->size += length + H2O_HTTP2_FRAME_HEADER_SIZE; |
130 | 6.04k | stream->req.bytes_sent += length; |
131 | 6.04k | } |
132 | | /* send a RST_STREAM if there's an error */ |
133 | 6.06k | if (send_state == H2O_SEND_STATE_ERROR) { |
134 | 24 | h2o_http2_encode_rst_stream_frame( |
135 | 24 | outbuf, stream->stream_id, -(stream->req.upstream_refused ? H2O_HTTP2_ERROR_REFUSED_STREAM : H2O_HTTP2_ERROR_PROTOCOL)); |
136 | 24 | } |
137 | 6.06k | } |
138 | | |
139 | | static h2o_sendvec_t *send_data(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream, h2o_sendvec_t *bufs, size_t bufcnt, |
140 | | h2o_send_state_t send_state) |
141 | 6.06k | { |
142 | 6.06k | h2o_iovec_t dst; |
143 | 6.06k | size_t max_payload_size; |
144 | | |
145 | 6.06k | if ((max_payload_size = calc_max_payload_size(conn, stream)) == 0) |
146 | 0 | goto Exit; |
147 | | |
148 | | /* reserve buffer and point dst to the payload */ |
149 | 6.06k | dst.base = |
150 | 6.06k | h2o_buffer_reserve(&conn->_write.buf, H2O_HTTP2_FRAME_HEADER_SIZE + max_payload_size).base + H2O_HTTP2_FRAME_HEADER_SIZE; |
151 | 6.06k | dst.len = max_payload_size; |
152 | | |
153 | | /* emit data */ |
154 | 6.06k | while (bufcnt != 0) { |
155 | 5.74k | size_t fill_size = sz_min(dst.len, bufs->len); |
156 | 5.74k | if (!(*bufs->callbacks->read_)(bufs, dst.base, fill_size)) { |
157 | 0 | h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_INTERNAL); |
158 | 0 | return NULL; |
159 | 0 | } |
160 | 5.74k | dst.base += fill_size; |
161 | 5.74k | dst.len -= fill_size; |
162 | 5.74k | if (bufs->len == 0) { |
163 | 4.98k | ++bufs; |
164 | 4.98k | --bufcnt; |
165 | 4.98k | if (bufcnt == 0) |
166 | 4.98k | break; |
167 | 4.98k | } |
168 | 760 | if (dst.len == 0) |
169 | 760 | break; |
170 | 760 | } |
171 | | |
172 | | /* commit the DATA frame if we have actually emitted payload */ |
173 | 6.06k | if (dst.len != max_payload_size || !h2o_send_state_is_in_progress(send_state)) { |
174 | 6.06k | size_t payload_len = max_payload_size - dst.len; |
175 | 6.06k | if (bufcnt != 0) { |
176 | 760 | send_state = H2O_SEND_STATE_IN_PROGRESS; |
177 | 760 | } |
178 | 6.06k | commit_data_header(conn, stream, &conn->_write.buf, payload_len, send_state); |
179 | 6.06k | } |
180 | | |
181 | 6.06k | Exit: |
182 | 6.06k | return bufs; |
183 | 6.06k | } |
184 | | |
185 | | static int is_blocking_asset(h2o_req_t *req) |
186 | 0 | { |
187 | 0 | if (req->res.mime_attr == NULL) |
188 | 0 | h2o_req_fill_mime_attributes(req); |
189 | 0 | return req->res.mime_attr->priority == H2O_MIME_ATTRIBUTE_PRIORITY_HIGHEST; |
190 | 0 | } |
191 | | |
192 | | static void request_write_and_close(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
193 | 9 | { |
194 | 9 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
195 | 9 | h2o_http2_scheduler_deactivate(&stream->_scheduler); |
196 | 9 | if (!h2o_linklist_is_linked(&stream->_link)) |
197 | 9 | h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_link); |
198 | 9 | h2o_http2_conn_request_write(conn); |
199 | 9 | } |
200 | | |
201 | | static void send_refused_stream(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
202 | 0 | { |
203 | 0 | h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_REFUSED_STREAM); |
204 | 0 | request_write_and_close(conn, stream); |
205 | 0 | } |
206 | | |
207 | | static int send_headers(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream, int is_end_stream) |
208 | 8.71k | { |
209 | 8.71k | stream->req.timestamps.response_start_at = h2o_gettimeofday(conn->super.ctx->loop); |
210 | | |
211 | | /* cancel push with an error response */ |
212 | 8.71k | if (h2o_http2_stream_is_push(stream->stream_id)) { |
213 | 0 | if (400 <= stream->req.res.status) |
214 | 0 | goto CancelPush; |
215 | 0 | if (stream->cache_digests != NULL) { |
216 | 0 | ssize_t etag_index = h2o_find_header(&stream->req.headers, H2O_TOKEN_ETAG, -1); |
217 | 0 | if (etag_index != -1) { |
218 | 0 | h2o_iovec_t url = h2o_concat(&stream->req.pool, stream->req.input.scheme->name, h2o_iovec_init(H2O_STRLIT("://")), |
219 | 0 | stream->req.input.authority, stream->req.input.path); |
220 | 0 | h2o_iovec_t *etag = &stream->req.headers.entries[etag_index].value; |
221 | 0 | if (h2o_cache_digests_lookup_by_url_and_etag(stream->cache_digests, url.base, url.len, etag->base, etag->len) == |
222 | 0 | H2O_CACHE_DIGESTS_STATE_FRESH) |
223 | 0 | goto CancelPush; |
224 | 0 | } |
225 | 0 | } |
226 | 0 | } |
227 | | |
228 | | /* reset casper cookie in case cache-digests exist */ |
229 | 8.71k | if (stream->cache_digests != NULL && stream->req.hostconf->http2.casper.capacity_bits != 0) { |
230 | 0 | h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, NULL, |
231 | 0 | H2O_STRLIT("h2o_casper=; Path=/; Expires=Sat, 01 Jan 2000 00:00:00 GMT")); |
232 | 0 | } |
233 | | |
234 | | /* CASPER */ |
235 | 8.71k | if (conn->casper != NULL) { |
236 | | /* update casper if necessary */ |
237 | 0 | if (stream->req.hostconf->http2.casper.track_all_types || is_blocking_asset(&stream->req)) { |
238 | 0 | if (h2o_http2_casper_lookup(conn->casper, stream->req.path.base, stream->req.path.len, 1)) { |
239 | | /* cancel if the pushed resource is already marked as cached */ |
240 | 0 | if (h2o_http2_stream_is_push(stream->stream_id)) |
241 | 0 | goto CancelPush; |
242 | 0 | } |
243 | 0 | } |
244 | 0 | if (stream->cache_digests != NULL) |
245 | 0 | goto SkipCookie; |
246 | | /* browsers might ignore push responses, or they may process the responses in a different order than they were pushed. |
247 | | * Therefore H2O tries to include casper cookie only in the last stream that may be received by the client, or when the |
248 | | * value become stable; see also: https://github.com/h2o/h2o/issues/421 |
249 | | */ |
250 | 0 | if (h2o_http2_stream_is_push(stream->stream_id)) { |
251 | 0 | if (!(conn->num_streams.pull.open == 0 && (conn->num_streams.push.half_closed - conn->num_streams.push.send_body) == 1)) |
252 | 0 | goto SkipCookie; |
253 | 0 | } else { |
254 | 0 | if (conn->num_streams.push.half_closed - conn->num_streams.push.send_body != 0) |
255 | 0 | goto SkipCookie; |
256 | 0 | } |
257 | 0 | h2o_iovec_t cookie = h2o_http2_casper_get_cookie(conn->casper); |
258 | 0 | h2o_add_header(&stream->req.pool, &stream->req.res.headers, H2O_TOKEN_SET_COOKIE, NULL, cookie.base, cookie.len); |
259 | 0 | SkipCookie:; |
260 | 0 | } |
261 | | |
262 | 8.71k | if (h2o_http2_stream_is_push(stream->stream_id)) { |
263 | | /* for push, send the push promise */ |
264 | 0 | if (!stream->push.promise_sent) |
265 | 0 | h2o_http2_stream_send_push_promise(conn, stream); |
266 | | /* send ASAP if it is a blocking asset (even in case of Firefox we can't wait 1RTT for it to reprioritize the asset) */ |
267 | 0 | if (is_blocking_asset(&stream->req)) |
268 | 0 | h2o_http2_scheduler_rebind(&stream->_scheduler, &conn->scheduler, 257, 0); |
269 | 8.71k | } else { |
270 | | /* Handle absolute priority header */ |
271 | 8.71k | ssize_t absprio_cursor = h2o_find_header(&stream->req.res.headers, H2O_TOKEN_PRIORITY, -1); |
272 | 8.71k | if (absprio_cursor != -1 && conn->is_chromium_dependency_tree) { |
273 | | /* Found absolute priority header in the response header */ |
274 | 0 | h2o_absprio_t prio = h2o_absprio_default; |
275 | 0 | h2o_iovec_t *header_value = &stream->req.res.headers.entries[absprio_cursor].value; |
276 | 0 | h2o_absprio_parse_priority(header_value->base, header_value->len, &prio); |
277 | 0 | uint16_t new_weight = h2o_absprio_urgency_to_chromium_weight(prio.urgency); |
278 | 0 | h2o_http2_scheduler_node_t *new_parent = h2o_http2_scheduler_find_parent_by_weight(&conn->scheduler, new_weight); |
279 | 0 | if (new_parent == &stream->_scheduler.node) { |
280 | | /* find_new_parent might return `stream` itself. In this case re-specify the current |
281 | | * parent as a new parent */ |
282 | 0 | new_parent = h2o_http2_scheduler_get_parent(&stream->_scheduler); |
283 | 0 | } |
284 | 0 | if (new_parent != h2o_http2_scheduler_get_parent(&stream->_scheduler) || |
285 | 0 | new_weight != h2o_http2_scheduler_get_weight(&stream->_scheduler)) { |
286 | | /* Reprioritize the stream based on priority header information */ |
287 | | |
288 | | /* First, preserve the current (client-given) priority information so that subsequent |
289 | | * streams from the client can correctly refer to the original priority. */ |
290 | 0 | h2o_http2_conn_preserve_stream_scheduler(conn, stream); |
291 | | /* Open a new scheduler for the modified priority information for this stream */ |
292 | 0 | h2o_http2_scheduler_open(&stream->_scheduler, new_parent, new_weight, 1); |
293 | 0 | } |
294 | 8.71k | } else if (conn->num_streams.priority.open == 0 && stream->req.hostconf->http2.reprioritize_blocking_assets && |
295 | 8.71k | h2o_http2_scheduler_get_parent(&stream->_scheduler) == &conn->scheduler && is_blocking_asset(&stream->req)) { |
296 | | /* raise the priority of asset files that block rendering to highest if the user-agent is _not_ using dependency-based |
297 | | * prioritization (e.g. that of Firefox) |
298 | | */ |
299 | 0 | h2o_http2_scheduler_rebind(&stream->_scheduler, &conn->scheduler, 257, 0); |
300 | 0 | } |
301 | 8.71k | } |
302 | | |
303 | | /* send HEADERS, as well as start sending body */ |
304 | 8.71k | if (h2o_http2_stream_is_push(stream->stream_id)) |
305 | 0 | h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, NULL, |
306 | 0 | H2O_STRLIT("pushed")); |
307 | 8.71k | if (stream->req.send_server_timing) |
308 | 0 | h2o_add_server_timing_header(&stream->req, 1); |
309 | 8.71k | stream->req.header_bytes_sent += h2o_hpack_flatten_response( |
310 | 8.71k | &conn->_write.buf, &conn->_output_header_table, conn->peer_settings.header_table_size, stream->stream_id, |
311 | 8.71k | conn->peer_settings.max_frame_size, stream->req.res.status, stream->req.res.headers.entries, stream->req.res.headers.size, |
312 | 8.71k | &conn->super.ctx->globalconf->server_name, stream->req.res.content_length, is_end_stream); |
313 | 8.71k | h2o_http2_conn_request_write(conn); |
314 | 8.71k | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY); |
315 | | |
316 | 8.71k | return 0; |
317 | | |
318 | 0 | CancelPush: |
319 | 0 | h2o_add_header_by_str(&stream->req.pool, &stream->req.res.headers, H2O_STRLIT("x-http2-push"), 0, NULL, |
320 | 0 | H2O_STRLIT("cancelled")); |
321 | 0 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
322 | 0 | h2o_linklist_insert(&conn->_write.streams_to_proceed, &stream->_link); |
323 | 0 | if (stream->push.promise_sent) { |
324 | 0 | h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream->stream_id, -H2O_HTTP2_ERROR_INTERNAL); |
325 | 0 | h2o_http2_conn_request_write(conn); |
326 | 0 | } |
327 | 0 | return -1; |
328 | 8.71k | } |
329 | | |
330 | | void finalostream_send(h2o_ostream_t *self, h2o_req_t *req, h2o_sendvec_t *bufs, size_t bufcnt, h2o_send_state_t state) |
331 | 9.14k | { |
332 | 9.14k | h2o_http2_stream_t *stream = H2O_STRUCT_FROM_MEMBER(h2o_http2_stream_t, _ostr_final, self); |
333 | 9.14k | h2o_http2_conn_t *conn = (h2o_http2_conn_t *)req->conn; |
334 | | |
335 | 9.14k | assert(h2o_send_state_is_in_progress(stream->send_state)); |
336 | 0 | assert(stream->_data.size == 0); |
337 | | |
338 | 9.14k | if (stream->blocked_by_server) |
339 | 5.54k | h2o_http2_stream_set_blocked_by_server(conn, stream, 0); |
340 | | |
341 | 9.14k | if (stream->req.res.status == 425 && stream->req.reprocess_if_too_early) { |
342 | 0 | assert(stream->state <= H2O_HTTP2_STREAM_STATE_SEND_HEADERS); |
343 | 0 | h2o_http2_conn_register_for_replay(conn, stream); |
344 | 0 | return; |
345 | 0 | } |
346 | | |
347 | 9.14k | stream->send_state = state; |
348 | 9.14k | int is_end_stream = state == H2O_SEND_STATE_FINAL && bufcnt == 0; |
349 | | |
350 | | /* send headers */ |
351 | 9.14k | switch (stream->state) { |
352 | 361 | case H2O_HTTP2_STREAM_STATE_RECV_BODY: |
353 | 361 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_REQ_PENDING); |
354 | | /* fallthru */ |
355 | 361 | case H2O_HTTP2_STREAM_STATE_REQ_PENDING: |
356 | 361 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_HEADERS); |
357 | | /* fallthru */ |
358 | 8.71k | case H2O_HTTP2_STREAM_STATE_SEND_HEADERS: |
359 | 8.71k | if (stream->req.upstream_refused) { |
360 | 0 | send_refused_stream(conn, stream); |
361 | 0 | return; |
362 | 0 | } |
363 | 8.71k | h2o_probe_log_response(&stream->req, stream->stream_id); |
364 | 8.71k | if (send_headers(conn, stream, is_end_stream) != 0) |
365 | 0 | return; |
366 | 8.71k | if (is_end_stream) { |
367 | 9 | request_write_and_close(conn, stream); |
368 | 9 | return; |
369 | 9 | } |
370 | | /* fallthru */ |
371 | 9.07k | case H2O_HTTP2_STREAM_STATE_SEND_BODY: |
372 | 9.07k | if (state != H2O_SEND_STATE_IN_PROGRESS) { |
373 | 8.28k | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL); |
374 | 8.28k | } |
375 | 9.07k | break; |
376 | 63 | case H2O_HTTP2_STREAM_STATE_END_STREAM: |
377 | | /* might get set by h2o_http2_stream_reset */ |
378 | 63 | return; |
379 | 0 | default: |
380 | 0 | assert(!"cannot be in a receiving state"); |
381 | 9.14k | } |
382 | | |
383 | | /* save the contents in queue */ |
384 | 9.07k | if (bufcnt != 0) { |
385 | 8.70k | h2o_vector_reserve(&req->pool, &stream->_data, bufcnt); |
386 | 8.70k | memcpy(stream->_data.entries, bufs, sizeof(*bufs) * bufcnt); |
387 | 8.70k | stream->_data.size = bufcnt; |
388 | 8.70k | } |
389 | | |
390 | 9.07k | h2o_http2_conn_register_for_proceed_callback(conn, stream); |
391 | 9.07k | } |
392 | | |
393 | | static void finalostream_send_informational(h2o_ostream_t *self, h2o_req_t *req) |
394 | 0 | { |
395 | 0 | h2o_http2_stream_t *stream = H2O_STRUCT_FROM_MEMBER(h2o_http2_stream_t, _ostr_final, self); |
396 | 0 | h2o_http2_conn_t *conn = (h2o_http2_conn_t *)req->conn; |
397 | |
|
398 | 0 | stream->req.header_bytes_sent += h2o_hpack_flatten_response( |
399 | 0 | &conn->_write.buf, &conn->_output_header_table, conn->peer_settings.header_table_size, stream->stream_id, |
400 | 0 | conn->peer_settings.max_frame_size, req->res.status, req->res.headers.entries, req->res.headers.size, NULL, SIZE_MAX, 0); |
401 | 0 | h2o_http2_conn_request_write(conn); |
402 | 0 | } |
403 | | |
404 | | void h2o_http2_stream_send_pending_data(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
405 | 6.47k | { |
406 | 6.47k | if (h2o_http2_window_get_avail(&stream->output_window) <= 0) |
407 | 403 | return; |
408 | | |
409 | 6.06k | h2o_sendvec_t *nextbuf = send_data(conn, stream, stream->_data.entries, stream->_data.size, stream->send_state); |
410 | 6.06k | if (nextbuf == NULL && stream->_data.entries != NULL) { |
411 | | /* error */ |
412 | 0 | stream->_data.size = 0; |
413 | 0 | stream->send_state = H2O_SEND_STATE_ERROR; |
414 | 0 | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
415 | 6.06k | } else if (nextbuf == stream->_data.entries + stream->_data.size) { |
416 | | /* sent all data */ |
417 | 5.30k | stream->_data.size = 0; |
418 | 5.30k | if (stream->state == H2O_HTTP2_STREAM_STATE_SEND_BODY_IS_FINAL) |
419 | 4.85k | h2o_http2_stream_set_state(conn, stream, H2O_HTTP2_STREAM_STATE_END_STREAM); |
420 | 5.30k | } else if (nextbuf != stream->_data.entries) { |
421 | | /* adjust the buffer */ |
422 | 0 | size_t newsize = stream->_data.size - (nextbuf - stream->_data.entries); |
423 | 0 | memmove(stream->_data.entries, nextbuf, sizeof(stream->_data.entries[0]) * newsize); |
424 | 0 | stream->_data.size = newsize; |
425 | 0 | } |
426 | | |
427 | | /* if the stream entered error state, suppress sending trailers */ |
428 | 6.06k | if (stream->send_state == H2O_SEND_STATE_ERROR) |
429 | 24 | stream->req.send_server_timing = 0; |
430 | 6.06k | } |
431 | | |
432 | | void h2o_http2_stream_proceed(h2o_http2_conn_t *conn, h2o_http2_stream_t *stream) |
433 | 5.39k | { |
434 | 5.39k | if (stream->state == H2O_HTTP2_STREAM_STATE_END_STREAM) { |
435 | 4.96k | switch (stream->req_body.state) { |
436 | 4.56k | case H2O_HTTP2_REQ_BODY_NONE: |
437 | 4.85k | case H2O_HTTP2_REQ_BODY_CLOSE_DELIVERED: |
438 | 4.85k | h2o_http2_stream_close(conn, stream); |
439 | 4.85k | break; |
440 | 116 | default: |
441 | 116 | break; /* the stream will be closed when the read side is done */ |
442 | 4.96k | } |
443 | 4.96k | } else { |
444 | 433 | if (!stream->blocked_by_server) |
445 | 345 | h2o_http2_stream_set_blocked_by_server(conn, stream, 1); |
446 | 433 | h2o_proceed_response(&stream->req); |
447 | 433 | } |
448 | 5.39k | } |