Coverage Report

Created: 2026-01-17 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/h2o/include/h2o/httpclient.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2017 Ichito Nagata, Fastly, Inc.
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
#ifndef h2o__httpclient_h
23
#define h2o__httpclient_h
24
25
#ifdef __cplusplus
26
extern "C" {
27
#endif
28
29
#include "quicly.h"
30
#include "h2o/header.h"
31
#include "h2o/hostinfo.h"
32
#include "h2o/http3_common.h"
33
#include "h2o/send_state.h"
34
#include "h2o/socket.h"
35
#include "h2o/socketpool.h"
36
37
typedef struct st_h2o_httpclient_t h2o_httpclient_t;
38
39
typedef void (*h2o_httpclient_forward_datagram_cb)(h2o_httpclient_t *client, h2o_iovec_t *datagrams, size_t num_datagrams);
40
41
/**
42
 * Additional properties related to the HTTP request being issued.
43
 * When the connect callback is being called, the properties of the objects are set to their initial values. Applications MAY alter
44
 * the properties to achieve desirable behavior. The reason we require the protocol stacks to initialize the values to their default
45
 * values instead of requiring applications to set all the values correctly is to avoid requiring applications making changes
46
 * every time a new field is added to the object.
47
 */
48
typedef struct st_h2o_httpclient_properties_t {
49
    /**
50
     * When the value is a non-NULL pointer (at the moment, only happens with the HTTP/1 client), the application MAY set it to an
51
     * iovec pointing to the payload of the PROXY protocol (i.e., the first line).
52
     */
53
    h2o_iovec_t *proxy_protocol;
54
    /**
55
     * When the value is a non-NULL pointer (at the moment, only happens with the HTTP/1 client), the application MAY set it to 1 to
56
     * indicate that the request body should be encoded using the chunked transfer-encoding.
57
     */
58
    int *chunked;
59
    /**
60
     * When the value is a non-NULL pointer (at the moment, only happens with the HTTP/1 client), the application MAY set it to the
61
     * value of the connection header field to be sent to the server. This value is advisory in sense that 1) the server might
62
     * decide to close the connection even if the client sent `keep-alive` and 2) the field may be rewritten to `upgrade` if the
63
     * client requested upgrade (extended CONNECT).
64
     */
65
    h2o_iovec_t *connection_header;
66
    /**
67
     * defaults to false
68
     */
69
    unsigned prefer_pipe_reader : 1;
70
    /**
71
     * When the value is 1, httpclient sends 'expect: 100-continue' header and suspends sending request body
72
     * until it sees 100-continue response
73
     */
74
    unsigned send_own_expect : 1;
75
} h2o_httpclient_properties_t;
76
77
typedef struct st_h2o_httpclient_pipe_reader_t h2o_httpclient_pipe_reader_t;
78
79
typedef struct st_h2o_httpclient_on_head_t {
80
    int version;
81
    int status;
82
    h2o_iovec_t msg;
83
    h2o_header_t *headers;
84
    size_t num_headers;
85
    int header_requires_dup;
86
    struct {
87
        h2o_httpclient_forward_datagram_cb write_, *read_;
88
    } forward_datagram;
89
    /**
90
     * If this pointer is set to non-NULL by the HTTP client, it is offering the user the opportunity to read content to a pipe,
91
     * rather than suppliend them as bytes. To take that opportunity, users should set the members of the pointed struct to
92
     * appropriate values. Note that even when the user opts in to using a pipe, first chunk of content may still be served through
93
     * memory, as the content would be read into memory alongside the HTTP response headers.
94
     */
95
    h2o_httpclient_pipe_reader_t *pipe_reader;
96
} h2o_httpclient_on_head_t;
97
98
typedef void (*h2o_httpclient_proceed_req_cb)(h2o_httpclient_t *client, const char *errstr);
99
typedef int (*h2o_httpclient_body_cb)(h2o_httpclient_t *client, const char *errstr, h2o_header_t *trailers, size_t num_trailers);
100
typedef h2o_httpclient_body_cb (*h2o_httpclient_head_cb)(h2o_httpclient_t *client, const char *errstr,
101
                                                         h2o_httpclient_on_head_t *args);
102
/**
103
 * Called when the protocol stack is ready to issue a request. Application must set all the output parameters (i.e. all except
104
 * `client`, `errstr`, `origin`) and return a callback that will be called when the protocol stack receives the response headers
105
 * from the server.
106
 */
107
typedef h2o_httpclient_head_cb (*h2o_httpclient_connect_cb)(h2o_httpclient_t *client, const char *errstr, h2o_iovec_t *method,
108
                                                            h2o_url_t *url, const h2o_header_t **headers, size_t *num_headers,
109
                                                            h2o_iovec_t *body, h2o_httpclient_proceed_req_cb *proceed_req_cb,
110
                                                            h2o_httpclient_properties_t *props, h2o_url_t *origin);
111
typedef int (*h2o_httpclient_informational_cb)(h2o_httpclient_t *client, int version, int status, h2o_iovec_t msg,
112
                                               h2o_header_t *headers, size_t num_headers);
113
114
typedef void (*h2o_httpclient_finish_cb)(h2o_httpclient_t *client);
115
116
struct st_h2o_httpclient_pipe_reader_t {
117
    int fd;
118
    h2o_httpclient_body_cb on_body_piped;
119
};
120
121
typedef struct st_h2o_httpclient_connection_pool_t {
122
    /**
123
     * used to establish connections and pool those when h1 is used.
124
     * socketpool is shared among multiple threads while connection pool is dedicated to each thread
125
     */
126
    h2o_socketpool_t *socketpool;
127
128
    struct {
129
        h2o_linklist_t conns;
130
    } http2;
131
132
    struct {
133
        h2o_linklist_t conns;
134
    } http3;
135
136
} h2o_httpclient_connection_pool_t;
137
138
typedef struct st_h2o_httpclient_protocol_ratio_t {
139
    /**
140
     * If non-negative, indicates the percentage of requests for which use of HTTP/2 will be attempted. If set to negative, all
141
     * connections are established with ALPN offering both H1 and H2, then the load is balanced between the different protocol
142
     * versions. This behavior helps balance the load among a mixture of servers behind a load balancer, some supporting both H1 and
143
     * H2 and some supporting only H1.
144
     */
145
    int8_t http2;
146
    /**
147
     * Indicates the percentage of requests for which HTTP/3 should be used. Unlike HTTP/2, this value cannot be negative, because
148
     * unlike ALPN over TLS over TCP, the choice of the protocol is up to the client.
149
     */
150
    int8_t http3;
151
} h2o_httpclient_protocol_ratio_t;
152
153
typedef struct st_h2o_http3client_ctx_t h2o_http3client_ctx_t;
154
155
typedef struct st_h2o_httpclient_ctx_t {
156
    h2o_loop_t *loop;
157
    h2o_multithread_receiver_t *getaddr_receiver;
158
    uint64_t io_timeout;
159
    uint64_t connect_timeout;
160
    uint64_t first_byte_timeout;
161
    uint64_t keepalive_timeout; /* only used for http2 for now */
162
    size_t max_buffer_size;
163
    unsigned tunnel_enabled : 1;
164
    unsigned force_cleartext_http2 : 1;
165
166
    struct st_h2o_httpclient_protocol_selector_t {
167
        h2o_httpclient_protocol_ratio_t ratio;
168
        /**
169
         * Each deficit is initialized to zero, then incremented by the respective percentage, and the protocol corresponding to the
170
         * one with the highest value is chosen. Then, the chosen variable is decremented by 100.
171
         */
172
        int16_t _deficits[4];
173
    } protocol_selector;
174
175
    /**
176
     * HTTP/2-specific settings
177
     */
178
    struct {
179
        h2o_socket_latency_optimization_conditions_t latency_optimization;
180
        uint32_t max_concurrent_streams;
181
    } http2;
182
183
    /**
184
     * HTTP/3-specific settings; 1-to(0|1) relationship, NULL when h3 is not used
185
     */
186
    h2o_http3client_ctx_t *http3;
187
188
} h2o_httpclient_ctx_t;
189
190
struct st_h2o_http3client_ctx_t {
191
    ptls_openssl_verify_certificate_t verify_cert;
192
    ptls_context_t tls;
193
    quicly_context_t quic;
194
    h2o_quic_ctx_t h3;
195
    uint64_t max_frame_payload_size;
196
    /**
197
     * Optional callback invoked by the HTTP/3 client implementation to obtain information used for resuming a connection. When the
198
     * connection is to be resumed, the callback should set `*address_token` and `*session_ticket` to a vector that can be freed by
199
     * calling free (3), as well as writing the resumed transport parameters to `*resumed_tp`. Otherwise, `*address_token`,
200
     * `*session_ticket`, `*resumed_tp` can be left untouched, and a full handshake will be exercised. The function returns if the
201
     * operation was successful. When false is returned, the connection attempt is aborted.
202
     */
203
    int (*load_session)(h2o_httpclient_ctx_t *ctx, struct sockaddr *server_addr, const char *server_name,
204
                        ptls_iovec_t *address_token, ptls_iovec_t *session_ticket, quicly_transport_parameters_t *resumed_tp);
205
};
206
207
typedef struct st_h2o_httpclient_timings_t {
208
    struct timeval start_at;
209
    struct timeval request_begin_at;
210
    struct timeval request_end_at;
211
    struct timeval response_start_at;
212
    struct timeval response_end_at;
213
} h2o_httpclient_timings_t;
214
215
/**
216
 * Properties of a HTTP client connection.
217
 */
218
typedef struct st_h2o_httpclient_conn_properties_t {
219
    /**
220
     * TLS properties. Definitions match that returned by corresponding h2o_socket function: `h2o_socket_ssl_*`.
221
     */
222
    struct {
223
        const char *protocol_version;
224
        int session_reused;
225
        const char *cipher;
226
        int cipher_bits;
227
    } ssl;
228
    /**
229
     * Underlying TCP connection, if any.
230
     */
231
    h2o_socket_t *sock;
232
} h2o_httpclient_conn_properties_t;
233
234
struct st_h2o_httpclient_t {
235
    /**
236
     * memory pool
237
     */
238
    h2o_mem_pool_t *pool;
239
    /**
240
     * context
241
     */
242
    h2o_httpclient_ctx_t *ctx;
243
    /**
244
     * connection pool
245
     */
246
    h2o_httpclient_connection_pool_t *connpool;
247
    /**
248
     * buffer in which response data is stored (see update_window)
249
     */
250
    h2o_buffer_t **buf;
251
    /**
252
     * application data pointer
253
     */
254
    void *data;
255
    /**
256
     * optional callback to receive informational response(s); 101 is considered final and is never delivered through this callback
257
     */
258
    h2o_httpclient_informational_cb informational_cb;
259
    /**
260
     * server-timing data
261
     */
262
    h2o_httpclient_timings_t timings;
263
    /**
264
     * If the stream is to be converted to convey some other protocol, this value should be set to the name of the protocol, which
265
     * will be indicated by the `upgrade` request header field. Additionally, intent to create a CONNECT tunnel is indicated by a
266
     * special label called `h2o_httpclient_upgrade_to_connect`.
267
     */
268
    const char *upgrade_to;
269
270
    /**
271
     * bytes written (above the TLS layer)
272
     */
273
    struct {
274
        uint64_t header;
275
        uint64_t body;
276
        uint64_t total;
277
    } bytes_written;
278
279
    /**
280
     * bytes read (above the TLS layer)
281
     */
282
    struct {
283
        uint64_t header;
284
        uint64_t body;
285
        uint64_t total;
286
    } bytes_read;
287
288
    /**
289
     * cancels a in-flight request
290
     */
291
    void (*cancel)(h2o_httpclient_t *client);
292
    /**
293
     * returns a pointer to the underlying h2o_socket_t
294
     */
295
    void (*get_conn_properties)(h2o_httpclient_t *client, h2o_httpclient_conn_properties_t *properties);
296
    /**
297
     * callback that should be called when some data is fetched out from `buf`.
298
     */
299
    void (*update_window)(h2o_httpclient_t *client);
300
    /**
301
     * Function for writing request body. `proceed_req_cb` supplied through the `on_connect` callback will be called when the
302
     * given data is sent to the server. Regarding the usage, refer to the doc-comment of `h2o_write_req_cb`.
303
     */
304
    int (*write_req)(h2o_httpclient_t *client, h2o_iovec_t chunk, int is_end_stream);
305
306
    h2o_timer_t _timeout;
307
    h2o_socketpool_connect_request_t *_connect_req;
308
    union {
309
        h2o_httpclient_connect_cb on_connect;
310
        h2o_httpclient_head_cb on_head;
311
        h2o_httpclient_body_cb on_body;
312
    } _cb;
313
};
314
315
/**
316
 * public members of h2 client connection
317
 */
318
typedef struct st_h2o_httpclient__h2_conn_t {
319
    /**
320
     * context
321
     */
322
    h2o_httpclient_ctx_t *ctx;
323
    /**
324
     * origin server (path is ignored)
325
     */
326
    h2o_url_t origin_url;
327
    /**
328
     * underlying socket
329
     */
330
    h2o_socket_t *sock;
331
    /**
332
     * number of open streams (FIXME can't we refer to khash?)
333
     */
334
    size_t num_streams;
335
    /**
336
     * linklist of connections anchored to h2o_httpclient_connection_pool_t::http2.conns. The link is in the ascending order of
337
     * `num_streams`.
338
     */
339
    h2o_linklist_t link;
340
} h2o_httpclient__h2_conn_t;
341
342
struct st_h2o_httpclient__h3_conn_t {
343
    h2o_http3_conn_t super;
344
    h2o_httpclient_ctx_t *ctx;
345
    /**
346
     * When the socket is associated to a global pool, used to identify the origin. If not associated to a global pool, the values
347
     * are zero-filled.
348
     */
349
    struct {
350
        /**
351
         * the origin URL; null-termination of authority and host is guaranteed
352
         */
353
        h2o_url_t origin_url;
354
        /**
355
         * port number in C string
356
         */
357
        char named_serv[sizeof(H2O_UINT16_LONGEST_STR)];
358
    } server;
359
    ptls_handshake_properties_t handshake_properties;
360
    h2o_timer_t timeout;
361
    h2o_hostinfo_getaddr_req_t *getaddr_req;
362
    /**
363
     * linked to h2o_httpclient_ctx_t::http3.conns
364
     */
365
    h2o_linklist_t link;
366
    /**
367
     * linklist used to queue pending requests
368
     */
369
    h2o_linklist_t pending_requests;
370
};
371
372
extern const char h2o_httpclient_error_is_eos[];
373
extern const char h2o_httpclient_error_refused_stream[];
374
extern const char h2o_httpclient_error_unknown_alpn_protocol[];
375
extern const char h2o_httpclient_error_io[];
376
extern const char h2o_httpclient_error_connect_timeout[];
377
extern const char h2o_httpclient_error_first_byte_timeout[];
378
extern const char h2o_httpclient_error_io_timeout[];
379
extern const char h2o_httpclient_error_invalid_content_length[];
380
extern const char h2o_httpclient_error_flow_control[];
381
extern const char h2o_httpclient_error_http1_line_folding[];
382
extern const char h2o_httpclient_error_http1_unexpected_transfer_encoding[];
383
extern const char h2o_httpclient_error_http1_parse_failed[];
384
extern const char h2o_httpclient_error_protocol_violation[];
385
extern const char h2o_httpclient_error_internal[];
386
extern const char h2o_httpclient_error_malformed_frame[];
387
extern const char h2o_httpclient_error_unexpected_101[];
388
389
extern const char h2o_httpclient_upgrade_to_connect[];
390
391
void h2o_httpclient_connection_pool_init(h2o_httpclient_connection_pool_t *connpool, h2o_socketpool_t *sockpool);
392
393
/**
394
 * issues a HTTP request using the connection pool. Either H1 or H2 may be used, depending on the given context.
395
 * TODO: create H1- or H2-specific connect function that works without the connection pool?
396
 */
397
void h2o_httpclient_connect(h2o_httpclient_t **client, h2o_mem_pool_t *pool, void *data, h2o_httpclient_ctx_t *ctx,
398
                            h2o_httpclient_connection_pool_t *connpool, h2o_url_t *target, const char *upgrade_to,
399
                            h2o_httpclient_connect_cb on_connect);
400
401
void h2o_httpclient__h1_on_connect(h2o_httpclient_t *client, h2o_socket_t *sock, h2o_url_t *origin);
402
extern const size_t h2o_httpclient__h1_size;
403
404
void h2o_httpclient__h2_on_connect(h2o_httpclient_t *client, h2o_socket_t *sock, h2o_url_t *origin);
405
uint32_t h2o_httpclient__h2_get_max_concurrent_streams(h2o_httpclient__h2_conn_t *conn);
406
extern const size_t h2o_httpclient__h2_size;
407
408
void h2o_httpclient_set_conn_properties_of_socket(h2o_socket_t *sock, h2o_httpclient_conn_properties_t *properties);
409
410
#ifdef quicly_h /* create http3client.h? */
411
412
#include "h2o/http3_common.h"
413
414
void h2o_httpclient_http3_notify_connection_update(h2o_quic_ctx_t *ctx, h2o_quic_conn_t *conn);
415
extern quicly_stream_open_t h2o_httpclient_http3_on_stream_open;
416
extern quicly_receive_datagram_frame_t h2o_httpclient_http3_on_receive_datagram_frame;
417
void h2o_httpclient__connect_h3(h2o_httpclient_t **client, h2o_mem_pool_t *pool, void *data, h2o_httpclient_ctx_t *ctx,
418
                                h2o_httpclient_connection_pool_t *connpool, h2o_url_t *target, const char *upgrade_to,
419
                                h2o_httpclient_connect_cb cb);
420
/**
421
 * internal API for checking if the stream is to be turned into a tunnel
422
 */
423
static int h2o_httpclient__tunnel_is_ready(h2o_httpclient_t *client, int status, int http_version);
424
425
/* inline definitions */
426
427
inline int h2o_httpclient__tunnel_is_ready(h2o_httpclient_t *client, int status, int http_version)
428
3.36k
{
429
3.36k
    if (client->upgrade_to != NULL) {
430
0
        if (client->upgrade_to == h2o_httpclient_upgrade_to_connect && 200 <= status && status <= 299)
431
0
            return 1;
432
0
        if (http_version < 0x200) {
433
0
            if (status == 101)
434
0
                return 1;
435
0
        } else {
436
0
            if (200 <= status && status <= 299)
437
0
                return 1;
438
0
        }
439
0
    }
440
3.36k
    return 0;
441
3.36k
}
Unexecuted instantiation: driver.cc:h2o_httpclient__tunnel_is_ready(st_h2o_httpclient_t*, int, int)
Unexecuted instantiation: driver_common.cc:h2o_httpclient__tunnel_is_ready(st_h2o_httpclient_t*, int, int)
Unexecuted instantiation: config.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: configurator.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: context.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: headers.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: request.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: util.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: access_log.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: file.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: mimemap.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: proxy.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: http1.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: connection.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: scheduler.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: stream.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: http2_debug_state.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: common.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: server.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: http3client.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: httpclient.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: absprio.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: logconf.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: compress.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: gzip.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: headers_util.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: qpack.c:h2o_httpclient__tunnel_is_ready
http1client.c:h2o_httpclient__tunnel_is_ready
Line
Count
Source
428
3.36k
{
429
3.36k
    if (client->upgrade_to != NULL) {
430
0
        if (client->upgrade_to == h2o_httpclient_upgrade_to_connect && 200 <= status && status <= 299)
431
0
            return 1;
432
0
        if (http_version < 0x200) {
433
0
            if (status == 101)
434
0
                return 1;
435
0
        } else {
436
0
            if (200 <= status && status <= 299)
437
0
                return 1;
438
0
        }
439
0
    }
440
3.36k
    return 0;
441
3.36k
}
Unexecuted instantiation: http2client.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: pipe_sender.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: driver_url.cc:h2o_httpclient__tunnel_is_ready(st_h2o_httpclient_t*, int, int)
Unexecuted instantiation: driver_h3.cc:h2o_httpclient__tunnel_is_ready(st_h2o_httpclient_t*, int, int)
Unexecuted instantiation: errordoc.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: expires.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: fastcgi.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: h2olog.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: connect.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: redirect.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: reproxy.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: throttle_resp.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: self_trace.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: server_timing.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: status.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: events.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: memory.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: requests.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: ssl.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: durations.c:h2o_httpclient__tunnel_is_ready
Unexecuted instantiation: brotli.c:h2o_httpclient__tunnel_is_ready
442
443
#endif
444
445
#ifdef __cplusplus
446
}
447
#endif
448
449
#endif