Coverage Report

Created: 2025-10-10 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/h2o/lib/core/proxy.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2014,2015 DeNA Co., Ltd., Kazuho Oku, Masahiro Nagano
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 <fcntl.h>
23
#include <netdb.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <sys/socket.h>
27
#include "picohttpparser.h"
28
#include "h2o.h"
29
#include "h2o/http1.h"
30
#include "h2o/httpclient.h"
31
#include "h2o/pipe_sender.h"
32
33
struct rp_generator_t {
34
    h2o_generator_t super;
35
    h2o_req_t *src_req;
36
    h2o_httpclient_t *client;
37
    struct {
38
        h2o_iovec_t bufs[2]; /* first buf is the request line and headers, the second is the POST content */
39
        int is_head;
40
    } up_req;
41
    h2o_buffer_t *last_content_before_send;
42
    h2o_doublebuffer_t sending;
43
    h2o_timer_t send_headers_timeout;
44
    h2o_pipe_sender_t pipe_sender;
45
    /**
46
     * number of bytes read from body; this value is a copy of `client->bytes_read.body` but persists after `client` is discarded
47
     */
48
    size_t body_bytes_read;
49
    unsigned had_body_error : 1; /* set if an error happened while fetching the body so that we can propagate the error */
50
    unsigned req_done : 1;
51
    unsigned res_done : 1;
52
    int *generator_disposed;
53
};
54
55
static h2o_httpclient_ctx_t *get_client_ctx(h2o_req_t *req)
56
3.69k
{
57
3.69k
    h2o_req_overrides_t *overrides = req->overrides;
58
3.69k
    if (overrides != NULL && overrides->client_ctx != NULL)
59
3.69k
        return overrides->client_ctx;
60
0
    return &req->conn->ctx->proxy.client_ctx;
61
3.69k
}
62
63
static h2o_iovec_t rewrite_location(h2o_mem_pool_t *pool, const char *location, size_t location_len, h2o_url_t *match,
64
                                    const h2o_url_scheme_t *req_scheme, h2o_iovec_t req_authority, h2o_iovec_t req_basepath)
65
0
{
66
0
    h2o_url_t loc_parsed;
67
68
0
    if (h2o_url_parse(pool, location, location_len, &loc_parsed) != 0)
69
0
        goto NoRewrite;
70
0
    if (loc_parsed.scheme != &H2O_URL_SCHEME_HTTP)
71
0
        goto NoRewrite;
72
0
    if (!h2o_url_hosts_are_equal(&loc_parsed, match))
73
0
        goto NoRewrite;
74
0
    if (h2o_url_get_port(&loc_parsed) != h2o_url_get_port(match))
75
0
        goto NoRewrite;
76
0
    if (loc_parsed.path.len < match->path.len)
77
0
        goto NoRewrite;
78
0
    if (memcmp(loc_parsed.path.base, match->path.base, match->path.len) != 0)
79
0
        goto NoRewrite;
80
81
0
    return h2o_concat(pool, req_scheme->name, h2o_iovec_init(H2O_STRLIT("://")), req_authority, req_basepath,
82
0
                      h2o_iovec_init(loc_parsed.path.base + match->path.len, loc_parsed.path.len - match->path.len));
83
84
0
NoRewrite:
85
0
    return (h2o_iovec_t){NULL};
86
0
}
87
88
static h2o_iovec_t build_request_merge_headers(h2o_mem_pool_t *pool, h2o_iovec_t merged, h2o_iovec_t added, int seperator)
89
10.6k
{
90
10.6k
    if (added.len == 0)
91
976
        return merged;
92
9.64k
    if (merged.len == 0)
93
7.26k
        return added;
94
95
2.38k
    size_t newlen = merged.len + 2 + added.len;
96
2.38k
    char *buf = h2o_mem_alloc_pool(pool, *buf, newlen);
97
2.38k
    memcpy(buf, merged.base, merged.len);
98
2.38k
    buf[merged.len] = seperator;
99
2.38k
    buf[merged.len + 1] = ' ';
100
2.38k
    memcpy(buf + merged.len + 2, added.base, added.len);
101
2.38k
    merged.base = buf;
102
2.38k
    merged.len = newlen;
103
2.38k
    return merged;
104
9.64k
}
105
106
/*
107
 * A request without neither Content-Length or Transfer-Encoding header implies a zero-length request body (see 6th rule of RFC 7230
108
 * 3.3.3).
109
 * OTOH, section 3.3.3 states:
110
 *
111
 *   A user agent SHOULD send a Content-Length in a request message when
112
 *   no Transfer-Encoding is sent and the request method defines a meaning
113
 *   for an enclosed payload body.  For example, a Content-Length header
114
 *   field is normally sent in a POST request even when the value is 0
115
 *   (indicating an empty payload body).  A user agent SHOULD NOT send a
116
 *   Content-Length header field when the request message does not contain
117
 *   a payload body and the method semantics do not anticipate such a
118
 *   body.
119
 *
120
 * PUT and POST define a meaning for the payload body, let's emit a
121
 * Content-Length header if it doesn't exist already, since the server
122
 * might send a '411 Length Required' response.
123
 *
124
 * see also: ML thread starting at https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0580.html
125
 */
126
static int req_requires_content_length(h2o_req_t *req)
127
2.48k
{
128
2.48k
    int is_put_or_post = (req->method.len >= 1 && req->method.base[0] == 'P' &&
129
783
                          (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST")) ||
130
357
                           h2o_memis(req->method.base, req->method.len, H2O_STRLIT("PUT"))));
131
132
2.48k
    return is_put_or_post && h2o_find_header(&req->res.headers, H2O_TOKEN_TRANSFER_ENCODING, -1) == -1;
133
2.48k
}
134
135
static h2o_iovec_t build_content_length(h2o_mem_pool_t *pool, size_t cl)
136
1.21k
{
137
1.21k
    h2o_iovec_t cl_buf;
138
1.21k
    cl_buf.base = h2o_mem_alloc_pool(pool, char, sizeof(H2O_SIZE_T_LONGEST_STR));
139
1.21k
    cl_buf.len = sprintf(cl_buf.base, "%zu", cl);
140
1.21k
    return cl_buf;
141
1.21k
}
142
143
static void build_request(h2o_req_t *req, h2o_iovec_t *method, h2o_url_t *url, h2o_headers_t *headers,
144
                          h2o_httpclient_properties_t *props, int keepalive, const char *upgrade_to, int use_proxy_protocol,
145
                          int *reprocess_if_too_early, h2o_url_t *origin)
146
3.63k
{
147
3.63k
    size_t remote_addr_len = SIZE_MAX;
148
3.63k
    char remote_addr[NI_MAXHOST];
149
3.63k
    struct sockaddr_storage ss;
150
3.63k
    socklen_t sslen;
151
3.63k
    h2o_iovec_t xff_buf = {NULL}, via_buf = {NULL};
152
3.63k
    int preserve_x_forwarded_proto = req->conn->ctx->globalconf->proxy.preserve_x_forwarded_proto;
153
3.63k
    int emit_x_forwarded_headers = req->conn->ctx->globalconf->proxy.emit_x_forwarded_headers;
154
3.63k
    int emit_via_header = req->conn->ctx->globalconf->proxy.emit_via_header;
155
156
    /* for x-f-f */
157
3.63k
    if ((sslen = req->conn->callbacks->get_peername(req->conn, (void *)&ss)) != 0)
158
3.63k
        remote_addr_len = h2o_socket_getnumerichost((void *)&ss, sslen, remote_addr);
159
160
3.63k
    if (props->proxy_protocol != NULL && use_proxy_protocol) {
161
0
        props->proxy_protocol->base = h2o_mem_alloc_pool(&req->pool, char, H2O_PROXY_HEADER_MAX_LENGTH);
162
0
        props->proxy_protocol->len = h2o_stringify_proxy_header(req->conn, props->proxy_protocol->base);
163
0
    }
164
165
    /* copy method (if it is an extended CONNECT switching versions, convert as appropriate) */
166
3.63k
    *method = h2o_strdup(&req->pool, req->method.base, req->method.len);
167
3.63k
    if (upgrade_to != NULL && upgrade_to != h2o_httpclient_upgrade_to_connect) {
168
0
        if (req->version >= 0x200 && h2o_memis(method->base, method->len, H2O_STRLIT("CONNECT")) &&
169
0
            props->connection_header != NULL) {
170
0
            *method = h2o_iovec_init(H2O_STRLIT("GET"));
171
0
        } else if (req->version < 0x200 && h2o_memis(method->base, method->len, H2O_STRLIT("GET")) &&
172
0
                   props->connection_header == NULL) {
173
0
            *method = h2o_iovec_init(H2O_STRLIT("CONNECT"));
174
0
        }
175
0
    }
176
177
    /* url */
178
3.63k
    if (h2o_url_init(url, origin->scheme, req->authority, h2o_strdup(&req->pool, req->path.base, req->path.len)) != 0)
179
0
        h2o_fatal("h2o_url_init failed");
180
181
3.63k
    if (props->connection_header != NULL) {
182
3.63k
        if (keepalive) {
183
3.63k
            *props->connection_header = h2o_iovec_init(H2O_STRLIT("keep-alive"));
184
3.63k
        } else {
185
0
            *props->connection_header = h2o_iovec_init(H2O_STRLIT("close"));
186
0
        }
187
3.63k
    }
188
189
    /* setup CL or TE, if necessary; chunked encoding is used when the request body is stream and content-length is unknown */
190
3.63k
    if (!req->is_tunnel_req) {
191
3.62k
        if (req->proceed_req == NULL) {
192
2.66k
            if (req->entity.base != NULL || req_requires_content_length(req)) {
193
671
                h2o_iovec_t cl_buf = build_content_length(&req->pool, req->entity.len);
194
671
                h2o_add_header(&req->pool, headers, H2O_TOKEN_CONTENT_LENGTH, NULL, cl_buf.base, cl_buf.len);
195
671
            }
196
2.66k
        } else {
197
962
            if (req->content_length != SIZE_MAX) {
198
543
                h2o_iovec_t cl_buf = build_content_length(&req->pool, req->content_length);
199
543
                h2o_add_header(&req->pool, headers, H2O_TOKEN_CONTENT_LENGTH, NULL, cl_buf.base, cl_buf.len);
200
543
            } else if (props->chunked != NULL) {
201
419
                *props->chunked = 1;
202
419
                h2o_add_header(&req->pool, headers, H2O_TOKEN_TRANSFER_ENCODING, NULL, H2O_STRLIT("chunked"));
203
419
            }
204
962
        }
205
3.62k
    }
206
207
    /* headers */
208
3.63k
    h2o_iovec_vector_t cookie_values = {NULL};
209
3.63k
    int found_early_data = 0;
210
3.63k
    if (H2O_LIKELY(req->headers.size != 0)) {
211
470k
        for (const h2o_header_t *h = req->headers.entries, *h_end = h + req->headers.size; h != h_end; ++h) {
212
468k
            if (h2o_iovec_is_token(h->name)) {
213
457k
                const h2o_token_t *token = (void *)h->name;
214
457k
                if (token->flags.proxy_should_drop_for_req)
215
785
                    continue;
216
456k
                if (token == H2O_TOKEN_COOKIE) {
217
201k
                    h2o_vector_reserve(&req->pool, &cookie_values, cookie_values.size + 1);
218
201k
                    cookie_values.entries[cookie_values.size++] = h->value;
219
201k
                    continue;
220
254k
                } else if (token == H2O_TOKEN_VIA) {
221
1.21k
                    if (!emit_via_header) {
222
0
                        goto AddHeader;
223
0
                    }
224
1.21k
                    via_buf = build_request_merge_headers(&req->pool, via_buf, h->value, ',');
225
1.21k
                    continue;
226
253k
                } else if (token == H2O_TOKEN_X_FORWARDED_FOR) {
227
2.14k
                    if (!emit_x_forwarded_headers) {
228
0
                        goto AddHeader;
229
0
                    }
230
2.14k
                    xff_buf = build_request_merge_headers(&req->pool, xff_buf, h->value, ',');
231
2.14k
                    continue;
232
251k
                } else if (token == H2O_TOKEN_EARLY_DATA) {
233
379
                    found_early_data = 1;
234
379
                    goto AddHeader;
235
379
                }
236
456k
            }
237
262k
            if (!preserve_x_forwarded_proto && h2o_lcstris(h->name->base, h->name->len, H2O_STRLIT("x-forwarded-proto")))
238
67
                continue;
239
263k
        AddHeader:
240
263k
            if (h2o_iovec_is_token(h->name)) {
241
251k
                const h2o_token_t *token = (void *)h->name;
242
251k
                h2o_add_header(&req->pool, headers, token, h->orig_name, h->value.base, h->value.len);
243
251k
            } else {
244
11.4k
                h2o_add_header_by_str(&req->pool, headers, h->name->base, h->name->len, 0, h->orig_name, h->value.base,
245
11.4k
                                      h->value.len);
246
11.4k
            }
247
263k
        }
248
1.57k
    }
249
3.63k
    if (found_early_data) {
250
46
        *reprocess_if_too_early = 0;
251
3.58k
    } else if (*reprocess_if_too_early) {
252
0
        h2o_add_header(&req->pool, headers, H2O_TOKEN_EARLY_DATA, NULL, H2O_STRLIT("1"));
253
0
    }
254
255
3.63k
    if (cookie_values.size == 1) {
256
        /* fast path */
257
102
        h2o_add_header(&req->pool, headers, H2O_TOKEN_COOKIE, NULL, cookie_values.entries[0].base, cookie_values.entries[0].len);
258
3.52k
    } else if (cookie_values.size > 1) {
259
        /* merge the cookie headers; see HTTP/2 8.1.2.5 and HTTP/1 (RFC6265 5.4) */
260
377
        h2o_iovec_t cookie_buf =
261
377
            h2o_join_list(&req->pool, cookie_values.entries, cookie_values.size, h2o_iovec_init(H2O_STRLIT("; ")));
262
377
        h2o_add_header(&req->pool, headers, H2O_TOKEN_COOKIE, NULL, cookie_buf.base, cookie_buf.len);
263
377
    }
264
3.63k
    if (emit_x_forwarded_headers) {
265
3.63k
        if (!preserve_x_forwarded_proto)
266
3.63k
            h2o_add_header_by_str(&req->pool, headers, H2O_STRLIT("x-forwarded-proto"), 0, NULL, req->input.scheme->name.base,
267
3.63k
                                  req->input.scheme->name.len);
268
3.63k
        if (remote_addr_len != SIZE_MAX)
269
3.63k
            xff_buf = build_request_merge_headers(&req->pool, xff_buf, h2o_strdup(&req->pool, remote_addr, remote_addr_len), ',');
270
3.63k
        if (xff_buf.len != 0)
271
3.63k
            h2o_add_header(&req->pool, headers, H2O_TOKEN_X_FORWARDED_FOR, NULL, xff_buf.base, xff_buf.len);
272
3.63k
    }
273
3.63k
    if (emit_via_header) {
274
3.63k
        h2o_iovec_t added;
275
3.63k
        added.base = h2o_mem_alloc_pool(&req->pool, char, sizeof("1.1 ") - 1 + req->input.authority.len);
276
3.63k
        added.len = 0;
277
278
3.63k
        if (req->version < 0x200) {
279
2.21k
            added.base[added.len++] = '1';
280
2.21k
            added.base[added.len++] = '.';
281
2.21k
            added.base[added.len++] = '0' + (0x100 <= req->version && req->version <= 0x109 ? req->version - 0x100 : 0);
282
2.21k
        } else {
283
1.41k
            added.base[added.len++] = '0' + req->version / 0x100;
284
1.41k
        }
285
3.63k
        added.base[added.len++] = ' ';
286
3.63k
        memcpy(added.base + added.len, req->input.authority.base, req->input.authority.len);
287
3.63k
        added.len += req->input.authority.len;
288
289
3.63k
        via_buf = build_request_merge_headers(&req->pool, via_buf, added, ',');
290
3.63k
        h2o_add_header(&req->pool, headers, H2O_TOKEN_VIA, NULL, via_buf.base, via_buf.len);
291
3.63k
    }
292
293
    /* rewrite headers if necessary */
294
3.63k
    if (req->overrides != NULL && req->overrides->headers_cmds != NULL) {
295
0
        h2o_headers_command_t *cmd;
296
0
        for (cmd = req->overrides->headers_cmds; cmd->cmd != H2O_HEADERS_CMD_NULL; ++cmd)
297
0
            h2o_rewrite_headers(&req->pool, headers, cmd);
298
0
    }
299
3.63k
}
300
301
static h2o_httpclient_t *detach_client(struct rp_generator_t *self)
302
3.69k
{
303
3.69k
    h2o_httpclient_t *client = self->client;
304
3.69k
    assert(client != NULL);
305
3.69k
    client->data = NULL;
306
3.69k
    self->client = NULL;
307
3.69k
    return client;
308
3.69k
}
309
310
static void do_close(struct rp_generator_t *self)
311
3.75k
{
312
    /**
313
     * This can be called in the following three scenarios:
314
     *   1. Downstream timeout before receiving header from upstream
315
     *        dispose callback calls this function, but stop callback doesn't
316
     *   2. Reprocess
317
     *        stop callback calls this, but dispose callback does it later (after reprocessed request gets finished)
318
     *   3. Others
319
     *        Both of stop and dispose callbacks call this function in order
320
     * Thus, to ensure to do closing things, both of dispose and stop callbacks call this function (reminder: that means that this
321
     * function might get called multiple times).
322
     */
323
3.75k
    if (self->client != NULL) {
324
643
        h2o_httpclient_t *client = detach_client(self);
325
643
        client->cancel(client);
326
643
    }
327
3.75k
    h2o_timer_unlink(&self->send_headers_timeout);
328
3.75k
    h2o_pipe_sender_dispose(&self->pipe_sender, self->src_req->conn->ctx);
329
3.75k
}
330
331
static void do_stop(h2o_generator_t *generator, h2o_req_t *req)
332
59
{
333
59
    struct rp_generator_t *self = (void *)generator;
334
59
    do_close(self);
335
59
}
336
337
static void do_send(struct rp_generator_t *self)
338
6.94k
{
339
6.94k
    h2o_iovec_t vecs[1];
340
6.94k
    size_t veccnt;
341
6.94k
    h2o_send_state_t ststate;
342
343
6.94k
    vecs[0] = h2o_doublebuffer_prepare(&self->sending,
344
6.94k
                                       self->last_content_before_send != NULL ? &self->last_content_before_send : self->client->buf,
345
6.94k
                                       self->src_req->preferred_chunk_size);
346
347
6.94k
    if (self->last_content_before_send != NULL && vecs[0].len == self->sending.buf->size &&
348
2.45k
        self->last_content_before_send->size == 0) {
349
2.45k
        veccnt = vecs[0].len != 0 ? 1 : 0;
350
2.45k
        ststate = H2O_SEND_STATE_FINAL;
351
4.48k
    } else {
352
4.48k
        if (vecs[0].len == 0)
353
1.97k
            return;
354
2.51k
        veccnt = 1;
355
2.51k
        ststate = H2O_SEND_STATE_IN_PROGRESS;
356
2.51k
    }
357
358
4.96k
    if (self->had_body_error)
359
38
        ststate = H2O_SEND_STATE_ERROR;
360
361
    /* Even when the piped sender is used, body bytes that were read together with the HTTP response headers are sent using the
362
     * buffer. As the amount of bytes available in the piped sender is calculated as `body_bytes_read - pipe_sender.bytes_sent`,
363
     * adjust `h2o_pipe_sender_t::bytes_sent` here so that the field would reflect the number of body bytes being sent. */
364
4.96k
    if (veccnt != 0 && h2o_pipe_sender_in_use(&self->pipe_sender))
365
0
        self->pipe_sender.bytes_sent += vecs[0].len;
366
367
4.96k
    h2o_send(self->src_req, vecs, veccnt, ststate);
368
4.96k
}
369
370
static void do_send_from_pipe(struct rp_generator_t *self)
371
0
{
372
0
    h2o_send_state_t send_state = self->had_body_error ? H2O_SEND_STATE_ERROR
373
0
                                  : self->res_done     ? H2O_SEND_STATE_FINAL
374
0
                                                       : H2O_SEND_STATE_IN_PROGRESS;
375
376
0
    if (self->body_bytes_read == self->pipe_sender.bytes_sent) {
377
0
        if (h2o_send_state_is_in_progress(send_state)) {
378
            /* resume reading only when we know that the pipe (to which we read) has become empty */
379
0
            self->client->update_window(self->client);
380
0
        } else {
381
0
            h2o_send(self->src_req, NULL, 0, send_state);
382
0
        }
383
0
        return;
384
0
    }
385
386
0
   size_t len;
387
0
    if ((len = self->body_bytes_read - self->pipe_sender.bytes_sent) > H2O_PULL_SENDVEC_MAX_SIZE) {
388
0
        if (send_state == H2O_SEND_STATE_FINAL)
389
0
            send_state = H2O_SEND_STATE_IN_PROGRESS;
390
0
        len = H2O_PULL_SENDVEC_MAX_SIZE;
391
0
    }
392
0
    h2o_pipe_sender_send(self->src_req, &self->pipe_sender, len, send_state);
393
0
}
394
395
static void do_proceed(h2o_generator_t *generator, h2o_req_t *req)
396
2.45k
{
397
2.45k
    struct rp_generator_t *self = (void *)generator;
398
399
2.45k
    if (self->sending.inflight) {
400
2.45k
        h2o_doublebuffer_consume(&self->sending);
401
2.45k
    } else {
402
0
        assert(h2o_pipe_sender_in_use(&self->pipe_sender));
403
0
        assert(self->pipe_sender.inflight);
404
0
        self->pipe_sender.inflight = 0;
405
0
    }
406
407
2.45k
    if (h2o_pipe_sender_in_use(&self->pipe_sender) && self->sending.buf->size == 0) {
408
0
        do_send_from_pipe(self);
409
2.45k
    } else {
410
2.45k
        do_send(self);
411
2.45k
        if (!(self->res_done || self->had_body_error))
412
1.97k
            self->client->update_window(self->client);
413
2.45k
    }
414
2.45k
}
415
416
static void copy_stats(struct rp_generator_t *self)
417
9.41k
{
418
9.41k
    self->src_req->proxy_stats.timestamps = self->client->timings;
419
9.41k
    self->src_req->proxy_stats.bytes_written.total = self->client->bytes_written.total;
420
9.41k
    self->src_req->proxy_stats.bytes_written.header = self->client->bytes_written.header;
421
9.41k
    self->src_req->proxy_stats.bytes_written.body = self->client->bytes_written.body;
422
9.41k
    self->src_req->proxy_stats.bytes_read.total = self->client->bytes_read.total;
423
9.41k
    self->src_req->proxy_stats.bytes_read.header = self->client->bytes_read.header;
424
9.41k
    self->src_req->proxy_stats.bytes_read.body = self->client->bytes_read.body;
425
9.41k
}
426
427
static void on_body_on_close(struct rp_generator_t *self, const char *errstr)
428
2.47k
{
429
2.47k
    copy_stats(self);
430
431
    /* detach the content */
432
2.47k
    self->last_content_before_send = *self->client->buf;
433
2.47k
    h2o_buffer_init(self->client->buf, &h2o_socket_buffer_prototype);
434
2.47k
    if (errstr == h2o_httpclient_error_is_eos) {
435
2.43k
        self->res_done = 1;
436
2.43k
        if (self->req_done)
437
2.11k
            detach_client(self);
438
2.43k
    } else {
439
38
        detach_client(self);
440
38
        h2o_req_log_error(self->src_req, "lib/core/proxy.c", "%s", errstr);
441
38
        self->had_body_error = 1;
442
38
        if (self->src_req->proceed_req != NULL)
443
8
            self->src_req->proceed_req(self->src_req, errstr);
444
38
    }
445
2.47k
}
446
447
static int on_body(h2o_httpclient_t *client, const char *errstr, h2o_header_t *trailers, size_t num_trailers)
448
4.98k
{
449
4.98k
    int generator_disposed = 0;
450
4.98k
    struct rp_generator_t *self = client->data;
451
452
4.98k
    self->body_bytes_read = client->bytes_read.body;
453
4.98k
    h2o_timer_unlink(&self->send_headers_timeout);
454
455
4.98k
    if (num_trailers != 0) {
456
0
        assert(errstr == h2o_httpclient_error_is_eos);
457
0
        self->src_req->res.trailers = (h2o_headers_t){trailers, num_trailers, num_trailers};
458
0
    }
459
460
4.98k
    if (errstr != NULL) {
461
        /* Call `on_body_on_close`. This function might dispose `self`, in which case `generator_disposed` would be set to true. */
462
2.47k
        self->generator_disposed = &generator_disposed;
463
2.47k
        on_body_on_close(self, errstr);
464
2.47k
        if (!generator_disposed)
465
2.47k
            self->generator_disposed = NULL;
466
2.47k
    }
467
4.98k
    if (!generator_disposed && !self->sending.inflight)
468
4.48k
        do_send(self);
469
470
4.98k
    return 0;
471
4.98k
}
472
473
static int on_body_piped(h2o_httpclient_t *client, const char *errstr, h2o_header_t *trailers, size_t num_trailers)
474
0
{
475
0
    struct rp_generator_t *self = client->data;
476
477
0
    self->body_bytes_read = client->bytes_read.body;
478
0
    h2o_timer_unlink(&self->send_headers_timeout);
479
480
0
    if (num_trailers != 0) {
481
0
        assert(errstr == h2o_httpclient_error_is_eos);
482
0
        self->src_req->res.trailers = (h2o_headers_t){trailers, num_trailers, num_trailers};
483
0
    }
484
485
0
    if (errstr != NULL)
486
0
        on_body_on_close(self, errstr);
487
0
    if (!self->sending.inflight && !self->pipe_sender.inflight)
488
0
        do_send_from_pipe(self);
489
490
0
    return 0;
491
0
}
492
493
static char compress_hint_to_enum(const char *val, size_t len)
494
0
{
495
0
    if (h2o_lcstris(val, len, H2O_STRLIT("on"))) {
496
0
        return H2O_COMPRESS_HINT_ENABLE;
497
0
    }
498
0
    if (h2o_lcstris(val, len, H2O_STRLIT("off"))) {
499
0
        return H2O_COMPRESS_HINT_DISABLE;
500
0
    }
501
0
    if (h2o_lcstris(val, len, H2O_STRLIT("gzip"))) {
502
0
        return H2O_COMPRESS_HINT_ENABLE_GZIP;
503
0
    }
504
0
    if (h2o_lcstris(val, len, H2O_STRLIT("br"))) {
505
0
        return H2O_COMPRESS_HINT_ENABLE_BR;
506
0
    }
507
0
    if (h2o_lcstris(val, len, H2O_STRLIT("zstd"))) {
508
0
        return H2O_COMPRESS_HINT_ENABLE_ZSTD;
509
0
    }
510
0
    return H2O_COMPRESS_HINT_AUTO;
511
0
}
512
513
static void on_send_headers_timeout(h2o_timer_t *entry)
514
0
{
515
0
    struct rp_generator_t *self = H2O_STRUCT_FROM_MEMBER(struct rp_generator_t, send_headers_timeout, entry);
516
0
    h2o_doublebuffer_prepare_empty(&self->sending);
517
0
    h2o_send(self->src_req, NULL, 0, H2O_SEND_STATE_IN_PROGRESS);
518
0
}
519
520
static h2o_httpclient_body_cb on_head(h2o_httpclient_t *client, const char *errstr, h2o_httpclient_on_head_t *args)
521
3.31k
{
522
3.31k
    struct rp_generator_t *self = client->data;
523
3.31k
    h2o_req_t *req = self->src_req;
524
3.31k
    size_t i;
525
3.31k
    int emit_missing_date_header = req->conn->ctx->globalconf->proxy.emit_missing_date_header;
526
3.31k
    int seen_date_header = 0;
527
528
3.31k
    copy_stats(self);
529
530
3.31k
    if (errstr != NULL && errstr != h2o_httpclient_error_is_eos) {
531
78
        detach_client(self);
532
78
        h2o_req_log_error(req, "lib/core/proxy.c", "%s", errstr);
533
534
78
        if (errstr == h2o_httpclient_error_refused_stream) {
535
0
            req->upstream_refused = 1;
536
0
            static h2o_generator_t generator = {NULL, NULL};
537
0
            h2o_start_response(req, &generator);
538
0
            h2o_send(req, NULL, 0, H2O_SEND_STATE_ERROR);
539
78
        } else {
540
78
            h2o_send_error_502(req, "Gateway Error", errstr, 0);
541
78
            if (self->src_req->proceed_req != NULL)
542
7
                self->src_req->proceed_req(self->src_req, h2o_httpclient_error_refused_stream);
543
78
        }
544
545
78
        return NULL;
546
78
    }
547
548
    /* copy the response (note: all the headers must be copied; http1client discards the input once we return from this callback) */
549
3.23k
    req->res.status = args->status;
550
3.23k
    req->res.reason = h2o_strdup(&req->pool, args->msg.base, args->msg.len).base;
551
6.46k
    for (i = 0; i != args->num_headers; ++i) {
552
3.23k
        h2o_iovec_t value = args->headers[i].value;
553
3.23k
        if (h2o_iovec_is_token(args->headers[i].name)) {
554
3.23k
            const h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, args->headers[i].name);
555
3.23k
            if (token->flags.proxy_should_drop_for_res) {
556
3.23k
                if (token == H2O_TOKEN_CONNECTION && self->src_req->version < 0x200 && req->overrides != NULL &&
557
2.14k
                    req->overrides->forward_close_connection) {
558
0
                    if (h2o_lcstris(args->headers[i].value.base, args->headers[i].value.len, H2O_STRLIT("close")))
559
0
                        self->src_req->http1_is_persistent = 0;
560
0
                }
561
3.23k
                continue;
562
3.23k
            }
563
0
            if (token == H2O_TOKEN_CONTENT_LENGTH) {
564
0
                if (req->res.content_length != SIZE_MAX ||
565
0
                    (req->res.content_length = h2o_strtosize(args->headers[i].value.base, args->headers[i].value.len)) ==
566
0
                        SIZE_MAX) {
567
0
                    detach_client(self);
568
0
                    h2o_req_log_error(req, "lib/core/proxy.c", "%s", "invalid response from upstream (malformed content-length)");
569
0
                    h2o_send_error_502(req, "Gateway Error", "invalid response from upstream", 0);
570
0
                    if (self->src_req->proceed_req != NULL)
571
0
                        self->src_req->proceed_req(self->src_req, h2o_httpclient_error_io);
572
0
                    return NULL;
573
0
                }
574
0
                goto Skip;
575
0
            } else if (token == H2O_TOKEN_LOCATION) {
576
0
                if (req->res_is_delegated && (300 <= args->status && args->status <= 399) && args->status != 304) {
577
0
                    detach_client(self);
578
0
                    h2o_iovec_t method = h2o_get_redirect_method(req->method, args->status);
579
0
                    h2o_send_redirect_internal(req, method, args->headers[i].value.base, args->headers[i].value.len, 1);
580
0
                    return NULL;
581
0
                }
582
0
                if (req->overrides != NULL && req->overrides->location_rewrite.match != NULL) {
583
0
                    h2o_iovec_t new_value =
584
0
                        rewrite_location(&req->pool, value.base, value.len, req->overrides->location_rewrite.match,
585
0
                                         req->input.scheme, req->input.authority, req->overrides->location_rewrite.path_prefix);
586
0
                    if (new_value.base != NULL) {
587
0
                        value = new_value;
588
0
                        goto AddHeader;
589
0
                    }
590
0
                }
591
0
            } else if (token == H2O_TOKEN_LINK) {
592
0
                value = h2o_push_path_in_link_header(req, value.base, value.len);
593
0
                if (!value.len)
594
0
                    goto Skip;
595
0
            } else if (token == H2O_TOKEN_SERVER) {
596
0
                if (!req->conn->ctx->globalconf->proxy.preserve_server_header)
597
0
                    goto Skip;
598
0
            } else if (token == H2O_TOKEN_X_COMPRESS_HINT) {
599
0
                req->compress_hint = compress_hint_to_enum(value.base, value.len);
600
0
                goto Skip;
601
0
            } else if (token == H2O_TOKEN_DATE) {
602
0
                seen_date_header = 1;
603
0
            }
604
0
            if (args->header_requires_dup)
605
0
                value = h2o_strdup(&req->pool, value.base, value.len);
606
0
        AddHeader:
607
0
            h2o_add_header(&req->pool, &req->res.headers, token, args->headers[i].orig_name, value.base, value.len);
608
0
        Skip:;
609
0
        } else {
610
0
            h2o_iovec_t name = *args->headers[i].name;
611
0
            if (args->header_requires_dup) {
612
0
                name = h2o_strdup(&req->pool, name.base, name.len);
613
0
                value = h2o_strdup(&req->pool, value.base, value.len);
614
0
            }
615
0
            h2o_add_header_by_str(&req->pool, &req->res.headers, name.base, name.len, 0, args->headers[i].orig_name, value.base,
616
0
                                  value.len);
617
0
        }
618
3.23k
    }
619
620
3.23k
    if (!seen_date_header && emit_missing_date_header)
621
3.23k
        h2o_resp_add_date_header(req);
622
623
    /* extended CONNECT: adjust response based on the HTTP versions being used (TODO proper check of status code based on upstream
624
     * HTTP version) */
625
3.23k
    if (req->upgrade.base != NULL && (req->res.status == 101 || (200 <= req->res.status && req->res.status <= 299))) {
626
3
        assert(req->is_tunnel_req);
627
3
        if (req->version < 0x200) {
628
3
            req->res.status = 101;
629
3
            h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_UPGRADE, NULL, req->upgrade.base, req->upgrade.len);
630
3
        } else {
631
0
            req->res.status = 200;
632
0
        }
633
3
    }
634
635
    /* declare the start of the response */
636
3.23k
    h2o_start_response(req, &self->super);
637
638
3.23k
    if (errstr == h2o_httpclient_error_is_eos) {
639
724
        self->res_done = 1;
640
724
        if (self->req_done)
641
502
            detach_client(self);
642
724
        h2o_send(req, NULL, 0, H2O_SEND_STATE_FINAL);
643
724
        return NULL; /* TODO this returning NULL causes keepalive to be disabled in http1client. is this what we intended? */
644
724
    }
645
646
    /* switch to using pipe reader, if the opportunity is provided */
647
2.51k
    if (args->pipe_reader != NULL) {
648
0
        if (h2o_pipe_sender_start(req->conn->ctx, &self->pipe_sender)) {
649
0
            args->pipe_reader->fd = self->pipe_sender.fds[1];
650
0
            args->pipe_reader->on_body_piped = on_body_piped;
651
0
        } else {
652
0
            h2o_req_log_error(req, "lib/core/proxy.c", "failed to allocate zero-copy pipe; falling back to read/write");
653
0
        }
654
0
    }
655
656
    /* if httpclient has no received body at this time, immediately send only headers using zero timeout */
657
2.51k
    h2o_timer_link(req->conn->ctx->loop, 0, &self->send_headers_timeout);
658
659
2.51k
    return on_body;
660
3.23k
}
661
662
static int on_informational(h2o_httpclient_t *client, int version, int status, h2o_iovec_t msg, h2o_header_t *headers,
663
                            size_t num_headers)
664
0
{
665
0
    struct rp_generator_t *self = client->data;
666
0
    size_t i;
667
668
0
    for (i = 0; i != num_headers; ++i) {
669
0
        if (headers[i].name == &H2O_TOKEN_LINK->buf)
670
0
            h2o_push_path_in_link_header(self->src_req, headers[i].value.base, headers[i].value.len);
671
0
    }
672
673
0
    assert(status != 101 && "101 has to be notified as final");
674
675
0
    if (status != 100 ||
676
0
        (self->src_req->overrides != NULL && self->src_req->overrides->proxy_expect_mode == H2O_PROXY_EXPECT_FORWARD)) {
677
0
        self->src_req->res.status = status;
678
0
        self->src_req->res.headers = (h2o_headers_t){headers, num_headers, num_headers};
679
0
        h2o_send_informational(self->src_req);
680
0
    } else {
681
        /* we don't need to forward 100 since protocol handlers have already done */
682
0
    }
683
684
0
    return 0;
685
0
}
686
687
static void proceed_request(h2o_httpclient_t *client, const char *errstr)
688
1.56k
{
689
1.56k
    struct rp_generator_t *self = client->data;
690
1.56k
    if (self == NULL)
691
280
        return;
692
1.28k
    if (errstr != NULL)
693
43
        detach_client(self);
694
1.28k
    if (self->src_req->proceed_req != NULL)
695
1.08k
        self->src_req->proceed_req(self->src_req, errstr);
696
1.28k
}
697
698
static int write_req(void *ctx, int is_end_stream)
699
705
{
700
705
    struct rp_generator_t *self = ctx;
701
705
    h2o_httpclient_t *client = self->client;
702
705
    h2o_iovec_t chunk = self->src_req->entity;
703
704
705
    assert(chunk.len != 0 || is_end_stream);
705
706
705
    if (client == NULL) {
707
0
        return -1;
708
0
    }
709
710
705
    if (is_end_stream) {
711
557
        self->src_req->write_req.cb = NULL;
712
557
        self->req_done = 1;
713
557
        if (self->res_done)
714
280
            detach_client(self);
715
557
    }
716
717
705
    return client->write_req(client, chunk, is_end_stream);
718
705
}
719
720
static h2o_httpclient_head_cb on_connect(h2o_httpclient_t *client, const char *errstr, h2o_iovec_t *method, h2o_url_t *url,
721
                                         const h2o_header_t **headers, size_t *num_headers, h2o_iovec_t *body,
722
                                         h2o_httpclient_proceed_req_cb *proceed_req_cb, h2o_httpclient_properties_t *props,
723
                                         h2o_url_t *origin)
724
3.63k
{
725
3.63k
    struct rp_generator_t *self = client->data;
726
3.63k
    h2o_req_t *req = self->src_req;
727
3.63k
    int use_proxy_protocol = 0, reprocess_if_too_early = 0;
728
729
3.63k
    copy_stats(self);
730
731
3.63k
    if (errstr != NULL) {
732
0
        detach_client(self);
733
0
        h2o_req_log_error(self->src_req, "lib/core/proxy.c", "%s", errstr);
734
0
        h2o_send_error_502(self->src_req, "Gateway Error", errstr, 0);
735
0
        return NULL;
736
0
    }
737
738
3.63k
    assert(origin != NULL);
739
740
3.63k
    if (req->overrides != NULL) {
741
3.63k
        use_proxy_protocol = req->overrides->use_proxy_protocol;
742
3.63k
        props->send_own_expect = req->overrides->proxy_expect_mode == H2O_PROXY_EXPECT_ON;
743
3.63k
        req->overrides->location_rewrite.match = origin;
744
3.63k
        if (!req->overrides->proxy_preserve_host) {
745
3.63k
            req->scheme = origin->scheme;
746
3.63k
            req->authority = origin->authority;
747
3.63k
        }
748
3.63k
        h2o_iovec_t append = req->path;
749
3.63k
        if (origin->path.base[origin->path.len - 1] == '/' && append.base[0] == '/') {
750
0
            append.base += 1;
751
0
            append.len -= 1;
752
0
        }
753
3.63k
        req->path = h2o_concat(&req->pool, origin->path, append);
754
3.63k
        int has_null_char;
755
3.63k
        req->path_normalized =
756
3.63k
            h2o_url_normalize_path(&req->pool, req->path.base, req->path.len, &req->query_at, &req->norm_indexes, &has_null_char);
757
3.63k
        req->path_normalized_has_null_char = has_null_char;
758
3.63k
    }
759
760
3.63k
    reprocess_if_too_early = h2o_conn_is_early_data(req->conn);
761
3.63k
    h2o_headers_t headers_vec = (h2o_headers_t){NULL};
762
3.63k
    build_request(req, method, url, &headers_vec, props,
763
3.63k
                  !use_proxy_protocol && h2o_socketpool_can_keepalive(client->connpool->socketpool), self->client->upgrade_to,
764
3.63k
                  use_proxy_protocol, &reprocess_if_too_early, origin);
765
3.63k
    *headers = headers_vec.entries;
766
3.63k
    *num_headers = headers_vec.size;
767
768
3.63k
    if (reprocess_if_too_early)
769
0
        req->reprocess_if_too_early = 1;
770
771
3.63k
    *body = h2o_iovec_init(NULL, 0);
772
3.63k
    *proceed_req_cb = NULL;
773
3.63k
    self->req_done = 1;
774
3.63k
    if (self->src_req->entity.base != NULL) {
775
1.14k
        *body = self->src_req->entity;
776
1.14k
        if (self->src_req->proceed_req != NULL) {
777
965
            *proceed_req_cb = proceed_request;
778
965
            self->src_req->write_req.cb = write_req;
779
965
            self->src_req->write_req.ctx = self;
780
965
            self->req_done = 0;
781
965
        }
782
1.14k
    }
783
3.63k
    self->client->informational_cb = on_informational;
784
785
3.63k
    client->get_conn_properties(client, &req->proxy_stats.conn);
786
787
3.63k
    { /* indicate to httpclient if use of pipe is preferred */
788
3.63k
        h2o_conn_t *conn = self->src_req->conn;
789
3.63k
        switch (conn->ctx->globalconf->proxy.zerocopy) {
790
0
        case H2O_PROXY_ZEROCOPY_ALWAYS:
791
0
            props->prefer_pipe_reader = 1;
792
0
            break;
793
3.63k
        case H2O_PROXY_ZEROCOPY_ENABLED:
794
3.63k
            if (conn->callbacks->can_zerocopy != NULL && conn->callbacks->can_zerocopy(conn))
795
2.21k
                props->prefer_pipe_reader = 1;
796
3.63k
            break;
797
0
        default:
798
0
            break;
799
3.63k
        }
800
3.63k
    }
801
802
3.63k
    return on_head;
803
3.63k
}
804
805
static void on_generator_dispose(void *_self)
806
3.69k
{
807
3.69k
    struct rp_generator_t *self = _self;
808
3.69k
    do_close(self);
809
810
3.69k
    if (self->last_content_before_send != NULL) {
811
2.47k
        h2o_buffer_dispose(&self->last_content_before_send);
812
2.47k
    }
813
3.69k
    h2o_doublebuffer_dispose(&self->sending);
814
3.69k
    if (self->generator_disposed != NULL)
815
0
        *self->generator_disposed = 1;
816
3.69k
}
817
818
static struct rp_generator_t *proxy_send_prepare(h2o_req_t *req)
819
3.69k
{
820
3.69k
    struct rp_generator_t *self = h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose);
821
822
3.69k
    self->super.proceed = do_proceed;
823
3.69k
    self->super.stop = do_stop;
824
3.69k
    self->src_req = req;
825
3.69k
    self->generator_disposed = NULL;
826
3.69k
    self->client = NULL; /* when connection establish timeouts, self->client remains unset by `h2o_httpclient_connect` */
827
3.69k
    self->had_body_error = 0;
828
3.69k
    self->up_req.is_head = h2o_memis(req->method.base, req->method.len, H2O_STRLIT("HEAD"));
829
3.69k
    self->last_content_before_send = NULL;
830
3.69k
    h2o_doublebuffer_init(&self->sending, &h2o_socket_buffer_prototype);
831
3.69k
    memset(&req->proxy_stats, 0, sizeof(req->proxy_stats));
832
3.69k
    h2o_timer_init(&self->send_headers_timeout, on_send_headers_timeout);
833
3.69k
    self->req_done = 0;
834
3.69k
    self->res_done = 0;
835
3.69k
    h2o_pipe_sender_init(&self->pipe_sender);
836
3.69k
    self->body_bytes_read = 0;
837
838
3.69k
    return self;
839
3.69k
}
840
841
void h2o__proxy_process_request(h2o_req_t *req)
842
3.69k
{
843
3.69k
    h2o_req_overrides_t *overrides = req->overrides;
844
3.69k
    h2o_httpclient_ctx_t *client_ctx = get_client_ctx(req);
845
3.69k
    h2o_url_t target_buf, *target = &target_buf;
846
847
3.69k
    h2o_httpclient_connection_pool_t *connpool = &req->conn->ctx->proxy.connpool;
848
3.69k
    if (overrides != NULL && overrides->connpool != NULL) {
849
3.69k
        connpool = overrides->connpool;
850
3.69k
        if (!overrides->proxy_preserve_host)
851
3.69k
            target = NULL;
852
3.69k
    }
853
3.69k
    if (target == &target_buf && h2o_url_init(&target_buf, req->scheme, req->authority, h2o_iovec_init(H2O_STRLIT("/"))) != 0) {
854
0
        h2o_send_error_400(req, "Invalid Request", "Invalid Request", H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION);
855
0
        return;
856
0
    }
857
858
3.69k
    const char *upgrade_to = NULL;
859
3.69k
    if (req->is_tunnel_req) {
860
8
        if (req->upgrade.base != NULL) {
861
            /* Upgrade requests (e.g. websocket) are either tunnelled, rejected, or converted to an ordinary request depending on
862
             * the configuration. */
863
8
            if (client_ctx->tunnel_enabled) {
864
                /* Support for H3_DATAGRAM is advertised by the HTTP/3 handler but the proxy handler does not support forwarding
865
                 * datagrams nor conversion to/from capsules. Hence we send 421 to let the client retry using a different version of
866
                 * HTTP. */
867
0
                if (req->version == 0x300 && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("connect-udp"))) {
868
0
                    h2o_send_error_421(req, "Misdirected Request", "connect-udp tunneling is only supported in HTTP/1 and 2", 0);
869
0
                    return;
870
0
                }
871
0
                upgrade_to = h2o_strdup(&req->pool, req->upgrade.base, req->upgrade.len).base;
872
8
            } else {
873
                /* When recieving a websocket request over HTTP/1.x but tunneling is disabled, convert the request to an ordinary
874
                 * HTTP request, as we have always done. Otherwise, refuse the request. */
875
8
                if (!(req->version < 0x200 && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("websocket")))) {
876
5
                    h2o_send_error_403(req, "Forbidden", "The proxy act as a gateway.", H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION);
877
5
                    return;
878
5
                }
879
8
            }
880
8
        } else {
881
            /* CONNECT request; process as a CONNECT upgrade or reject */
882
0
            if (client_ctx->tunnel_enabled) {
883
0
                upgrade_to = h2o_httpclient_upgrade_to_connect;
884
0
            } else {
885
0
                h2o_send_error_405(req, "Method Not Allowed", "refusing CONNECT", H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION);
886
0
                return;
887
0
            }
888
0
        }
889
8
    }
890
3.69k
    struct rp_generator_t *self = proxy_send_prepare(req);
891
892
    /*
893
      When the PROXY protocol is being used (i.e. when overrides->use_proxy_protocol is set), the client needs to establish a new
894
     connection even when there is a pooled connection to the peer, since the header (as defined in
895
     https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) needs to be sent at the beginning of the connection.
896
897
     However, currently h2o_http1client_connect doesn't provide an interface to enforce estabilishing a new connection. In other
898
     words, there is a chance that we would use a pool connection here.
899
900
     OTOH, the probability of seeing such issue is rare; it would only happen if the same destination identified by its host:port is
901
     accessed in both ways (i.e. in one path with use_proxy_protocol set and in the other path without).
902
903
     So I leave this as it is for the time being.
904
     */
905
3.69k
    h2o_httpclient_connect(&self->client, &req->pool, self, client_ctx, connpool, target, upgrade_to, on_connect);
906
3.69k
}