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