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