Coverage Report

Created: 2025-08-26 06:34

/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
}