blob: 52d332e38b4527652f7de740f6dc625ef3cc6396 [file] [log] [blame]
William Lallemand83614a92021-08-13 14:47:57 +02001/*
2 * HTTP Client
3 *
4 * Copyright (C) 2021 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 * This file implements an HTTP Client API.
12 *
13 */
William Lallemand83614a92021-08-13 14:47:57 +020014
William Lallemand2a8fe8b2021-08-20 14:25:15 +020015#include <haproxy/api.h>
William Lallemand33b0d092021-08-13 16:05:53 +020016#include <haproxy/applet.h>
17#include <haproxy/cli.h>
William Lallemandcf5cb0b2022-04-22 14:48:45 +020018#include <haproxy/ssl_ckch.h>
William Lallemand33b0d092021-08-13 16:05:53 +020019#include <haproxy/dynbuf.h>
William Lallemand83614a92021-08-13 14:47:57 +020020#include <haproxy/cfgparse.h>
William Lallemand83614a92021-08-13 14:47:57 +020021#include <haproxy/global.h>
William Lallemand0da616e2021-10-28 15:34:26 +020022#include <haproxy/istbuf.h>
William Lallemand33b0d092021-08-13 16:05:53 +020023#include <haproxy/h1_htx.h>
24#include <haproxy/http.h>
William Lallemand2b7dc4e2022-02-24 16:55:41 +010025#include <haproxy/http_ana-t.h>
William Lallemand33b0d092021-08-13 16:05:53 +020026#include <haproxy/http_client.h>
27#include <haproxy/http_htx.h>
William Lallemand5392ff62022-04-28 16:55:02 +020028#include <haproxy/http_rules.h>
William Lallemand33b0d092021-08-13 16:05:53 +020029#include <haproxy/htx.h>
William Lallemand83614a92021-08-13 14:47:57 +020030#include <haproxy/log.h>
31#include <haproxy/proxy.h>
William Lallemand5392ff62022-04-28 16:55:02 +020032#include <haproxy/resolvers.h>
Willy Tarreau5edca2f2022-05-27 09:25:10 +020033#include <haproxy/sc_strm.h>
William Lallemand2a8fe8b2021-08-20 14:25:15 +020034#include <haproxy/server.h>
Willy Tarreaudf3231c2022-09-02 09:02:21 +020035#include <haproxy/ssl_sock.h>
William Lallemand7f1df8f2022-04-14 17:50:20 +020036#include <haproxy/sock_inet.h>
Willy Tarreaucb086c62022-05-27 09:47:12 +020037#include <haproxy/stconn.h>
William Lallemand83614a92021-08-13 14:47:57 +020038#include <haproxy/tools.h>
39
40#include <string.h>
41
William Lallemand83614a92021-08-13 14:47:57 +020042static struct proxy *httpclient_proxy;
William Lallemand6fce46a2022-05-04 14:53:41 +020043
William Lallemand957ab132021-08-24 18:33:28 +020044#ifdef USE_OPENSSL
William Lallemand6fce46a2022-05-04 14:53:41 +020045/* if the httpclient is not configured, error are ignored and features are limited */
46static int hard_error_ssl = 0;
William Lallemandf1344b32022-04-26 12:00:06 +020047static int httpclient_ssl_verify = SSL_SOCK_VERIFY_REQUIRED;
William Lallemand683fbb82022-05-04 15:43:01 +020048static char *httpclient_ssl_ca_file = NULL;
William Lallemand957ab132021-08-24 18:33:28 +020049#endif
William Lallemand33b0d092021-08-13 16:05:53 +020050static struct applet httpclient_applet;
51
William Lallemand7c5a7ef2022-05-04 15:59:44 +020052/* if the httpclient is not configured, error are ignored and features are limited */
William Lallemand8a734cb2022-05-04 16:10:47 +020053static int hard_error_resolvers = 0;
54static char *resolvers_id = NULL;
William Lallemand7c5a7ef2022-05-04 15:59:44 +020055static char *resolvers_prefer = NULL;
William Lallemande279f592023-05-11 21:08:38 +020056static int resolvers_disabled = 0;
William Lallemandeaa703e2022-04-22 17:52:33 +020057
William Lallemand4ad693e2023-09-05 15:55:04 +020058static int httpclient_retries = CONN_RETRIES;
William Lallemandb9ed1572023-09-05 16:42:27 +020059static int httpclient_timeout_connect = MS_TO_TICKS(5000);
William Lallemand4ad693e2023-09-05 15:55:04 +020060
William Lallemand03a4eb12021-08-18 16:46:21 +020061/* --- This part of the file implement an HTTP client over the CLI ---
62 * The functions will be starting by "hc_cli" for "httpclient cli"
63 */
64
Willy Tarreau89a7c412022-05-05 19:38:21 +020065/* the CLI context for the httpclient command */
66struct hcli_svc_ctx {
67 struct httpclient *hc; /* the httpclient instance */
68 uint flags; /* flags from HC_CLI_F_* above */
69};
William Lallemand03a4eb12021-08-18 16:46:21 +020070
71/* These are the callback used by the HTTP Client when it needs to notify new
Willy Tarreau89a7c412022-05-05 19:38:21 +020072 * data, we only sets a flag in the IO handler via the svcctx.
73 */
William Lallemand03a4eb12021-08-18 16:46:21 +020074void hc_cli_res_stline_cb(struct httpclient *hc)
75{
76 struct appctx *appctx = hc->caller;
Willy Tarreau89a7c412022-05-05 19:38:21 +020077 struct hcli_svc_ctx *ctx;
William Lallemand03a4eb12021-08-18 16:46:21 +020078
William Lallemanddfc3f892021-08-20 11:35:29 +020079 if (!appctx)
80 return;
81
Willy Tarreau89a7c412022-05-05 19:38:21 +020082 ctx = appctx->svcctx;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +010083 ctx->flags |= HC_F_RES_STLINE;
William Lallemanddfc3f892021-08-20 11:35:29 +020084 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020085}
86
87void hc_cli_res_headers_cb(struct httpclient *hc)
88{
89 struct appctx *appctx = hc->caller;
Willy Tarreau89a7c412022-05-05 19:38:21 +020090 struct hcli_svc_ctx *ctx;
William Lallemand03a4eb12021-08-18 16:46:21 +020091
William Lallemanddfc3f892021-08-20 11:35:29 +020092 if (!appctx)
93 return;
94
Willy Tarreau89a7c412022-05-05 19:38:21 +020095 ctx = appctx->svcctx;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +010096 ctx->flags |= HC_F_RES_HDR;
William Lallemanddfc3f892021-08-20 11:35:29 +020097 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020098}
99
100void hc_cli_res_body_cb(struct httpclient *hc)
101{
102 struct appctx *appctx = hc->caller;
Willy Tarreau89a7c412022-05-05 19:38:21 +0200103 struct hcli_svc_ctx *ctx;
William Lallemand03a4eb12021-08-18 16:46:21 +0200104
William Lallemanddfc3f892021-08-20 11:35:29 +0200105 if (!appctx)
106 return;
107
Willy Tarreau89a7c412022-05-05 19:38:21 +0200108 ctx = appctx->svcctx;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100109 ctx->flags |= HC_F_RES_BODY;
William Lallemanddfc3f892021-08-20 11:35:29 +0200110 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +0200111}
112
113void hc_cli_res_end_cb(struct httpclient *hc)
114{
115 struct appctx *appctx = hc->caller;
Willy Tarreau89a7c412022-05-05 19:38:21 +0200116 struct hcli_svc_ctx *ctx;
William Lallemand03a4eb12021-08-18 16:46:21 +0200117
William Lallemanddfc3f892021-08-20 11:35:29 +0200118 if (!appctx)
119 return;
120
Willy Tarreau89a7c412022-05-05 19:38:21 +0200121 ctx = appctx->svcctx;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100122 ctx->flags |= HC_F_RES_END;
William Lallemanddfc3f892021-08-20 11:35:29 +0200123 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +0200124}
125
126/*
127 * Parse an httpclient keyword on the cli:
128 * httpclient <ID> <method> <URI>
129 */
130static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
131{
Willy Tarreau89a7c412022-05-05 19:38:21 +0200132 struct hcli_svc_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
William Lallemand03a4eb12021-08-18 16:46:21 +0200133 struct httpclient *hc;
134 char *err = NULL;
135 enum http_meth_t meth;
136 char *meth_str;
137 struct ist uri;
William Lallemanddec25c32021-10-25 19:48:37 +0200138 struct ist body = IST_NULL;
William Lallemand03a4eb12021-08-18 16:46:21 +0200139
140 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
141 return 1;
142
143 if (!*args[1] || !*args[2]) {
144 memprintf(&err, ": not enough parameters");
145 goto err;
146 }
147
148 meth_str = args[1];
149 uri = ist(args[2]);
150
William Lallemanddec25c32021-10-25 19:48:37 +0200151 if (payload)
152 body = ist(payload);
153
William Lallemand03a4eb12021-08-18 16:46:21 +0200154 meth = find_http_meth(meth_str, strlen(meth_str));
155
156 hc = httpclient_new(appctx, meth, uri);
157 if (!hc) {
158 goto err;
159 }
160
161 /* update the httpclient callbacks */
162 hc->ops.res_stline = hc_cli_res_stline_cb;
163 hc->ops.res_headers = hc_cli_res_headers_cb;
164 hc->ops.res_payload = hc_cli_res_body_cb;
165 hc->ops.res_end = hc_cli_res_end_cb;
166
Willy Tarreau89a7c412022-05-05 19:38:21 +0200167 ctx->hc = hc; /* store the httpclient ptr in the applet */
168 ctx->flags = 0;
William Lallemand03a4eb12021-08-18 16:46:21 +0200169
William Lallemandbad9c8c2022-01-14 14:10:33 +0100170 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, body) != ERR_NONE)
William Lallemand03a4eb12021-08-18 16:46:21 +0200171 goto err;
172
173
174 if (!httpclient_start(hc))
175 goto err;
176
177 return 0;
178
179err:
180 memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : "");
181 return cli_err(appctx, err);
182}
183
184/* This function dumps the content of the httpclient receive buffer
185 * on the CLI output
186 *
187 * Return 1 when the processing is finished
188 * return 0 if it needs to be called again
189 */
190static int hc_cli_io_handler(struct appctx *appctx)
191{
Willy Tarreau89a7c412022-05-05 19:38:21 +0200192 struct hcli_svc_ctx *ctx = appctx->svcctx;
Willy Tarreauc12b3212022-05-27 11:08:15 +0200193 struct stconn *sc = appctx_sc(appctx);
Willy Tarreau89a7c412022-05-05 19:38:21 +0200194 struct httpclient *hc = ctx->hc;
William Lallemand03a4eb12021-08-18 16:46:21 +0200195 struct http_hdr *hdrs, *hdr;
196
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100197 if (ctx->flags & HC_F_RES_STLINE) {
Christopher Faulet0158bb22022-06-01 17:08:19 +0200198 chunk_printf(&trash, "%.*s %d %.*s\n", (unsigned int)istlen(hc->res.vsn), istptr(hc->res.vsn),
199 hc->res.status, (unsigned int)istlen(hc->res.reason), istptr(hc->res.reason));
200 if (applet_putchk(appctx, &trash) == -1)
201 goto more;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100202 ctx->flags &= ~HC_F_RES_STLINE;
William Lallemand03a4eb12021-08-18 16:46:21 +0200203 }
204
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100205 if (ctx->flags & HC_F_RES_HDR) {
Christopher Faulet0158bb22022-06-01 17:08:19 +0200206 chunk_reset(&trash);
William Lallemand03a4eb12021-08-18 16:46:21 +0200207 hdrs = hc->res.hdrs;
208 for (hdr = hdrs; isttest(hdr->v); hdr++) {
Christopher Faulet0158bb22022-06-01 17:08:19 +0200209 if (!h1_format_htx_hdr(hdr->n, hdr->v, &trash))
210 goto too_many_hdrs;
William Lallemand03a4eb12021-08-18 16:46:21 +0200211 }
Christopher Faulet0158bb22022-06-01 17:08:19 +0200212 if (!chunk_memcat(&trash, "\r\n", 2))
213 goto too_many_hdrs;
214 if (applet_putchk(appctx, &trash) == -1)
215 goto more;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100216 ctx->flags &= ~HC_F_RES_HDR;
William Lallemand03a4eb12021-08-18 16:46:21 +0200217 }
218
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100219 if (ctx->flags & HC_F_RES_BODY) {
William Lallemand03a4eb12021-08-18 16:46:21 +0200220 int ret;
221
Willy Tarreau475e4632022-05-27 10:26:46 +0200222 ret = httpclient_res_xfer(hc, sc_ib(sc));
223 channel_add_input(sc_ic(sc), ret); /* forward what we put in the buffer channel */
William Lallemand03a4eb12021-08-18 16:46:21 +0200224
Christopher Faulet0158bb22022-06-01 17:08:19 +0200225 /* remove the flag if the buffer was emptied */
226 if (httpclient_data(hc))
227 goto more;
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100228 ctx->flags &= ~HC_F_RES_BODY;
William Lallemand03a4eb12021-08-18 16:46:21 +0200229 }
230
231 /* we must close only if F_END is the last flag */
Remi Tricot-Le Breton95e7cf12022-12-20 11:11:03 +0100232 if (ctx->flags == HC_F_RES_END) {
233 ctx->flags &= ~HC_F_RES_END;
Christopher Faulet89f26262022-06-01 17:17:24 +0200234 goto end;
William Lallemand03a4eb12021-08-18 16:46:21 +0200235 }
236
Christopher Faulet0158bb22022-06-01 17:08:19 +0200237more:
238 if (!ctx->flags)
239 applet_have_no_more_data(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +0200240 return 0;
Christopher Faulet89f26262022-06-01 17:17:24 +0200241end:
242 return 1;
Christopher Faulet0158bb22022-06-01 17:08:19 +0200243
244too_many_hdrs:
245 return cli_err(appctx, "Too many headers.\n");
William Lallemand03a4eb12021-08-18 16:46:21 +0200246}
247
248static void hc_cli_release(struct appctx *appctx)
249{
Willy Tarreau89a7c412022-05-05 19:38:21 +0200250 struct hcli_svc_ctx *ctx = appctx->svcctx;
251 struct httpclient *hc = ctx->hc;
William Lallemand03a4eb12021-08-18 16:46:21 +0200252
253 /* Everything possible was printed on the CLI, we can destroy the client */
William Lallemandecb83e12021-09-28 11:00:43 +0200254 httpclient_stop_and_destroy(hc);
William Lallemand03a4eb12021-08-18 16:46:21 +0200255
256 return;
257}
258
259/* register cli keywords */
260static struct cli_kw_list cli_kws = {{ },{
Willy Tarreau2c8f9842022-02-18 16:26:36 +0100261 { { "httpclient", NULL }, "httpclient <method> <URI> : launch an HTTP request", hc_cli_parse, hc_cli_io_handler, hc_cli_release, NULL, ACCESS_EXPERT},
William Lallemand03a4eb12021-08-18 16:46:21 +0200262 { { NULL }, NULL, NULL, NULL }
263}};
264
265INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
266
267
268/* --- This part of the file implements the actual HTTP client API --- */
269
William Lallemand33b0d092021-08-13 16:05:53 +0200270/*
271 * Generate a simple request and fill the httpclient request buffer with it.
272 * The request contains a request line generated from the absolute <url> and
273 * <meth> as well as list of headers <hdrs>.
274 *
275 * If the buffer was filled correctly the function returns 0, if not it returns
276 * an error_code but there is no guarantee that the buffer wasn't modified.
277 */
William Lallemanddec25c32021-10-25 19:48:37 +0200278int httpclient_req_gen(struct httpclient *hc, const struct ist url, enum http_meth_t meth, const struct http_hdr *hdrs, const struct ist payload)
William Lallemand33b0d092021-08-13 16:05:53 +0200279{
280 struct htx_sl *sl;
281 struct htx *htx;
282 int err_code = 0;
283 struct ist meth_ist, vsn;
William Lallemanddec25c32021-10-25 19:48:37 +0200284 unsigned int flags = HTX_SL_F_VER_11 | HTX_SL_F_NORMALIZED_URI | HTX_SL_F_HAS_SCHM;
William Lallemandf03b53c2021-11-24 15:38:17 +0100285 int i;
William Lallemandbad9c8c2022-01-14 14:10:33 +0100286 int foundhost = 0, foundaccept = 0, foundua = 0;
William Lallemand33b0d092021-08-13 16:05:53 +0200287
Christopher Faulet600985d2022-01-12 11:14:08 +0100288 if (!b_alloc(&hc->req.buf))
289 goto error;
290
William Lallemand33b0d092021-08-13 16:05:53 +0200291 if (meth >= HTTP_METH_OTHER)
292 goto error;
293
294 meth_ist = http_known_methods[meth];
295
296 vsn = ist("HTTP/1.1");
297
298 htx = htx_from_buf(&hc->req.buf);
299 if (!htx)
300 goto error;
William Lallemande1e045f2022-01-14 14:08:34 +0100301
302 if (!hc->ops.req_payload && !isttest(payload))
303 flags |= HTX_SL_F_BODYLESS;
304
William Lallemand33b0d092021-08-13 16:05:53 +0200305 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_ist, url, vsn);
306 if (!sl) {
307 goto error;
308 }
309 sl->info.req.meth = meth;
310
William Lallemandf03b53c2021-11-24 15:38:17 +0100311 for (i = 0; hdrs && hdrs[i].n.len; i++) {
312 /* Don't check the value length because a header value may be empty */
313 if (isttest(hdrs[i].v) == 0)
314 continue;
315
316 if (isteqi(hdrs[i].n, ist("host")))
317 foundhost = 1;
William Lallemandbad9c8c2022-01-14 14:10:33 +0100318 else if (isteqi(hdrs[i].n, ist("accept")))
319 foundaccept = 1;
320 else if (isteqi(hdrs[i].n, ist("user-agent")))
321 foundua = 1;
William Lallemandf03b53c2021-11-24 15:38:17 +0100322
323 if (!htx_add_header(htx, hdrs[i].n, hdrs[i].v))
324 goto error;
325 }
William Lallemand33b0d092021-08-13 16:05:53 +0200326
William Lallemandf03b53c2021-11-24 15:38:17 +0100327 if (!foundhost) {
328 /* Add Host Header from URL */
329 if (!htx_add_header(htx, ist("Host"), ist("h")))
William Lallemand79a34782021-09-20 16:19:15 +0200330 goto error;
William Lallemandf03b53c2021-11-24 15:38:17 +0100331 if (!http_update_host(htx, sl, url))
William Lallemand79a34782021-09-20 16:19:15 +0200332 goto error;
333 }
William Lallemand33b0d092021-08-13 16:05:53 +0200334
William Lallemandbad9c8c2022-01-14 14:10:33 +0100335 if (!foundaccept) {
336 if (!htx_add_header(htx, ist("Accept"), ist("*/*")))
337 goto error;
338 }
339
340 if (!foundua) {
341 if (!htx_add_header(htx, ist("User-Agent"), ist(HTTPCLIENT_USERAGENT)))
342 goto error;
343 }
344
345
William Lallemandf03b53c2021-11-24 15:38:17 +0100346 if (!htx_add_endof(htx, HTX_BLK_EOH))
347 goto error;
348
William Lallemanda80b22e2022-12-22 14:49:43 +0100349 if (isttest(payload) && istlen(payload)) {
William Lallemanddec25c32021-10-25 19:48:37 +0200350 /* add the payload if it can feat in the buffer, no need to set
351 * the Content-Length, the data will be sent chunked */
352 if (!htx_add_data_atonce(htx, payload))
353 goto error;
354 }
355
William Lallemand0da616e2021-10-28 15:34:26 +0200356 /* If req.payload was set, does not set the end of stream which *MUST*
357 * be set in the callback */
358 if (!hc->ops.req_payload)
359 htx->flags |= HTX_FL_EOM;
William Lallemand33b0d092021-08-13 16:05:53 +0200360
361 htx_to_buf(htx, &hc->req.buf);
362
363 return 0;
364error:
365 err_code |= ERR_ALERT | ERR_ABORT;
366 return err_code;
367}
368
369/*
370 * transfer the response to the destination buffer and wakeup the HTTP client
371 * applet so it could fill again its buffer.
372 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500373 * Return the number of bytes transferred.
William Lallemand33b0d092021-08-13 16:05:53 +0200374 */
375int httpclient_res_xfer(struct httpclient *hc, struct buffer *dst)
376{
Willy Tarreau11adb1d2022-02-18 17:28:25 +0100377 size_t room = b_room(dst);
William Lallemand33b0d092021-08-13 16:05:53 +0200378 int ret;
379
Willy Tarreau11adb1d2022-02-18 17:28:25 +0100380 ret = b_force_xfer(dst, &hc->res.buf, MIN(room, b_data(&hc->res.buf)));
William Lallemand33b0d092021-08-13 16:05:53 +0200381 /* call the client once we consumed all data */
Christopher Faulet600985d2022-01-12 11:14:08 +0100382 if (!b_data(&hc->res.buf)) {
383 b_free(&hc->res.buf);
384 if (hc->appctx)
385 appctx_wakeup(hc->appctx);
386 }
William Lallemand33b0d092021-08-13 16:05:53 +0200387 return ret;
388}
389
390/*
William Lallemand0da616e2021-10-28 15:34:26 +0200391 * Transfer raw HTTP payload from src, and insert it into HTX format in the
392 * httpclient.
393 *
394 * Must be used to transfer the request body.
395 * Then wakeup the httpclient so it can transfer it.
396 *
397 * <end> tries to add the ending data flag if it succeed to copy all data.
398 *
399 * Return the number of bytes copied from src.
400 */
401int httpclient_req_xfer(struct httpclient *hc, struct ist src, int end)
402{
403 int ret = 0;
404 struct htx *htx;
405
Christopher Faulet600985d2022-01-12 11:14:08 +0100406 if (!b_alloc(&hc->req.buf))
407 goto error;
408
William Lallemand0da616e2021-10-28 15:34:26 +0200409 htx = htx_from_buf(&hc->req.buf);
410 if (!htx)
411 goto error;
412
413 if (hc->appctx)
414 appctx_wakeup(hc->appctx);
415
416 ret += htx_add_data(htx, src);
417
418
419 /* if we copied all the data and the end flag is set */
420 if ((istlen(src) == ret) && end) {
Christopher Faulet48005de2022-10-14 15:10:24 +0200421 /* no more data are expected. If the HTX buffer is empty, be
422 * sure to add something (EOT block in this case) to have
423 * something to send. It is important to be sure the EOM flags
424 * will be handled by the endpoint. Because the message is
425 * empty, this should not fail. Otherwise it is an error
426 */
427 if (htx_is_empty(htx)) {
428 if (!htx_add_endof(htx, HTX_BLK_EOT))
429 goto error;
430 }
William Lallemand0da616e2021-10-28 15:34:26 +0200431 htx->flags |= HTX_FL_EOM;
432 }
433 htx_to_buf(htx, &hc->req.buf);
434
435error:
436
437 return ret;
438}
439
William Lallemandb4a4ef62022-02-23 14:18:16 +0100440/* Set the 'timeout server' in ms for the next httpclient request */
441void httpclient_set_timeout(struct httpclient *hc, int timeout)
442{
443 hc->timeout_server = timeout;
444}
445
William Lallemand7b2e0ee2022-02-17 19:10:55 +0100446/*
447 * Sets a destination for the httpclient from an HAProxy addr format
448 * This will prevent to determine the destination from the URL
449 * Return 0 in case of success or -1 otherwise.
450 */
451int httpclient_set_dst(struct httpclient *hc, const char *dst)
452{
453 struct sockaddr_storage *sk;
454 char *errmsg = NULL;
455
456 sockaddr_free(&hc->dst);
457 /* 'sk' is statically allocated (no need to be freed). */
458 sk = str2sa_range(dst, NULL, NULL, NULL, NULL, NULL,
459 &errmsg, NULL, NULL,
460 PA_O_PORT_OK | PA_O_STREAM | PA_O_XPRT | PA_O_CONNECT);
461 if (!sk) {
462 ha_alert("httpclient: Failed to parse destination address in %s\n", errmsg);
463 free(errmsg);
464 return -1;
465 }
466
467 if (!sockaddr_alloc(&hc->dst, sk, sizeof(*sk))) {
468 ha_alert("httpclient: Failed to allocate sockaddr in %s:%d.\n", __FUNCTION__, __LINE__);
469 return -1;
470 }
471
472 return 0;
473}
William Lallemand0da616e2021-10-28 15:34:26 +0200474
475/*
Thierry Fournier74a9eb52022-10-10 12:46:38 +0200476 * Split <url> in <scheme>, <host>, <port>
William Lallemand7f1df8f2022-04-14 17:50:20 +0200477 */
Thierry Fournier74a9eb52022-10-10 12:46:38 +0200478static int httpclient_spliturl(struct ist url, enum http_scheme *scheme,
479 struct ist *host, int *port)
William Lallemand7f1df8f2022-04-14 17:50:20 +0200480{
481 enum http_scheme scheme_tmp = SCH_HTTP;
482 int port_tmp = 0;
483 struct ist scheme_ist, authority_ist, host_ist, port_ist;
484 char *p, *end;
485 struct http_uri_parser parser;
486
487 parser = http_uri_parser_init(url);
488 scheme_ist = http_parse_scheme(&parser);
Thierry Fournier74a9eb52022-10-10 12:46:38 +0200489 if (!isttest(scheme_ist)) {
490 return 0;
491 }
William Lallemand7f1df8f2022-04-14 17:50:20 +0200492
493 if (isteqi(scheme_ist, ist("http://"))){
494 scheme_tmp = SCH_HTTP;
495 port_tmp = 80;
496 } else if (isteqi(scheme_ist, ist("https://"))) {
497 scheme_tmp = SCH_HTTPS;
498 port_tmp = 443;
499 }
500
501 authority_ist = http_parse_authority(&parser, 1);
Thierry Fournier74a9eb52022-10-10 12:46:38 +0200502 if (!isttest(authority_ist)) {
503 return 0;
504 }
William Lallemand7f1df8f2022-04-14 17:50:20 +0200505 p = end = istend(authority_ist);
506
507 /* look for a port at the end of the authority */
508 while (p > istptr(authority_ist) && isdigit((unsigned char)*--p))
509 ;
510
511 if (*p == ':') {
512 host_ist = ist2(istptr(authority_ist), p - istptr(authority_ist));
513 port_ist = istnext(ist2(p, end - p));
514 ist2str(trash.area, port_ist);
515 port_tmp = atoi(trash.area);
516 } else {
517 host_ist = authority_ist;
518 }
519
520 if (scheme)
521 *scheme = scheme_tmp;
522 if (host)
523 *host = host_ist;
524 if (port)
525 *port = port_tmp;
526
Thierry Fournier74a9eb52022-10-10 12:46:38 +0200527 return 1;
William Lallemand7f1df8f2022-04-14 17:50:20 +0200528}
529
530/*
William Lallemand33b0d092021-08-13 16:05:53 +0200531 * Start the HTTP client
532 * Create the appctx, session, stream and wakeup the applet
533 *
William Lallemand33b0d092021-08-13 16:05:53 +0200534 * Return the <appctx> or NULL if it failed
535 */
536struct appctx *httpclient_start(struct httpclient *hc)
537{
538 struct applet *applet = &httpclient_applet;
539 struct appctx *appctx;
William Lallemand33b0d092021-08-13 16:05:53 +0200540
William Lallemand5085bc32022-02-17 12:52:09 +0100541 /* if the client was started and not ended, an applet is already
542 * running, we shouldn't try anything */
543 if (httpclient_started(hc) && !httpclient_ended(hc))
544 return NULL;
545
William Lallemand33b0d092021-08-13 16:05:53 +0200546 /* The HTTP client will be created in the same thread as the caller,
547 * avoiding threading issues */
Christopher Faulet6095d572022-05-16 17:09:48 +0200548 appctx = appctx_new_here(applet, NULL);
William Lallemand33b0d092021-08-13 16:05:53 +0200549 if (!appctx)
Christopher Fauleta9e8b392022-03-23 11:01:09 +0100550 goto out;
Christopher Fauletb1e08362022-05-12 15:33:14 +0200551 appctx->svcctx = hc;
552 hc->flags = 0;
William Lallemand7b2e0ee2022-02-17 19:10:55 +0100553
Christopher Fauletb1e08362022-05-12 15:33:14 +0200554 if (appctx_init(appctx) == -1) {
555 ha_alert("httpclient: Failed to initialize appctx %s:%d.\n", __FUNCTION__, __LINE__);
Christopher Faulet92202da2022-05-11 12:22:10 +0200556 goto out_free_appctx;
William Lallemand85332732022-05-04 10:59:51 +0200557 }
558
William Lallemand33b0d092021-08-13 16:05:53 +0200559 return appctx;
560
William Lallemand33b0d092021-08-13 16:05:53 +0200561out_free_appctx:
Christopher Fauletb1e08362022-05-12 15:33:14 +0200562 appctx_free_on_early_error(appctx);
William Lallemand33b0d092021-08-13 16:05:53 +0200563out:
564
565 return NULL;
566}
567
William Lallemandecb83e12021-09-28 11:00:43 +0200568/*
569 * This function tries to destroy the httpclient if it wasn't running.
570 * If it was running, stop the client and ask it to autodestroy itself.
571 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500572 * Once this function is used, all pointer sto the client must be removed
William Lallemandecb83e12021-09-28 11:00:43 +0200573 *
574 */
575void httpclient_stop_and_destroy(struct httpclient *hc)
576{
577
William Lallemandb8b13702021-09-28 12:15:37 +0200578 /* The httpclient was already stopped or never started, we can safely destroy it */
579 if (hc->flags & HTTPCLIENT_FS_ENDED || !(hc->flags & HTTPCLIENT_FS_STARTED)) {
William Lallemandecb83e12021-09-28 11:00:43 +0200580 httpclient_destroy(hc);
581 } else {
Willy Tarreaub4829202022-09-01 20:40:26 +0200582 /* if the client wasn't stopped, ask for a stop and destroy */
William Lallemandecb83e12021-09-28 11:00:43 +0200583 hc->flags |= (HTTPCLIENT_FA_AUTOKILL | HTTPCLIENT_FA_STOP);
Willy Tarreaub4829202022-09-01 20:40:26 +0200584 /* the calling applet doesn't exist anymore */
585 hc->caller = NULL;
William Lallemandecb83e12021-09-28 11:00:43 +0200586 if (hc->appctx)
587 appctx_wakeup(hc->appctx);
588 }
589}
590
William Lallemand33b0d092021-08-13 16:05:53 +0200591/* Free the httpclient */
592void httpclient_destroy(struct httpclient *hc)
593{
William Lallemand03f5a1c2021-09-27 15:17:47 +0200594 struct http_hdr *hdrs;
595
596
William Lallemand33b0d092021-08-13 16:05:53 +0200597 if (!hc)
598 return;
William Lallemandecb83e12021-09-28 11:00:43 +0200599
William Lallemand2a879002021-10-05 15:50:45 +0200600 /* we should never destroy a client which was started but not stopped */
601 BUG_ON(httpclient_started(hc) && !httpclient_ended(hc));
William Lallemandecb83e12021-09-28 11:00:43 +0200602
William Lallemand03f5a1c2021-09-27 15:17:47 +0200603 /* request */
604 istfree(&hc->req.url);
William Lallemand33b0d092021-08-13 16:05:53 +0200605 b_free(&hc->req.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200606 /* response */
607 istfree(&hc->res.vsn);
608 istfree(&hc->res.reason);
609 hdrs = hc->res.hdrs;
610 while (hdrs && isttest(hdrs->n)) {
611 istfree(&hdrs->n);
612 istfree(&hdrs->v);
613 hdrs++;
614 }
615 ha_free(&hc->res.hdrs);
William Lallemand33b0d092021-08-13 16:05:53 +0200616 b_free(&hc->res.buf);
William Lallemand7b2e0ee2022-02-17 19:10:55 +0100617 sockaddr_free(&hc->dst);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200618
William Lallemand33b0d092021-08-13 16:05:53 +0200619 free(hc);
620
621 return;
622}
623
624/* Allocate an httpclient and its buffers
William Lallemand992ad622022-09-12 17:39:04 +0200625 * Use the default httpclient_proxy
626 *
William Lallemand33b0d092021-08-13 16:05:53 +0200627 * Return NULL on failure */
628struct httpclient *httpclient_new(void *caller, enum http_meth_t meth, struct ist url)
629{
630 struct httpclient *hc;
William Lallemand33b0d092021-08-13 16:05:53 +0200631
632 hc = calloc(1, sizeof(*hc));
633 if (!hc)
634 goto err;
635
Christopher Faulet600985d2022-01-12 11:14:08 +0100636 hc->req.buf = BUF_NULL;
637 hc->res.buf = BUF_NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200638 hc->caller = caller;
William Lallemand67b77842021-11-10 16:57:25 +0100639 hc->req.url = istdup(url);
William Lallemand33b0d092021-08-13 16:05:53 +0200640 hc->req.meth = meth;
William Lallemand992ad622022-09-12 17:39:04 +0200641 httpclient_set_proxy(hc, httpclient_proxy);
William Lallemand33b0d092021-08-13 16:05:53 +0200642
643 return hc;
644
645err:
646 httpclient_destroy(hc);
647 return NULL;
648}
649
William Lallemand992ad622022-09-12 17:39:04 +0200650/* Allocate an httpclient and its buffers,
651 * Use the proxy <px>
652 *
653 * Return and httpclient or NULL.
654 */
655struct httpclient *httpclient_new_from_proxy(struct proxy *px, void *caller, enum http_meth_t meth, struct ist url)
656{
657 struct httpclient *hc;
658
659 hc = httpclient_new(caller, meth, url);
660 if (!hc)
661 return NULL;
662
663 httpclient_set_proxy(hc, px);
664
665 return hc;
666}
667
668/*
669 * Configure an httpclient with a specific proxy <px>
670 *
671 * The proxy <px> must contains 2 srv, one configured for clear connections, the other for SSL.
672 *
673 */
674int httpclient_set_proxy(struct httpclient *hc, struct proxy *px)
675{
676 struct server *srv;
677
678 hc->px = px;
679
680 for (srv = px->srv; srv != NULL; srv = srv->next) {
681 if (srv->xprt == xprt_get(XPRT_RAW)) {
682 hc->srv_raw = srv;
683#ifdef USE_OPENSSL
684 } else if (srv->xprt == xprt_get(XPRT_SSL)) {
685 hc->srv_ssl = srv;
686#endif
687 }
688 }
689
690 return 0;
691}
692
William Lallemand33b0d092021-08-13 16:05:53 +0200693static void httpclient_applet_io_handler(struct appctx *appctx)
694{
Willy Tarreau1eea6652022-05-05 20:12:01 +0200695 struct httpclient *hc = appctx->svcctx;
Willy Tarreauc12b3212022-05-27 11:08:15 +0200696 struct stconn *sc = appctx_sc(appctx);
Willy Tarreaub89f8722022-05-27 10:37:32 +0200697 struct stream *s = __sc_strm(sc);
William Lallemand33b0d092021-08-13 16:05:53 +0200698 struct channel *req = &s->req;
699 struct channel *res = &s->res;
700 struct htx_blk *blk = NULL;
701 struct htx *htx;
William Lallemandb7020302021-08-20 11:24:13 +0200702 struct htx_sl *sl = NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200703 uint32_t hdr_num;
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100704 uint32_t sz;
William Lallemand933fe392021-11-04 09:45:58 +0100705 int ret;
William Lallemand33b0d092021-08-13 16:05:53 +0200706
Christopher Faulet1901c1b2023-04-11 07:38:34 +0200707 if (unlikely(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) {
708 if (co_data(res)) {
709 htx = htx_from_buf(&res->buf);
710 co_htx_skip(res, htx, co_data(res));
711 htx_to_buf(htx, &res->buf);
712 }
Christopher Fauletbe08df82023-03-31 11:30:32 +0200713 goto out;
Christopher Faulet1901c1b2023-04-11 07:38:34 +0200714 }
William Lallemanda93eac42022-10-20 18:36:03 +0200715 /* The IO handler could be called after the release, so we need to
716 * check if hc is still there to run the IO handler */
717 if (!hc)
Christopher Fauletbe08df82023-03-31 11:30:32 +0200718 goto out;
William Lallemanda93eac42022-10-20 18:36:03 +0200719
William Lallemand33b0d092021-08-13 16:05:53 +0200720 while (1) {
William Lallemandecb83e12021-09-28 11:00:43 +0200721
722 /* required to stop */
723 if (hc->flags & HTTPCLIENT_FA_STOP)
Christopher Fauletbe08df82023-03-31 11:30:32 +0200724 goto error;
William Lallemandecb83e12021-09-28 11:00:43 +0200725
William Lallemand33b0d092021-08-13 16:05:53 +0200726 switch(appctx->st0) {
727
728 case HTTPCLIENT_S_REQ:
William Lallemanddb8a1f32021-11-08 16:55:14 +0100729 /* we know that the buffer is empty here, since
730 * it's the first call, we can freely copy the
731 * request from the httpclient buffer */
William Lallemand933fe392021-11-04 09:45:58 +0100732 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
Christopher Faulet7b3d38a2023-05-05 11:28:45 +0200733 if (!ret) {
734 sc_need_room(sc, 0);
735 goto out;
736 }
William Lallemand933fe392021-11-04 09:45:58 +0100737
Christopher Faulet600985d2022-01-12 11:14:08 +0100738 if (!b_data(&hc->req.buf))
739 b_free(&hc->req.buf);
740
William Lallemanddb8a1f32021-11-08 16:55:14 +0100741 htx = htx_from_buf(&req->buf);
Christopher Faulet7b3d38a2023-05-05 11:28:45 +0200742 if (!htx) {
743 sc_need_room(sc, 0);
744 goto out;
745 }
William Lallemand933fe392021-11-04 09:45:58 +0100746
William Lallemanddb8a1f32021-11-08 16:55:14 +0100747 channel_add_input(req, htx->data);
748
William Lallemand933fe392021-11-04 09:45:58 +0100749 if (htx->flags & HTX_FL_EOM) /* check if a body need to be added */
750 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
751 else
752 appctx->st0 = HTTPCLIENT_S_REQ_BODY;
753
Christopher Fauletbe08df82023-03-31 11:30:32 +0200754 goto out; /* we need to leave the IO handler once we wrote the request */
755 break;
756
William Lallemand0da616e2021-10-28 15:34:26 +0200757 case HTTPCLIENT_S_REQ_BODY:
758 /* call the payload callback */
759 {
760 if (hc->ops.req_payload) {
William Lallemandccc7ee42022-03-18 17:57:15 +0100761 struct htx *hc_htx;
William Lallemand0da616e2021-10-28 15:34:26 +0200762
William Lallemand0da616e2021-10-28 15:34:26 +0200763 /* call the request callback */
764 hc->ops.req_payload(hc);
William Lallemanddb8a1f32021-11-08 16:55:14 +0100765
Christopher Fauletca90de22023-08-04 14:28:23 +0200766 hc_htx = htxbuf(&hc->req.buf);
William Lallemandccc7ee42022-03-18 17:57:15 +0100767 if (htx_is_empty(hc_htx))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200768 goto out;
Christopher Faulet600985d2022-01-12 11:14:08 +0100769
Christopher Fauletca90de22023-08-04 14:28:23 +0200770 htx = htx_from_buf(&req->buf);
William Lallemandccc7ee42022-03-18 17:57:15 +0100771 if (htx_is_empty(htx)) {
Christopher Fauletdca3b5b2022-04-07 10:47:07 +0200772 size_t data = hc_htx->data;
773
William Lallemandccc7ee42022-03-18 17:57:15 +0100774 /* Here htx_to_buf() will set buffer data to 0 because
775 * the HTX is empty, and allow us to do an xfer.
776 */
777 htx_to_buf(hc_htx, &hc->req.buf);
778 htx_to_buf(htx, &req->buf);
William Lallemandccc7ee42022-03-18 17:57:15 +0100779 b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
Christopher Fauletdca3b5b2022-04-07 10:47:07 +0200780 channel_add_input(req, data);
William Lallemandccc7ee42022-03-18 17:57:15 +0100781 } else {
782 struct htx_ret ret;
Christopher Faulet600985d2022-01-12 11:14:08 +0100783
Christopher Faulet6b4f1f62022-04-29 13:56:12 +0200784 ret = htx_xfer_blks(htx, hc_htx, htx_used_space(hc_htx), HTX_BLK_UNUSED);
William Lallemandccc7ee42022-03-18 17:57:15 +0100785 channel_add_input(req, ret.ret);
William Lallemanddb8a1f32021-11-08 16:55:14 +0100786
William Lallemandccc7ee42022-03-18 17:57:15 +0100787 /* we must copy the EOM if we empty the buffer */
788 if (htx_is_empty(hc_htx)) {
789 htx->flags |= (hc_htx->flags & HTX_FL_EOM);
790 }
791 htx_to_buf(htx, &req->buf);
792 htx_to_buf(hc_htx, &hc->req.buf);
793 }
794
795
796 if (!b_data(&hc->req.buf))
797 b_free(&hc->req.buf);
William Lallemand0da616e2021-10-28 15:34:26 +0200798 }
799
Christopher Fauletca90de22023-08-04 14:28:23 +0200800 htx = htxbuf(&req->buf);
William Lallemand0da616e2021-10-28 15:34:26 +0200801
802 /* if the request contains the HTX_FL_EOM, we finished the request part. */
Christopher Fauletbe08df82023-03-31 11:30:32 +0200803 if (htx->flags & HTX_FL_EOM)
William Lallemand0da616e2021-10-28 15:34:26 +0200804 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
805
William Lallemand1eca8942022-03-17 14:57:23 +0100806 goto process_data; /* we need to leave the IO handler once we wrote the request */
William Lallemand0da616e2021-10-28 15:34:26 +0200807 }
Christopher Fauletbe08df82023-03-31 11:30:32 +0200808 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200809
810 case HTTPCLIENT_S_RES_STLINE:
Christopher Fauletbe08df82023-03-31 11:30:32 +0200811 /* Request is finished, report EOI */
812 se_fl_set(appctx->sedesc, SE_FL_EOI);
813
William Lallemand33b0d092021-08-13 16:05:53 +0200814 /* copy the start line in the hc structure,then remove the htx block */
William Lallemanda625b032022-03-17 14:45:46 +0100815 if (!co_data(res))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200816 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +0200817 htx = htxbuf(&res->buf);
Christopher Fauletca90de22023-08-04 14:28:23 +0200818 if (htx_is_empty(htx))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200819 goto out;
William Lallemand97f69c62022-03-10 17:23:40 +0100820 blk = htx_get_head_blk(htx);
William Lallemand33b0d092021-08-13 16:05:53 +0200821 if (blk && (htx_get_blk_type(blk) == HTX_BLK_RES_SL))
822 sl = htx_get_blk_ptr(htx, blk);
823 if (!sl || (!(sl->flags & HTX_SL_F_IS_RESP)))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200824 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +0200825
826 /* copy the status line in the httpclient */
827 hc->res.status = sl->info.res.status;
828 hc->res.vsn = istdup(htx_sl_res_vsn(sl));
829 hc->res.reason = istdup(htx_sl_res_reason(sl));
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100830 sz = htx_get_blksz(blk);
Christopher Faulet0055d562022-04-29 14:09:03 +0200831 c_rew(res, sz);
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100832 htx_remove_blk(htx, blk);
William Lallemand33b0d092021-08-13 16:05:53 +0200833 /* caller callback */
834 if (hc->ops.res_stline)
835 hc->ops.res_stline(hc);
836
Christopher Fauletca90de22023-08-04 14:28:23 +0200837 htx_to_buf(htx, &res->buf);
838
William Lallemand33b0d092021-08-13 16:05:53 +0200839 /* if there is no HTX data anymore and the EOM flag is
840 * set, leave (no body) */
841 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM)
842 appctx->st0 = HTTPCLIENT_S_RES_END;
843 else
844 appctx->st0 = HTTPCLIENT_S_RES_HDR;
Christopher Fauletca90de22023-08-04 14:28:23 +0200845
William Lallemand33b0d092021-08-13 16:05:53 +0200846 break;
847
848 case HTTPCLIENT_S_RES_HDR:
849 /* first copy the headers in a local hdrs
850 * structure, once we the total numbers of the
851 * header we allocate the right size and copy
852 * them. The htx block of the headers are
853 * removed each time one is read */
854 {
855 struct http_hdr hdrs[global.tune.max_http_hdr];
856
William Lallemanda625b032022-03-17 14:45:46 +0100857 if (!co_data(res))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200858 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +0200859 htx = htxbuf(&res->buf);
Christopher Fauletca90de22023-08-04 14:28:23 +0200860 if (htx_is_empty(htx))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200861 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +0200862
863 hdr_num = 0;
Christopher Faulet534645d2022-04-29 13:44:46 +0200864 blk = htx_get_head_blk(htx);
865 while (blk) {
William Lallemand33b0d092021-08-13 16:05:53 +0200866 enum htx_blk_type type = htx_get_blk_type(blk);
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100867 uint32_t sz = htx_get_blksz(blk);
William Lallemand33b0d092021-08-13 16:05:53 +0200868
Christopher Faulet534645d2022-04-29 13:44:46 +0200869 c_rew(res, sz);
William Lallemandc020b252022-03-09 18:56:02 +0100870
Christopher Faulet18de6f22022-06-01 16:37:49 +0200871 if (type == HTX_BLK_HDR) {
William Lallemandc020b252022-03-09 18:56:02 +0100872 hdrs[hdr_num].n = istdup(htx_get_blk_name(htx, blk));
873 hdrs[hdr_num].v = istdup(htx_get_blk_value(htx, blk));
William Lallemandc020b252022-03-09 18:56:02 +0100874 hdr_num++;
875 }
Christopher Faulet534645d2022-04-29 13:44:46 +0200876 else if (type == HTX_BLK_EOH) {
877 /* create a NULL end of array and leave the loop */
William Lallemand33b0d092021-08-13 16:05:53 +0200878 hdrs[hdr_num].n = IST_NULL;
879 hdrs[hdr_num].v = IST_NULL;
Christopher Faulet18de6f22022-06-01 16:37:49 +0200880 htx_remove_blk(htx, blk);
William Lallemand33b0d092021-08-13 16:05:53 +0200881 break;
882 }
Christopher Faulet18de6f22022-06-01 16:37:49 +0200883 blk = htx_remove_blk(htx, blk);
William Lallemand33b0d092021-08-13 16:05:53 +0200884 }
Christopher Fauletca90de22023-08-04 14:28:23 +0200885 htx_to_buf(htx, &res->buf);
William Lallemand33b0d092021-08-13 16:05:53 +0200886
William Lallemand0d6f7792021-08-20 11:59:49 +0200887 if (hdr_num) {
888 /* alloc and copy the headers in the httpclient struct */
889 hc->res.hdrs = calloc((hdr_num + 1), sizeof(*hc->res.hdrs));
890 if (!hc->res.hdrs)
Christopher Fauletbe08df82023-03-31 11:30:32 +0200891 goto error;
William Lallemand0d6f7792021-08-20 11:59:49 +0200892 memcpy(hc->res.hdrs, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
William Lallemand33b0d092021-08-13 16:05:53 +0200893
William Lallemand0d6f7792021-08-20 11:59:49 +0200894 /* caller callback */
895 if (hc->ops.res_headers)
896 hc->ops.res_headers(hc);
897 }
William Lallemand33b0d092021-08-13 16:05:53 +0200898
899 /* if there is no HTX data anymore and the EOM flag is
900 * set, leave (no body) */
William Lallemand1123dde2021-09-21 10:58:10 +0200901 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM) {
William Lallemand33b0d092021-08-13 16:05:53 +0200902 appctx->st0 = HTTPCLIENT_S_RES_END;
William Lallemand1123dde2021-09-21 10:58:10 +0200903 } else {
William Lallemand33b0d092021-08-13 16:05:53 +0200904 appctx->st0 = HTTPCLIENT_S_RES_BODY;
William Lallemand1123dde2021-09-21 10:58:10 +0200905 }
William Lallemand33b0d092021-08-13 16:05:53 +0200906 }
Christopher Fauletbe08df82023-03-31 11:30:32 +0200907 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200908
909 case HTTPCLIENT_S_RES_BODY:
910 /*
911 * The IO handler removes the htx blocks in the response buffer and
912 * push them in the hc->res.buf buffer in a raw format.
913 */
William Lallemanda625b032022-03-17 14:45:46 +0100914 if (!co_data(res))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200915 goto out;
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100916
William Lallemand33b0d092021-08-13 16:05:53 +0200917 htx = htxbuf(&res->buf);
Christopher Fauletca90de22023-08-04 14:28:23 +0200918 if (htx_is_empty(htx))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200919 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +0200920
Christopher Faulet600985d2022-01-12 11:14:08 +0100921 if (!b_alloc(&hc->res.buf))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200922 goto out;
Christopher Faulet600985d2022-01-12 11:14:08 +0100923
William Lallemand33b0d092021-08-13 16:05:53 +0200924 if (b_full(&hc->res.buf))
Christopher Faulet600985d2022-01-12 11:14:08 +0100925 goto process_data;
William Lallemand33b0d092021-08-13 16:05:53 +0200926
927 /* decapsule the htx data to raw data */
Christopher Faulet534645d2022-04-29 13:44:46 +0200928 blk = htx_get_head_blk(htx);
929 while (blk) {
William Lallemandc8f1eb92022-03-09 11:58:51 +0100930 enum htx_blk_type type = htx_get_blk_type(blk);
931 size_t count = co_data(res);
932 uint32_t blksz = htx_get_blksz(blk);
933 uint32_t room = b_room(&hc->res.buf);
934 uint32_t vlen;
William Lallemand33b0d092021-08-13 16:05:53 +0200935
William Lallemandc8f1eb92022-03-09 11:58:51 +0100936 /* we should try to copy the maximum output data in a block, which fit
937 * the destination buffer */
938 vlen = MIN(count, blksz);
939 vlen = MIN(vlen, room);
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100940
Christopher Fauletca90de22023-08-04 14:28:23 +0200941 if (vlen == 0) {
942 htx_to_buf(htx, &res->buf);
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100943 goto process_data;
Christopher Fauletca90de22023-08-04 14:28:23 +0200944 }
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100945
William Lallemand33b0d092021-08-13 16:05:53 +0200946 if (type == HTX_BLK_DATA) {
947 struct ist v = htx_get_blk_value(htx, blk);
948
William Lallemandc8f1eb92022-03-09 11:58:51 +0100949 __b_putblk(&hc->res.buf, v.ptr, vlen);
950 c_rew(res, vlen);
William Lallemand33b0d092021-08-13 16:05:53 +0200951
William Lallemandc8f1eb92022-03-09 11:58:51 +0100952 if (vlen == blksz)
Christopher Faulet534645d2022-04-29 13:44:46 +0200953 blk = htx_remove_blk(htx, blk);
William Lallemandc8f1eb92022-03-09 11:58:51 +0100954 else
955 htx_cut_data_blk(htx, blk, vlen);
William Lallemand2b7dc4e2022-02-24 16:55:41 +0100956
William Lallemand33b0d092021-08-13 16:05:53 +0200957 /* the data must be processed by the caller in the receive phase */
958 if (hc->ops.res_payload)
959 hc->ops.res_payload(hc);
William Lallemandc8f1eb92022-03-09 11:58:51 +0100960
Ilya Shipitsin3b64a282022-07-29 22:26:53 +0500961 /* cannot copy everything, need to process */
Christopher Fauletca90de22023-08-04 14:28:23 +0200962 if (vlen != blksz) {
963 htx_to_buf(htx, &res->buf);
William Lallemandc8f1eb92022-03-09 11:58:51 +0100964 goto process_data;
Christopher Fauletca90de22023-08-04 14:28:23 +0200965 }
William Lallemand33b0d092021-08-13 16:05:53 +0200966 } else {
Christopher Fauletca90de22023-08-04 14:28:23 +0200967 if (vlen != blksz) {
968 htx_to_buf(htx, &res->buf);
William Lallemandc8f1eb92022-03-09 11:58:51 +0100969 goto process_data;
Christopher Fauletca90de22023-08-04 14:28:23 +0200970 }
William Lallemandc8f1eb92022-03-09 11:58:51 +0100971
William Lallemand33b0d092021-08-13 16:05:53 +0200972 /* remove any block which is not a data block */
William Lallemandc8f1eb92022-03-09 11:58:51 +0100973 c_rew(res, blksz);
Christopher Faulet534645d2022-04-29 13:44:46 +0200974 blk = htx_remove_blk(htx, blk);
William Lallemand33b0d092021-08-13 16:05:53 +0200975 }
976 }
William Lallemandc8f1eb92022-03-09 11:58:51 +0100977
Christopher Fauletca90de22023-08-04 14:28:23 +0200978 htx_to_buf(htx, &res->buf);
979
William Lallemand33b0d092021-08-13 16:05:53 +0200980 /* if not finished, should be called again */
William Lallemandc8f1eb92022-03-09 11:58:51 +0100981 if (!(htx_is_empty(htx) && (htx->flags & HTX_FL_EOM)))
Christopher Fauletbe08df82023-03-31 11:30:32 +0200982 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +0200983
William Lallemandc8f1eb92022-03-09 11:58:51 +0100984
William Lallemand33b0d092021-08-13 16:05:53 +0200985 /* end of message, we should quit */
986 appctx->st0 = HTTPCLIENT_S_RES_END;
Christopher Fauletbe08df82023-03-31 11:30:32 +0200987 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200988
989 case HTTPCLIENT_S_RES_END:
Christopher Fauletbe08df82023-03-31 11:30:32 +0200990 se_fl_set(appctx->sedesc, SE_FL_EOS);
991 goto out;
992 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200993 }
994 }
995
Christopher Fauletbe08df82023-03-31 11:30:32 +0200996out:
997 return;
William Lallemand33b0d092021-08-13 16:05:53 +0200998
Christopher Fauletbe08df82023-03-31 11:30:32 +0200999process_data:
Willy Tarreaub89f8722022-05-27 10:37:32 +02001000 sc_will_read(sc);
Christopher Fauletbe08df82023-03-31 11:30:32 +02001001 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +02001002
Christopher Fauletbe08df82023-03-31 11:30:32 +02001003error:
1004 se_fl_set(appctx->sedesc, SE_FL_ERROR);
1005 goto out;
William Lallemand33b0d092021-08-13 16:05:53 +02001006}
1007
Christopher Fauletb1e08362022-05-12 15:33:14 +02001008static int httpclient_applet_init(struct appctx *appctx)
1009{
1010 struct httpclient *hc = appctx->svcctx;
1011 struct stream *s;
1012 struct sockaddr_storage *addr = NULL;
1013 struct sockaddr_storage ss_url = {};
1014 struct sockaddr_storage *ss_dst;
1015 enum obj_type *target = NULL;
1016 struct ist host = IST_NULL;
1017 enum http_scheme scheme;
1018 int port;
1019 int doresolve = 0;
1020
1021
1022 /* parse the URL and */
Thierry Fournier74a9eb52022-10-10 12:46:38 +02001023 if (!httpclient_spliturl(hc->req.url, &scheme, &host, &port))
1024 goto out_error;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001025
1026 if (hc->dst) {
1027 /* if httpclient_set_dst() was used, sets the alternative address */
1028 ss_dst = hc->dst;
1029 } else {
1030 /* set the dst using the host, or 0.0.0.0 to resolve */
1031 ist2str(trash.area, host);
1032 ss_dst = str2ip2(trash.area, &ss_url, 0);
1033 if (!ss_dst) { /* couldn't get an IP from that, try to resolve */
1034 doresolve = 1;
1035 ss_dst = str2ip2("0.0.0.0", &ss_url, 0);
1036 }
1037 sock_inet_set_port(ss_dst, port);
1038 }
1039
Christopher Fauletb1e08362022-05-12 15:33:14 +02001040 if (!sockaddr_alloc(&addr, ss_dst, sizeof(*ss_dst)))
1041 goto out_error;
1042
1043 /* choose the SSL server or not */
1044 switch (scheme) {
1045 case SCH_HTTP:
William Lallemand992ad622022-09-12 17:39:04 +02001046 target = &hc->srv_raw->obj_type;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001047 break;
1048 case SCH_HTTPS:
1049#ifdef USE_OPENSSL
William Lallemand992ad622022-09-12 17:39:04 +02001050 if (hc->srv_ssl) {
1051 target = &hc->srv_ssl->obj_type;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001052 } else {
1053 ha_alert("httpclient: SSL was disabled (wrong verify/ca-file)!\n");
1054 goto out_free_addr;
1055 }
1056#else
1057 ha_alert("httpclient: OpenSSL is not available %s:%d.\n", __FUNCTION__, __LINE__);
1058 goto out_free_addr;
1059#endif
1060 break;
1061 }
1062
William Lallemand992ad622022-09-12 17:39:04 +02001063 if (appctx_finalize_startup(appctx, hc->px, &hc->req.buf) == -1) {
Christopher Fauletb1e08362022-05-12 15:33:14 +02001064 ha_alert("httpclient: Failed to initialize appctx %s:%d.\n", __FUNCTION__, __LINE__);
1065 goto out_free_addr;
1066 }
1067
1068 s = appctx_strm(appctx);
1069 s->target = target;
1070 /* set the "timeout server" */
Christopher Faulet5aaacfb2023-02-15 08:13:33 +01001071 s->scb->ioto = hc->timeout_server;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001072
1073 if (doresolve) {
1074 /* in order to do the set-dst we need to put the address on the front */
Willy Tarreau7cb9e6c2022-05-17 19:40:40 +02001075 s->scf->dst = addr;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001076 } else {
1077 /* in cases we don't use the resolve we already have the address
1078 * and must put it on the backend side, some of the cases are
1079 * not meant to be used on the frontend (sockpair, unix socket etc.) */
Willy Tarreau7cb9e6c2022-05-17 19:40:40 +02001080 s->scb->dst = addr;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001081 }
1082
Christopher Faulet9a790f62023-03-16 14:40:03 +01001083 s->scb->flags |= (SC_FL_RCV_ONCE|SC_FL_NOLINGER);
Christopher Fauletb1e08362022-05-12 15:33:14 +02001084 s->flags |= SF_ASSIGNED;
Christopher Fauletb1e08362022-05-12 15:33:14 +02001085
1086 /* applet is waiting for data */
Willy Tarreau90e8b452022-05-25 18:21:43 +02001087 applet_need_more_data(appctx);
Christopher Fauletb1e08362022-05-12 15:33:14 +02001088 appctx_wakeup(appctx);
1089
1090 hc->appctx = appctx;
1091 hc->flags |= HTTPCLIENT_FS_STARTED;
1092
1093 /* The request was transferred when the stream was created. So switch
1094 * directly to REQ_BODY or RES_STLINE state
1095 */
1096 appctx->st0 = (hc->ops.req_payload ? HTTPCLIENT_S_REQ_BODY : HTTPCLIENT_S_RES_STLINE);
1097 return 0;
1098
1099 out_free_addr:
1100 sockaddr_free(&addr);
1101 out_error:
1102 return -1;
1103}
1104
William Lallemand33b0d092021-08-13 16:05:53 +02001105static void httpclient_applet_release(struct appctx *appctx)
1106{
Willy Tarreau1eea6652022-05-05 20:12:01 +02001107 struct httpclient *hc = appctx->svcctx;
William Lallemand33b0d092021-08-13 16:05:53 +02001108
William Lallemand1123dde2021-09-21 10:58:10 +02001109 /* mark the httpclient as ended */
William Lallemandecb83e12021-09-28 11:00:43 +02001110 hc->flags |= HTTPCLIENT_FS_ENDED;
William Lallemand33b0d092021-08-13 16:05:53 +02001111 /* the applet is leaving, remove the ptr so we don't try to call it
1112 * again from the caller */
1113 hc->appctx = NULL;
1114
William Lallemandeb0d4c42022-04-06 14:12:37 +02001115 if (hc->ops.res_end)
1116 hc->ops.res_end(hc);
William Lallemandecb83e12021-09-28 11:00:43 +02001117
1118 /* destroy the httpclient when set to autotokill */
1119 if (hc->flags & HTTPCLIENT_FA_AUTOKILL) {
1120 httpclient_destroy(hc);
1121 }
1122
William Lallemanda93eac42022-10-20 18:36:03 +02001123 /* be sure not to use this ptr anymore if the IO handler is called a
1124 * last time */
1125 appctx->svcctx = NULL;
1126
William Lallemand33b0d092021-08-13 16:05:53 +02001127 return;
1128}
1129
1130/* HTTP client applet */
1131static struct applet httpclient_applet = {
1132 .obj_type = OBJ_TYPE_APPLET,
1133 .name = "<HTTPCLIENT>",
1134 .fct = httpclient_applet_io_handler,
Christopher Fauletb1e08362022-05-12 15:33:14 +02001135 .init = httpclient_applet_init,
William Lallemand33b0d092021-08-13 16:05:53 +02001136 .release = httpclient_applet_release,
1137};
1138
William Lallemand5392ff62022-04-28 16:55:02 +02001139
William Lallemand54aec5f2022-09-12 16:46:35 +02001140static int httpclient_resolve_init(struct proxy *px)
William Lallemand5392ff62022-04-28 16:55:02 +02001141{
1142 struct act_rule *rule;
1143 int i;
William Lallemand8a734cb2022-05-04 16:10:47 +02001144 char *do_resolve = NULL;
1145 char *http_rules[][11] = {
William Lallemand5392ff62022-04-28 16:55:02 +02001146 { "set-var(txn.hc_ip)", "dst", "" },
William Lallemandd78dfe72022-08-26 16:45:13 +02001147 { do_resolve, "hdr(Host),host_only", "if", "{", "var(txn.hc_ip)", "-m", "ip", "0.0.0.0", "}", "" },
William Lallemand5392ff62022-04-28 16:55:02 +02001148 { "return", "status", "503", "if", "{", "var(txn.hc_ip)", "-m", "ip", "0.0.0.0", "}", "" },
1149 { "capture", "var(txn.hc_ip)", "len", "40", "" },
1150 { "set-dst", "var(txn.hc_ip)", "" },
1151 { "" }
1152 };
1153
William Lallemande279f592023-05-11 21:08:38 +02001154
1155 if (resolvers_disabled)
1156 return 0;
1157
William Lallemand8a734cb2022-05-04 16:10:47 +02001158 if (!resolvers_id)
1159 resolvers_id = strdup("default");
1160
William Lallemand7c5a7ef2022-05-04 15:59:44 +02001161 memprintf(&do_resolve, "do-resolve(txn.hc_ip,%s%s%s)", resolvers_id, resolvers_prefer ? "," : "", resolvers_prefer ? resolvers_prefer : "");
William Lallemand8a734cb2022-05-04 16:10:47 +02001162 http_rules[1][0] = do_resolve;
1163
William Lallemand7867f632022-05-05 19:02:59 +02001164 /* Try to create the default resolvers section */
1165 resolvers_create_default();
1166
William Lallemand8a734cb2022-05-04 16:10:47 +02001167 /* if the resolver does not exist and no hard_error was set, simply ignore resolving */
1168 if (!find_resolvers_by_id(resolvers_id) && !hard_error_resolvers) {
1169 free(do_resolve);
William Lallemand5392ff62022-04-28 16:55:02 +02001170 return 0;
William Lallemand8a734cb2022-05-04 16:10:47 +02001171 }
William Lallemand5392ff62022-04-28 16:55:02 +02001172
1173
1174 for (i = 0; *http_rules[i][0] != '\0'; i++) {
William Lallemand54aec5f2022-09-12 16:46:35 +02001175 rule = parse_http_req_cond((const char **)http_rules[i], "httpclient", 0, px);
William Lallemand5392ff62022-04-28 16:55:02 +02001176 if (!rule) {
William Lallemand8a734cb2022-05-04 16:10:47 +02001177 free(do_resolve);
William Lallemand5392ff62022-04-28 16:55:02 +02001178 ha_alert("Couldn't setup the httpclient resolver.\n");
1179 return 1;
1180 }
William Lallemand54aec5f2022-09-12 16:46:35 +02001181 LIST_APPEND(&px->http_req_rules, &rule->list);
William Lallemand5392ff62022-04-28 16:55:02 +02001182 }
1183
William Lallemand8a734cb2022-05-04 16:10:47 +02001184 free(do_resolve);
William Lallemand5392ff62022-04-28 16:55:02 +02001185 return 0;
1186}
1187
William Lallemand83614a92021-08-13 14:47:57 +02001188/*
William Lallemand54aec5f2022-09-12 16:46:35 +02001189 * Creates an internal proxy which will be used for httpclient.
1190 * This will allocate 2 servers (raw and ssl) and 1 proxy.
1191 *
1192 * This function must be called from a precheck callback.
1193 *
1194 * Return a proxy or NULL.
William Lallemand83614a92021-08-13 14:47:57 +02001195 */
William Lallemand54aec5f2022-09-12 16:46:35 +02001196struct proxy *httpclient_create_proxy(const char *id)
William Lallemand83614a92021-08-13 14:47:57 +02001197{
William Lallemand85af49c2022-05-04 14:33:57 +02001198 int err_code = ERR_NONE;
William Lallemand83614a92021-08-13 14:47:57 +02001199 char *errmsg = NULL;
William Lallemand54aec5f2022-09-12 16:46:35 +02001200 struct proxy *px = NULL;
1201 struct server *srv_raw = NULL;
1202#ifdef USE_OPENSSL
1203 struct server *srv_ssl = NULL;
1204#endif
William Lallemand83614a92021-08-13 14:47:57 +02001205
William Lallemandc6ceba32022-04-22 16:49:53 +02001206 if (global.mode & MODE_MWORKER_WAIT)
William Lallemand85af49c2022-05-04 14:33:57 +02001207 return ERR_NONE;
William Lallemandc6ceba32022-04-22 16:49:53 +02001208
William Lallemand54aec5f2022-09-12 16:46:35 +02001209 px = alloc_new_proxy(id, PR_CAP_LISTEN|PR_CAP_INT|PR_CAP_HTTPCLIENT, &errmsg);
1210 if (!px) {
William Lallemand85af49c2022-05-04 14:33:57 +02001211 memprintf(&errmsg, "couldn't allocate proxy.");
William Lallemand83614a92021-08-13 14:47:57 +02001212 err_code |= ERR_ALERT | ERR_FATAL;
1213 goto err;
1214 }
1215
William Lallemand54aec5f2022-09-12 16:46:35 +02001216 px->options |= PR_O_WREQ_BODY;
1217 px->retry_type |= PR_RE_CONN_FAILED | PR_RE_DISCONNECTED | PR_RE_TIMEOUT;
1218 px->options2 |= PR_O2_INDEPSTR;
1219 px->mode = PR_MODE_HTTP;
1220 px->maxconn = 0;
1221 px->accept = NULL;
William Lallemand4ad693e2023-09-05 15:55:04 +02001222 px->conn_retries = httpclient_retries;
William Lallemandb9ed1572023-09-05 16:42:27 +02001223 px->timeout.connect = httpclient_timeout_connect;
William Lallemand54aec5f2022-09-12 16:46:35 +02001224 px->timeout.client = TICK_ETERNITY;
William Lallemand83614a92021-08-13 14:47:57 +02001225 /* The HTTP Client use the "option httplog" with the global log server */
William Lallemandd793ca22022-12-22 15:13:59 +01001226 px->conf.logformat_string = httpclient_log_format;
William Lallemand54aec5f2022-09-12 16:46:35 +02001227 px->http_needed = 1;
William Lallemand83614a92021-08-13 14:47:57 +02001228
1229 /* clear HTTP server */
William Lallemand54aec5f2022-09-12 16:46:35 +02001230 srv_raw = new_server(px);
1231 if (!srv_raw) {
William Lallemand83614a92021-08-13 14:47:57 +02001232 memprintf(&errmsg, "out of memory.");
William Lallemand85af49c2022-05-04 14:33:57 +02001233 err_code |= ERR_ALERT | ERR_FATAL;
William Lallemand83614a92021-08-13 14:47:57 +02001234 goto err;
1235 }
1236
William Lallemand54aec5f2022-09-12 16:46:35 +02001237 srv_settings_cpy(srv_raw, &px->defsrv, 0);
1238 srv_raw->iweight = 0;
1239 srv_raw->uweight = 0;
1240 srv_raw->xprt = xprt_get(XPRT_RAW);
1241 srv_raw->flags |= SRV_F_MAPPORTS; /* needed to apply the port change with resolving */
1242 srv_raw->id = strdup("<HTTPCLIENT>");
1243 if (!srv_raw->id) {
William Lallemand85af49c2022-05-04 14:33:57 +02001244 memprintf(&errmsg, "out of memory.");
1245 err_code |= ERR_ALERT | ERR_FATAL;
William Lallemand83614a92021-08-13 14:47:57 +02001246 goto err;
William Lallemand85af49c2022-05-04 14:33:57 +02001247 }
William Lallemand83614a92021-08-13 14:47:57 +02001248
William Lallemand957ab132021-08-24 18:33:28 +02001249#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +02001250 /* SSL HTTP server */
William Lallemand54aec5f2022-09-12 16:46:35 +02001251 srv_ssl = new_server(px);
1252 if (!srv_ssl) {
William Lallemand83614a92021-08-13 14:47:57 +02001253 memprintf(&errmsg, "out of memory.");
1254 err_code |= ERR_ALERT | ERR_FATAL;
1255 goto err;
1256 }
William Lallemand54aec5f2022-09-12 16:46:35 +02001257 srv_settings_cpy(srv_ssl, &px->defsrv, 0);
1258 srv_ssl->iweight = 0;
1259 srv_ssl->uweight = 0;
1260 srv_ssl->xprt = xprt_get(XPRT_SSL);
1261 srv_ssl->use_ssl = 1;
1262 srv_ssl->flags |= SRV_F_MAPPORTS; /* needed to apply the port change with resolving */
1263 srv_ssl->id = strdup("<HTTPSCLIENT>");
1264 if (!srv_ssl->id) {
William Lallemand85af49c2022-05-04 14:33:57 +02001265 memprintf(&errmsg, "out of memory.");
1266 err_code |= ERR_ALERT | ERR_FATAL;
William Lallemand83614a92021-08-13 14:47:57 +02001267 goto err;
William Lallemand85af49c2022-05-04 14:33:57 +02001268 }
William Lallemand83614a92021-08-13 14:47:57 +02001269
Willy Tarreaudf3231c2022-09-02 09:02:21 +02001270#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
William Lallemand54aec5f2022-09-12 16:46:35 +02001271 if (ssl_sock_parse_alpn("h2,http/1.1", &srv_ssl->ssl_ctx.alpn_str, &srv_ssl->ssl_ctx.alpn_len, &errmsg) != 0) {
Willy Tarreaudf3231c2022-09-02 09:02:21 +02001272 err_code |= ERR_ALERT | ERR_FATAL;
1273 goto err;
1274 }
1275#endif
William Lallemand54aec5f2022-09-12 16:46:35 +02001276 srv_ssl->ssl_ctx.verify = httpclient_ssl_verify;
William Lallemand4006b0f2022-04-25 18:23:35 +02001277 /* if the verify is required, try to load the system CA */
William Lallemandeaa703e2022-04-22 17:52:33 +02001278 if (httpclient_ssl_verify == SSL_SOCK_VERIFY_REQUIRED) {
William Lallemand683fbb82022-05-04 15:43:01 +02001279
Miroslav Zagoraca2ec1922022-11-02 16:11:50 +01001280 srv_ssl->ssl_ctx.ca_file = strdup(httpclient_ssl_ca_file ? httpclient_ssl_ca_file : "@system-ca");
William Lallemand0a2d6322022-11-24 19:14:19 +01001281 if (!__ssl_store_load_locations_file(srv_ssl->ssl_ctx.ca_file, 1, CAFILE_CERT, !hard_error_ssl)) {
William Lallemand6fce46a2022-05-04 14:53:41 +02001282 /* if we failed to load the ca-file, only quits in
1283 * error with hard_error, otherwise just disable the
1284 * feature. */
1285 if (hard_error_ssl) {
William Lallemand54aec5f2022-09-12 16:46:35 +02001286 memprintf(&errmsg, "cannot initialize SSL verify with 'ca-file \"%s\"'.", srv_ssl->ssl_ctx.ca_file);
William Lallemand6fce46a2022-05-04 14:53:41 +02001287 err_code |= ERR_ALERT | ERR_FATAL;
1288 goto err;
1289 } else {
William Lallemand54aec5f2022-09-12 16:46:35 +02001290 ha_free(&srv_ssl->ssl_ctx.ca_file);
1291 srv_drop(srv_ssl);
1292 srv_ssl = NULL;
William Lallemand6fce46a2022-05-04 14:53:41 +02001293 }
William Lallemand4006b0f2022-04-25 18:23:35 +02001294 }
William Lallemandeaa703e2022-04-22 17:52:33 +02001295 }
William Lallemandcf5cb0b2022-04-22 14:48:45 +02001296
William Lallemand957ab132021-08-24 18:33:28 +02001297#endif
William Lallemandcfcbe9e2021-08-24 17:15:58 +02001298
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +05001299 /* add the proxy in the proxy list only if everything is successful */
William Lallemand54aec5f2022-09-12 16:46:35 +02001300 px->next = proxies_list;
1301 proxies_list = px;
William Lallemand83614a92021-08-13 14:47:57 +02001302
William Lallemand54aec5f2022-09-12 16:46:35 +02001303 if (httpclient_resolve_init(px) != 0) {
William Lallemand85af49c2022-05-04 14:33:57 +02001304 memprintf(&errmsg, "cannot initialize resolvers.");
1305 err_code |= ERR_ALERT | ERR_FATAL;
William Lallemand5392ff62022-04-28 16:55:02 +02001306 goto err;
William Lallemand85af49c2022-05-04 14:33:57 +02001307 }
William Lallemand5392ff62022-04-28 16:55:02 +02001308
William Lallemand211c9672021-08-24 17:18:13 +02001309 /* link the 2 servers in the proxy */
William Lallemand54aec5f2022-09-12 16:46:35 +02001310 srv_raw->next = px->srv;
1311 px->srv = srv_raw;
William Lallemand957ab132021-08-24 18:33:28 +02001312
1313#ifdef USE_OPENSSL
William Lallemand54aec5f2022-09-12 16:46:35 +02001314 if (srv_ssl) {
1315 srv_ssl->next = px->srv;
1316 px->srv = srv_ssl;
William Lallemand4006b0f2022-04-25 18:23:35 +02001317 }
William Lallemand957ab132021-08-24 18:33:28 +02001318#endif
1319
William Lallemand211c9672021-08-24 17:18:13 +02001320
William Lallemand83614a92021-08-13 14:47:57 +02001321err:
William Lallemand85af49c2022-05-04 14:33:57 +02001322 if (err_code & ERR_CODE) {
1323 ha_alert("httpclient: cannot initialize: %s\n", errmsg);
1324 free(errmsg);
William Lallemand54aec5f2022-09-12 16:46:35 +02001325 srv_drop(srv_raw);
1326#ifdef USE_OPENSSL
1327 srv_drop(srv_ssl);
1328#endif
1329 free_proxy(px);
1330
1331 return NULL;
1332 }
1333 return px;
1334}
1335
1336/*
1337 * Initialize the proxy for the HTTP client with 2 servers, one for raw HTTP,
1338 * the other for HTTPS.
1339 */
1340static int httpclient_precheck()
1341{
William Lallemand54aec5f2022-09-12 16:46:35 +02001342 /* initialize the default httpclient_proxy which is used for the CLI and the lua */
1343
1344 httpclient_proxy = httpclient_create_proxy("<HTTPCLIENT>");
1345 if (!httpclient_proxy)
1346 return 1;
William Lallemand54aec5f2022-09-12 16:46:35 +02001347
1348 return 0;
William Lallemand83614a92021-08-13 14:47:57 +02001349}
1350
William Lallemand2c8b0842022-04-22 15:16:09 +02001351static int httpclient_postcheck()
William Lallemand83614a92021-08-13 14:47:57 +02001352{
William Lallemand85af49c2022-05-04 14:33:57 +02001353 int err_code = ERR_NONE;
William Lallemand83614a92021-08-13 14:47:57 +02001354 struct logsrv *logsrv;
William Lallemand992ad622022-09-12 17:39:04 +02001355 struct proxy *curproxy = NULL;
William Lallemand71e31582022-03-16 15:47:47 +01001356 char *errmsg = NULL;
William Lallemand992ad622022-09-12 17:39:04 +02001357#ifdef USE_OPENSSL
1358 struct server *srv = NULL;
1359 struct server *srv_ssl = NULL;
1360#endif
William Lallemand83614a92021-08-13 14:47:57 +02001361
William Lallemandc6ceba32022-04-22 16:49:53 +02001362 if (global.mode & MODE_MWORKER_WAIT)
William Lallemand85af49c2022-05-04 14:33:57 +02001363 return ERR_NONE;
William Lallemandc6ceba32022-04-22 16:49:53 +02001364
William Lallemand992ad622022-09-12 17:39:04 +02001365 /* Initialize the logs for every proxy dedicated to the httpclient */
1366 for (curproxy = proxies_list; curproxy; curproxy = curproxy->next) {
William Lallemand83614a92021-08-13 14:47:57 +02001367
William Lallemand992ad622022-09-12 17:39:04 +02001368 if (!(curproxy->cap & PR_CAP_HTTPCLIENT))
1369 continue;
William Lallemand83614a92021-08-13 14:47:57 +02001370
William Lallemand992ad622022-09-12 17:39:04 +02001371 /* copy logs from "global" log list */
1372 list_for_each_entry(logsrv, &global.logsrvs, list) {
Aurelien DARRAGON77679312023-07-05 15:52:19 +02001373 struct logsrv *node = dup_logsrv(logsrv);
William Lallemand992ad622022-09-12 17:39:04 +02001374
1375 if (!node) {
1376 memprintf(&errmsg, "out of memory.");
1377 err_code |= ERR_ALERT | ERR_FATAL;
1378 goto err;
1379 }
1380
William Lallemand992ad622022-09-12 17:39:04 +02001381 LIST_APPEND(&curproxy->logsrvs, &node->list);
William Lallemand83614a92021-08-13 14:47:57 +02001382 }
William Lallemand992ad622022-09-12 17:39:04 +02001383 if (curproxy->conf.logformat_string) {
1384 curproxy->conf.args.ctx = ARGC_LOG;
1385 if (!parse_logformat_string(curproxy->conf.logformat_string, curproxy, &curproxy->logformat,
1386 LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
1387 SMP_VAL_FE_LOG_END, &errmsg)) {
1388 memprintf(&errmsg, "failed to parse log-format : %s.", errmsg);
1389 err_code |= ERR_ALERT | ERR_FATAL;
1390 goto err;
1391 }
1392 curproxy->conf.args.file = NULL;
1393 curproxy->conf.args.line = 0;
1394 }
William Lallemand71e31582022-03-16 15:47:47 +01001395
1396#ifdef USE_OPENSSL
William Lallemand992ad622022-09-12 17:39:04 +02001397 /* initialize the SNI for the SSL servers */
1398
1399 for (srv = curproxy->srv; srv != NULL; srv = srv->next) {
1400 if (srv->xprt == xprt_get(XPRT_SSL)) {
1401 srv_ssl = srv;
1402 }
William Lallemand715c1012022-03-16 16:39:23 +01001403 }
Miroslav Zagoraccbfee3a2022-09-19 12:20:29 +02001404 if (srv_ssl && !srv_ssl->sni_expr) {
William Lallemand992ad622022-09-12 17:39:04 +02001405 /* init the SNI expression */
1406 /* always use the host header as SNI, without the port */
1407 srv_ssl->sni_expr = strdup("req.hdr(host),field(1,:)");
1408 err_code |= server_parse_sni_expr(srv_ssl, curproxy, &errmsg);
1409 if (err_code & ERR_CODE) {
1410 memprintf(&errmsg, "failed to configure sni: %s.", errmsg);
1411 goto err;
1412 }
1413 }
William Lallemand71e31582022-03-16 15:47:47 +01001414#endif
William Lallemand992ad622022-09-12 17:39:04 +02001415 }
William Lallemand71e31582022-03-16 15:47:47 +01001416
William Lallemand83614a92021-08-13 14:47:57 +02001417err:
William Lallemand85af49c2022-05-04 14:33:57 +02001418 if (err_code & ERR_CODE) {
1419 ha_alert("httpclient: failed to initialize: %s\n", errmsg);
1420 free(errmsg);
1421
1422 }
1423 return err_code;
William Lallemand83614a92021-08-13 14:47:57 +02001424}
1425
William Lallemand83614a92021-08-13 14:47:57 +02001426/* initialize the proxy and servers for the HTTP client */
1427
William Lallemand2c8b0842022-04-22 15:16:09 +02001428REGISTER_PRE_CHECK(httpclient_precheck);
1429REGISTER_POST_CHECK(httpclient_postcheck);
William Lallemandeaa703e2022-04-22 17:52:33 +02001430
William Lallemand8a734cb2022-05-04 16:10:47 +02001431static int httpclient_parse_global_resolvers(char **args, int section_type, struct proxy *curpx,
1432 const struct proxy *defpx, const char *file, int line,
1433 char **err)
1434{
1435 if (too_many_args(1, args, err, NULL))
1436 return -1;
1437
1438 /* any configuration should set the hard_error flag */
1439 hard_error_resolvers = 1;
1440
1441 free(resolvers_id);
1442 resolvers_id = strdup(args[1]);
1443
1444 return 0;
1445}
1446
William Lallemande279f592023-05-11 21:08:38 +02001447/* config parser for global "httpclient.resolvers.disabled", accepts "on" or "off" */
1448static int httpclient_parse_global_resolvers_disabled(char **args, int section_type, struct proxy *curpx,
1449 const struct proxy *defpx, const char *file, int line,
1450 char **err)
1451{
1452 if (too_many_args(1, args, err, NULL))
1453 return -1;
1454
1455 if (strcmp(args[1], "on") == 0)
1456 resolvers_disabled = 1;
1457 else if (strcmp(args[1], "off") == 0)
1458 resolvers_disabled = 0;
1459 else {
1460 memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]);
1461 return -1;
1462 }
1463 return 0;
1464}
1465
William Lallemand7c5a7ef2022-05-04 15:59:44 +02001466static int httpclient_parse_global_prefer(char **args, int section_type, struct proxy *curpx,
1467 const struct proxy *defpx, const char *file, int line,
1468 char **err)
1469{
1470 if (too_many_args(1, args, err, NULL))
1471 return -1;
1472
1473 /* any configuration should set the hard_error flag */
1474 hard_error_resolvers = 1;
1475
1476
1477 if (strcmp(args[1],"ipv4") == 0)
1478 resolvers_prefer = "ipv4";
1479 else if (strcmp(args[1],"ipv6") == 0)
1480 resolvers_prefer = "ipv6";
1481 else {
1482 ha_alert("parsing [%s:%d] : '%s' expects 'ipv4' or 'ipv6' as argument.\n", file, line, args[0]);
1483 return -1;
1484 }
1485
1486 return 0;
1487}
William Lallemand8a734cb2022-05-04 16:10:47 +02001488
1489
William Lallemandeaa703e2022-04-22 17:52:33 +02001490#ifdef USE_OPENSSL
William Lallemand683fbb82022-05-04 15:43:01 +02001491static int httpclient_parse_global_ca_file(char **args, int section_type, struct proxy *curpx,
1492 const struct proxy *defpx, const char *file, int line,
1493 char **err)
1494{
1495 if (too_many_args(1, args, err, NULL))
1496 return -1;
1497
1498 /* any configuration should set the hard_error flag */
1499 hard_error_ssl = 1;
1500
1501 free(httpclient_ssl_ca_file);
1502 httpclient_ssl_ca_file = strdup(args[1]);
1503
1504 return 0;
1505}
1506
William Lallemandeaa703e2022-04-22 17:52:33 +02001507static int httpclient_parse_global_verify(char **args, int section_type, struct proxy *curpx,
1508 const struct proxy *defpx, const char *file, int line,
1509 char **err)
1510{
1511 if (too_many_args(1, args, err, NULL))
1512 return -1;
1513
William Lallemand6fce46a2022-05-04 14:53:41 +02001514 /* any configuration should set the hard_error flag */
1515 hard_error_ssl = 1;
1516
William Lallemandeaa703e2022-04-22 17:52:33 +02001517 if (strcmp(args[1],"none") == 0)
William Lallemand04994de2022-04-28 19:35:21 +02001518 httpclient_ssl_verify = SSL_SOCK_VERIFY_NONE;
William Lallemandeaa703e2022-04-22 17:52:33 +02001519 else if (strcmp(args[1],"required") == 0)
William Lallemand04994de2022-04-28 19:35:21 +02001520 httpclient_ssl_verify = SSL_SOCK_VERIFY_REQUIRED;
William Lallemandeaa703e2022-04-22 17:52:33 +02001521 else {
1522 ha_alert("parsing [%s:%d] : '%s' expects 'none' or 'required' as argument.\n", file, line, args[0]);
1523 return -1;
1524 }
1525
1526 return 0;
1527}
William Lallemand8a734cb2022-05-04 16:10:47 +02001528#endif /* ! USE_OPENSSL */
William Lallemandeaa703e2022-04-22 17:52:33 +02001529
William Lallemand4ad693e2023-09-05 15:55:04 +02001530static int httpclient_parse_global_retries(char **args, int section_type, struct proxy *curpx,
1531 const struct proxy *defpx, const char *file, int line,
1532 char **err)
1533{
1534 if (too_many_args(1, args, err, NULL))
1535 return -1;
1536
1537 if (*(args[1]) == 0) {
1538 ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n",
1539 file, line, args[0]);
1540 return -1;
1541 }
1542 httpclient_retries = atol(args[1]);
1543
1544 return 0;
1545}
1546
William Lallemandb9ed1572023-09-05 16:42:27 +02001547static int httpclient_parse_global_timeout_connect(char **args, int section_type, struct proxy *curpx,
1548 const struct proxy *defpx, const char *file, int line,
1549 char **err)
1550{
1551 const char *res;
1552 unsigned timeout;
1553
1554 if (too_many_args(1, args, err, NULL))
1555 return -1;
1556
1557 if (*(args[1]) == 0) {
1558 ha_alert("parsing [%s:%d] : '%s' expects an integer argument.\n",
1559 file, line, args[0]);
1560 return -1;
1561 }
1562
1563 res = parse_time_err(args[1], &timeout, TIME_UNIT_MS);
1564 if (res == PARSE_TIME_OVER) {
1565 memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)",
1566 args[1], args[0]);
1567 return -1;
1568 }
1569 else if (res == PARSE_TIME_UNDER) {
1570 memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)",
1571 args[1], args[0]);
1572 return -1;
1573 }
1574 else if (res) {
1575 memprintf(err, "unexpected character '%c' in '%s'", *res, args[0]);
1576 return -1;
1577 }
1578
1579 if (*args[2] != 0) {
1580 memprintf(err, "'%s' : unexpected extra argument '%s' after value '%s'.", args[0], args[2], args[1]);
1581 return -1;
1582 }
1583
1584 httpclient_timeout_connect = MS_TO_TICKS(timeout);
1585
1586 return 0;
1587}
1588
William Lallemand4ad693e2023-09-05 15:55:04 +02001589
William Lallemandeaa703e2022-04-22 17:52:33 +02001590static struct cfg_kw_list cfg_kws = {ILH, {
William Lallemande279f592023-05-11 21:08:38 +02001591 { CFG_GLOBAL, "httpclient.resolvers.disabled", httpclient_parse_global_resolvers_disabled },
William Lallemand8a734cb2022-05-04 16:10:47 +02001592 { CFG_GLOBAL, "httpclient.resolvers.id", httpclient_parse_global_resolvers },
William Lallemand7c5a7ef2022-05-04 15:59:44 +02001593 { CFG_GLOBAL, "httpclient.resolvers.prefer", httpclient_parse_global_prefer },
William Lallemand4ad693e2023-09-05 15:55:04 +02001594 { CFG_GLOBAL, "httpclient.retries", httpclient_parse_global_retries },
William Lallemandb9ed1572023-09-05 16:42:27 +02001595 { CFG_GLOBAL, "httpclient.timeout.connect", httpclient_parse_global_timeout_connect },
William Lallemand8a734cb2022-05-04 16:10:47 +02001596#ifdef USE_OPENSSL
William Lallemand9ff95e22022-05-04 13:52:29 +02001597 { CFG_GLOBAL, "httpclient.ssl.verify", httpclient_parse_global_verify },
William Lallemand683fbb82022-05-04 15:43:01 +02001598 { CFG_GLOBAL, "httpclient.ssl.ca-file", httpclient_parse_global_ca_file },
William Lallemand8a734cb2022-05-04 16:10:47 +02001599#endif
William Lallemandeaa703e2022-04-22 17:52:33 +02001600 { 0, NULL, NULL },
1601}};
1602
1603INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);