blob: 0815c7c1e3e3c2dc937f2d80dacf590b36732bd6 [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>
18#include <haproxy/dynbuf.h>
William Lallemand83614a92021-08-13 14:47:57 +020019#include <haproxy/cfgparse.h>
20#include <haproxy/connection.h>
21#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>
25#include <haproxy/http_client.h>
26#include <haproxy/http_htx.h>
27#include <haproxy/htx.h>
William Lallemand83614a92021-08-13 14:47:57 +020028#include <haproxy/log.h>
29#include <haproxy/proxy.h>
William Lallemand2a8fe8b2021-08-20 14:25:15 +020030#include <haproxy/server.h>
Willy Tarreau1df20422021-10-06 11:28:24 +020031#include <haproxy/ssl_sock-t.h>
William Lallemand33b0d092021-08-13 16:05:53 +020032#include <haproxy/stream_interface.h>
William Lallemand83614a92021-08-13 14:47:57 +020033#include <haproxy/tools.h>
34
35#include <string.h>
36
37
38static struct proxy *httpclient_proxy;
39static struct server *httpclient_srv_raw;
William Lallemand957ab132021-08-24 18:33:28 +020040#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +020041static struct server *httpclient_srv_ssl;
William Lallemand957ab132021-08-24 18:33:28 +020042#endif
William Lallemand33b0d092021-08-13 16:05:53 +020043static struct applet httpclient_applet;
44
William Lallemand03a4eb12021-08-18 16:46:21 +020045/* --- This part of the file implement an HTTP client over the CLI ---
46 * The functions will be starting by "hc_cli" for "httpclient cli"
47 */
48
William Lallemand03a4eb12021-08-18 16:46:21 +020049/* What kind of data we need to read */
50#define HC_CLI_F_RES_STLINE 0x01
51#define HC_CLI_F_RES_HDR 0x02
52#define HC_CLI_F_RES_BODY 0x04
53#define HC_CLI_F_RES_END 0x08
54
55
56/* These are the callback used by the HTTP Client when it needs to notify new
57 * data, we only sets a flag in the IO handler */
58
59void hc_cli_res_stline_cb(struct httpclient *hc)
60{
61 struct appctx *appctx = hc->caller;
62
William Lallemanddfc3f892021-08-20 11:35:29 +020063 if (!appctx)
64 return;
65
William Lallemand03a4eb12021-08-18 16:46:21 +020066 appctx->ctx.cli.i0 |= HC_CLI_F_RES_STLINE;
William Lallemanddfc3f892021-08-20 11:35:29 +020067 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020068}
69
70void hc_cli_res_headers_cb(struct httpclient *hc)
71{
72 struct appctx *appctx = hc->caller;
73
William Lallemanddfc3f892021-08-20 11:35:29 +020074 if (!appctx)
75 return;
76
William Lallemand03a4eb12021-08-18 16:46:21 +020077 appctx->ctx.cli.i0 |= HC_CLI_F_RES_HDR;
William Lallemanddfc3f892021-08-20 11:35:29 +020078 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020079}
80
81void hc_cli_res_body_cb(struct httpclient *hc)
82{
83 struct appctx *appctx = hc->caller;
84
William Lallemanddfc3f892021-08-20 11:35:29 +020085 if (!appctx)
86 return;
87
William Lallemand03a4eb12021-08-18 16:46:21 +020088 appctx->ctx.cli.i0 |= HC_CLI_F_RES_BODY;
William Lallemanddfc3f892021-08-20 11:35:29 +020089 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020090}
91
92void hc_cli_res_end_cb(struct httpclient *hc)
93{
94 struct appctx *appctx = hc->caller;
95
William Lallemanddfc3f892021-08-20 11:35:29 +020096 if (!appctx)
97 return;
98
William Lallemand03a4eb12021-08-18 16:46:21 +020099 appctx->ctx.cli.i0 |= HC_CLI_F_RES_END;
William Lallemanddfc3f892021-08-20 11:35:29 +0200100 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +0200101}
102
103/*
104 * Parse an httpclient keyword on the cli:
105 * httpclient <ID> <method> <URI>
106 */
107static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
108{
109 struct httpclient *hc;
110 char *err = NULL;
111 enum http_meth_t meth;
112 char *meth_str;
113 struct ist uri;
William Lallemanddec25c32021-10-25 19:48:37 +0200114 struct ist body = IST_NULL;
William Lallemand03a4eb12021-08-18 16:46:21 +0200115
116 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
117 return 1;
118
119 if (!*args[1] || !*args[2]) {
120 memprintf(&err, ": not enough parameters");
121 goto err;
122 }
123
124 meth_str = args[1];
125 uri = ist(args[2]);
126
William Lallemanddec25c32021-10-25 19:48:37 +0200127 if (payload)
128 body = ist(payload);
129
William Lallemand03a4eb12021-08-18 16:46:21 +0200130 meth = find_http_meth(meth_str, strlen(meth_str));
131
132 hc = httpclient_new(appctx, meth, uri);
133 if (!hc) {
134 goto err;
135 }
136
137 /* update the httpclient callbacks */
138 hc->ops.res_stline = hc_cli_res_stline_cb;
139 hc->ops.res_headers = hc_cli_res_headers_cb;
140 hc->ops.res_payload = hc_cli_res_body_cb;
141 hc->ops.res_end = hc_cli_res_end_cb;
142
143 appctx->ctx.cli.p0 = hc; /* store the httpclient ptr in the applet */
144 appctx->ctx.cli.i0 = 0;
145
William Lallemandbad9c8c2022-01-14 14:10:33 +0100146 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, body) != ERR_NONE)
William Lallemand03a4eb12021-08-18 16:46:21 +0200147 goto err;
148
149
150 if (!httpclient_start(hc))
151 goto err;
152
153 return 0;
154
155err:
156 memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : "");
157 return cli_err(appctx, err);
158}
159
160/* This function dumps the content of the httpclient receive buffer
161 * on the CLI output
162 *
163 * Return 1 when the processing is finished
164 * return 0 if it needs to be called again
165 */
166static int hc_cli_io_handler(struct appctx *appctx)
167{
168 struct stream_interface *si = appctx->owner;
169 struct buffer *trash = alloc_trash_chunk();
170 struct httpclient *hc = appctx->ctx.cli.p0;
171 struct http_hdr *hdrs, *hdr;
172
173 if (!trash)
174 goto out;
175 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_STLINE) {
William Lallemand614e6832021-09-26 18:12:43 +0200176 chunk_appendf(trash, "%s %d %s\n",istptr(hc->res.vsn), hc->res.status, istptr(hc->res.reason));
William Lallemand03a4eb12021-08-18 16:46:21 +0200177 if (ci_putchk(si_ic(si), trash) == -1)
178 si_rx_room_blk(si);
179 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_STLINE;
180 goto out;
181 }
182
183 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_HDR) {
184 hdrs = hc->res.hdrs;
185 for (hdr = hdrs; isttest(hdr->v); hdr++) {
186 if (!h1_format_htx_hdr(hdr->n, hdr->v, trash))
187 goto out;
188 }
189 if (!chunk_memcat(trash, "\r\n", 2))
190 goto out;
191 if (ci_putchk(si_ic(si), trash) == -1)
192 si_rx_room_blk(si);
193 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_HDR;
194 goto out;
195 }
196
197 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_BODY) {
198 int ret;
199
200 ret = httpclient_res_xfer(hc, &si_ic(si)->buf);
201 channel_add_input(si_ic(si), ret); /* forward what we put in the buffer channel */
202
William Lallemand518878e2021-09-21 10:45:34 +0200203 if (!httpclient_data(hc)) {/* remove the flag if the buffer was emptied */
William Lallemand03a4eb12021-08-18 16:46:21 +0200204 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_BODY;
205 }
206 goto out;
207 }
208
209 /* we must close only if F_END is the last flag */
210 if (appctx->ctx.cli.i0 == HC_CLI_F_RES_END) {
211 si_shutw(si);
212 si_shutr(si);
213 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_END;
214 goto out;
215 }
216
217out:
218 /* we didn't clear every flags, we should come back to finish things */
219 if (appctx->ctx.cli.i0)
220 si_rx_room_blk(si);
221
222 free_trash_chunk(trash);
223 return 0;
224}
225
226static void hc_cli_release(struct appctx *appctx)
227{
228 struct httpclient *hc = appctx->ctx.cli.p0;
229
230 /* Everything possible was printed on the CLI, we can destroy the client */
William Lallemandecb83e12021-09-28 11:00:43 +0200231 httpclient_stop_and_destroy(hc);
William Lallemand03a4eb12021-08-18 16:46:21 +0200232
233 return;
234}
235
236/* register cli keywords */
237static struct cli_kw_list cli_kws = {{ },{
William Lallemand34b3a932021-10-19 10:58:30 +0200238 { { "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 +0200239 { { NULL }, NULL, NULL, NULL }
240}};
241
242INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
243
244
245/* --- This part of the file implements the actual HTTP client API --- */
246
William Lallemand33b0d092021-08-13 16:05:53 +0200247/*
248 * Generate a simple request and fill the httpclient request buffer with it.
249 * The request contains a request line generated from the absolute <url> and
250 * <meth> as well as list of headers <hdrs>.
251 *
252 * If the buffer was filled correctly the function returns 0, if not it returns
253 * an error_code but there is no guarantee that the buffer wasn't modified.
254 */
William Lallemanddec25c32021-10-25 19:48:37 +0200255int 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 +0200256{
257 struct htx_sl *sl;
258 struct htx *htx;
259 int err_code = 0;
260 struct ist meth_ist, vsn;
William Lallemanddec25c32021-10-25 19:48:37 +0200261 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 +0100262 int i;
William Lallemandbad9c8c2022-01-14 14:10:33 +0100263 int foundhost = 0, foundaccept = 0, foundua = 0;
William Lallemand33b0d092021-08-13 16:05:53 +0200264
Christopher Faulet600985d2022-01-12 11:14:08 +0100265 if (!b_alloc(&hc->req.buf))
266 goto error;
267
William Lallemand33b0d092021-08-13 16:05:53 +0200268 if (meth >= HTTP_METH_OTHER)
269 goto error;
270
271 meth_ist = http_known_methods[meth];
272
273 vsn = ist("HTTP/1.1");
274
275 htx = htx_from_buf(&hc->req.buf);
276 if (!htx)
277 goto error;
William Lallemande1e045f2022-01-14 14:08:34 +0100278
279 if (!hc->ops.req_payload && !isttest(payload))
280 flags |= HTX_SL_F_BODYLESS;
281
William Lallemand33b0d092021-08-13 16:05:53 +0200282 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_ist, url, vsn);
283 if (!sl) {
284 goto error;
285 }
286 sl->info.req.meth = meth;
287
William Lallemandf03b53c2021-11-24 15:38:17 +0100288 for (i = 0; hdrs && hdrs[i].n.len; i++) {
289 /* Don't check the value length because a header value may be empty */
290 if (isttest(hdrs[i].v) == 0)
291 continue;
292
293 if (isteqi(hdrs[i].n, ist("host")))
294 foundhost = 1;
William Lallemandbad9c8c2022-01-14 14:10:33 +0100295 else if (isteqi(hdrs[i].n, ist("accept")))
296 foundaccept = 1;
297 else if (isteqi(hdrs[i].n, ist("user-agent")))
298 foundua = 1;
William Lallemandf03b53c2021-11-24 15:38:17 +0100299
300 if (!htx_add_header(htx, hdrs[i].n, hdrs[i].v))
301 goto error;
302 }
William Lallemand33b0d092021-08-13 16:05:53 +0200303
William Lallemandf03b53c2021-11-24 15:38:17 +0100304 if (!foundhost) {
305 /* Add Host Header from URL */
306 if (!htx_add_header(htx, ist("Host"), ist("h")))
William Lallemand79a34782021-09-20 16:19:15 +0200307 goto error;
William Lallemandf03b53c2021-11-24 15:38:17 +0100308 if (!http_update_host(htx, sl, url))
William Lallemand79a34782021-09-20 16:19:15 +0200309 goto error;
310 }
William Lallemand33b0d092021-08-13 16:05:53 +0200311
William Lallemandbad9c8c2022-01-14 14:10:33 +0100312 if (!foundaccept) {
313 if (!htx_add_header(htx, ist("Accept"), ist("*/*")))
314 goto error;
315 }
316
317 if (!foundua) {
318 if (!htx_add_header(htx, ist("User-Agent"), ist(HTTPCLIENT_USERAGENT)))
319 goto error;
320 }
321
322
William Lallemandf03b53c2021-11-24 15:38:17 +0100323 if (!htx_add_endof(htx, HTX_BLK_EOH))
324 goto error;
325
William Lallemanddec25c32021-10-25 19:48:37 +0200326 if (isttest(payload)) {
327 /* add the payload if it can feat in the buffer, no need to set
328 * the Content-Length, the data will be sent chunked */
329 if (!htx_add_data_atonce(htx, payload))
330 goto error;
331 }
332
William Lallemand0da616e2021-10-28 15:34:26 +0200333 /* If req.payload was set, does not set the end of stream which *MUST*
334 * be set in the callback */
335 if (!hc->ops.req_payload)
336 htx->flags |= HTX_FL_EOM;
William Lallemand33b0d092021-08-13 16:05:53 +0200337
338 htx_to_buf(htx, &hc->req.buf);
339
340 return 0;
341error:
342 err_code |= ERR_ALERT | ERR_ABORT;
343 return err_code;
344}
345
346/*
347 * transfer the response to the destination buffer and wakeup the HTTP client
348 * applet so it could fill again its buffer.
349 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500350 * Return the number of bytes transferred.
William Lallemand33b0d092021-08-13 16:05:53 +0200351 */
352int httpclient_res_xfer(struct httpclient *hc, struct buffer *dst)
353{
354 int ret;
355
William Lallemand4d601842021-09-30 10:07:57 +0200356 ret = b_force_xfer(dst, &hc->res.buf, MIN(1024, b_data(&hc->res.buf)));
William Lallemand33b0d092021-08-13 16:05:53 +0200357 /* call the client once we consumed all data */
Christopher Faulet600985d2022-01-12 11:14:08 +0100358 if (!b_data(&hc->res.buf)) {
359 b_free(&hc->res.buf);
360 if (hc->appctx)
361 appctx_wakeup(hc->appctx);
362 }
William Lallemand33b0d092021-08-13 16:05:53 +0200363 return ret;
364}
365
366/*
William Lallemand0da616e2021-10-28 15:34:26 +0200367 * Transfer raw HTTP payload from src, and insert it into HTX format in the
368 * httpclient.
369 *
370 * Must be used to transfer the request body.
371 * Then wakeup the httpclient so it can transfer it.
372 *
373 * <end> tries to add the ending data flag if it succeed to copy all data.
374 *
375 * Return the number of bytes copied from src.
376 */
377int httpclient_req_xfer(struct httpclient *hc, struct ist src, int end)
378{
379 int ret = 0;
380 struct htx *htx;
381
Christopher Faulet600985d2022-01-12 11:14:08 +0100382 if (!b_alloc(&hc->req.buf))
383 goto error;
384
William Lallemand0da616e2021-10-28 15:34:26 +0200385 htx = htx_from_buf(&hc->req.buf);
386 if (!htx)
387 goto error;
388
389 if (hc->appctx)
390 appctx_wakeup(hc->appctx);
391
392 ret += htx_add_data(htx, src);
393
394
395 /* if we copied all the data and the end flag is set */
396 if ((istlen(src) == ret) && end) {
397 htx->flags |= HTX_FL_EOM;
398 }
399 htx_to_buf(htx, &hc->req.buf);
400
401error:
402
403 return ret;
404}
405
406
407/*
William Lallemand33b0d092021-08-13 16:05:53 +0200408 * Start the HTTP client
409 * Create the appctx, session, stream and wakeup the applet
410 *
411 * FIXME: It also fill the sockaddr with the IP address, but currently only IP
412 * in the URL are supported, it lacks a resolver.
413 *
414 * Return the <appctx> or NULL if it failed
415 */
416struct appctx *httpclient_start(struct httpclient *hc)
417{
418 struct applet *applet = &httpclient_applet;
419 struct appctx *appctx;
420 struct session *sess;
421 struct stream *s;
422 int len;
423 struct split_url out;
424
425 /* parse URI and fill sockaddr_storage */
426 /* FIXME: use a resolver */
William Lallemand614e6832021-09-26 18:12:43 +0200427 len = url2sa(istptr(hc->req.url), istlen(hc->req.url), &hc->dst, &out);
William Lallemand33b0d092021-08-13 16:05:53 +0200428 if (len == -1) {
William Lallemand614e6832021-09-26 18:12:43 +0200429 ha_alert("httpclient: cannot parse uri '%s'.\n", istptr(hc->req.url));
William Lallemand33b0d092021-08-13 16:05:53 +0200430 goto out;
431 }
432
433 /* The HTTP client will be created in the same thread as the caller,
434 * avoiding threading issues */
Willy Tarreaue6124462021-09-13 10:07:38 +0200435 appctx = appctx_new(applet);
William Lallemand33b0d092021-08-13 16:05:53 +0200436 if (!appctx)
437 goto out;
438
439 sess = session_new(httpclient_proxy, NULL, &appctx->obj_type);
440 if (!sess) {
441 ha_alert("httpclient: out of memory in %s:%d.\n", __FUNCTION__, __LINE__);
442 goto out_free_appctx;
443 }
444 if ((s = stream_new(sess, &appctx->obj_type, &BUF_NULL)) == NULL) {
445 ha_alert("httpclient: Failed to initialize stream %s:%d.\n", __FUNCTION__, __LINE__);
446 goto out_free_appctx;
447 }
448
Christopher Faulet16f16af2021-10-27 09:34:56 +0200449 if (!sockaddr_alloc(&s->si[1].dst, &hc->dst, sizeof(hc->dst))) {
William Lallemand33b0d092021-08-13 16:05:53 +0200450 ha_alert("httpclient: Failed to initialize stream in %s:%d.\n", __FUNCTION__, __LINE__);
451 goto out_free_stream;
452 }
453
454 /* choose the SSL server or not */
455 switch (out.scheme) {
456 case SCH_HTTP:
457 s->target = &httpclient_srv_raw->obj_type;
458 break;
459 case SCH_HTTPS:
William Lallemand957ab132021-08-24 18:33:28 +0200460#ifdef USE_OPENSSL
William Lallemand33b0d092021-08-13 16:05:53 +0200461 s->target = &httpclient_srv_ssl->obj_type;
William Lallemand957ab132021-08-24 18:33:28 +0200462#else
463 ha_alert("httpclient: OpenSSL is not available %s:%d.\n", __FUNCTION__, __LINE__);
464 goto out_free_stream;
465#endif
William Lallemand33b0d092021-08-13 16:05:53 +0200466 break;
467 }
468
469 s->flags |= SF_ASSIGNED|SF_ADDR_SET;
470 s->si[1].flags |= SI_FL_NOLINGER;
471 s->res.flags |= CF_READ_DONTWAIT;
472
473 /* applet is waiting for data */
474 si_cant_get(&s->si[0]);
475 appctx_wakeup(appctx);
476
477 task_wakeup(s->task, TASK_WOKEN_INIT);
478 hc->appctx = appctx;
William Lallemandb8b13702021-09-28 12:15:37 +0200479 hc->flags |= HTTPCLIENT_FS_STARTED;
William Lallemand33b0d092021-08-13 16:05:53 +0200480 appctx->ctx.httpclient.ptr = hc;
481 appctx->st0 = HTTPCLIENT_S_REQ;
482
483 return appctx;
484
485out_free_stream:
486 LIST_DELETE(&s->list);
487 pool_free(pool_head_stream, s);
488out_free_sess:
489 session_free(sess);
490out_free_appctx:
491 appctx_free(appctx);
492out:
493
494 return NULL;
495}
496
William Lallemandecb83e12021-09-28 11:00:43 +0200497/*
498 * This function tries to destroy the httpclient if it wasn't running.
499 * If it was running, stop the client and ask it to autodestroy itself.
500 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500501 * Once this function is used, all pointer sto the client must be removed
William Lallemandecb83e12021-09-28 11:00:43 +0200502 *
503 */
504void httpclient_stop_and_destroy(struct httpclient *hc)
505{
506
William Lallemandb8b13702021-09-28 12:15:37 +0200507 /* The httpclient was already stopped or never started, we can safely destroy it */
508 if (hc->flags & HTTPCLIENT_FS_ENDED || !(hc->flags & HTTPCLIENT_FS_STARTED)) {
William Lallemandecb83e12021-09-28 11:00:43 +0200509 httpclient_destroy(hc);
510 } else {
511 /* if the client wasn't stopped, ask for a stop and destroy */
512 hc->flags |= (HTTPCLIENT_FA_AUTOKILL | HTTPCLIENT_FA_STOP);
513 if (hc->appctx)
514 appctx_wakeup(hc->appctx);
515 }
516}
517
William Lallemand33b0d092021-08-13 16:05:53 +0200518/* Free the httpclient */
519void httpclient_destroy(struct httpclient *hc)
520{
William Lallemand03f5a1c2021-09-27 15:17:47 +0200521 struct http_hdr *hdrs;
522
523
William Lallemand33b0d092021-08-13 16:05:53 +0200524 if (!hc)
525 return;
William Lallemandecb83e12021-09-28 11:00:43 +0200526
William Lallemand2a879002021-10-05 15:50:45 +0200527 /* we should never destroy a client which was started but not stopped */
528 BUG_ON(httpclient_started(hc) && !httpclient_ended(hc));
William Lallemandecb83e12021-09-28 11:00:43 +0200529
William Lallemand03f5a1c2021-09-27 15:17:47 +0200530 /* request */
531 istfree(&hc->req.url);
William Lallemand33b0d092021-08-13 16:05:53 +0200532 b_free(&hc->req.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200533 /* response */
534 istfree(&hc->res.vsn);
535 istfree(&hc->res.reason);
536 hdrs = hc->res.hdrs;
537 while (hdrs && isttest(hdrs->n)) {
538 istfree(&hdrs->n);
539 istfree(&hdrs->v);
540 hdrs++;
541 }
542 ha_free(&hc->res.hdrs);
William Lallemand33b0d092021-08-13 16:05:53 +0200543 b_free(&hc->res.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200544
545
William Lallemand33b0d092021-08-13 16:05:53 +0200546 free(hc);
547
548 return;
549}
550
551/* Allocate an httpclient and its buffers
552 * Return NULL on failure */
553struct httpclient *httpclient_new(void *caller, enum http_meth_t meth, struct ist url)
554{
555 struct httpclient *hc;
William Lallemand33b0d092021-08-13 16:05:53 +0200556
557 hc = calloc(1, sizeof(*hc));
558 if (!hc)
559 goto err;
560
Christopher Faulet600985d2022-01-12 11:14:08 +0100561 hc->req.buf = BUF_NULL;
562 hc->res.buf = BUF_NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200563 hc->caller = caller;
William Lallemand67b77842021-11-10 16:57:25 +0100564 hc->req.url = istdup(url);
William Lallemand33b0d092021-08-13 16:05:53 +0200565 hc->req.meth = meth;
566
567 return hc;
568
569err:
570 httpclient_destroy(hc);
571 return NULL;
572}
573
574static void httpclient_applet_io_handler(struct appctx *appctx)
575{
576 struct httpclient *hc = appctx->ctx.httpclient.ptr;
577 struct stream_interface *si = appctx->owner;
578 struct stream *s = si_strm(si);
579 struct channel *req = &s->req;
580 struct channel *res = &s->res;
581 struct htx_blk *blk = NULL;
582 struct htx *htx;
William Lallemandb7020302021-08-20 11:24:13 +0200583 struct htx_sl *sl = NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200584 int32_t pos;
585 uint32_t hdr_num;
William Lallemand933fe392021-11-04 09:45:58 +0100586 int ret;
William Lallemand33b0d092021-08-13 16:05:53 +0200587
588
589 while (1) {
William Lallemandecb83e12021-09-28 11:00:43 +0200590
591 /* required to stop */
592 if (hc->flags & HTTPCLIENT_FA_STOP)
593 goto end;
594
William Lallemand33b0d092021-08-13 16:05:53 +0200595 switch(appctx->st0) {
596
597 case HTTPCLIENT_S_REQ:
William Lallemanddb8a1f32021-11-08 16:55:14 +0100598 /* we know that the buffer is empty here, since
599 * it's the first call, we can freely copy the
600 * request from the httpclient buffer */
William Lallemand933fe392021-11-04 09:45:58 +0100601 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
William Lallemanddb8a1f32021-11-08 16:55:14 +0100602 if (!ret)
603 goto more;
William Lallemand933fe392021-11-04 09:45:58 +0100604
Christopher Faulet600985d2022-01-12 11:14:08 +0100605 if (!b_data(&hc->req.buf))
606 b_free(&hc->req.buf);
607
William Lallemanddb8a1f32021-11-08 16:55:14 +0100608 htx = htx_from_buf(&req->buf);
William Lallemand933fe392021-11-04 09:45:58 +0100609 if (!htx)
610 goto more;
611
William Lallemanddb8a1f32021-11-08 16:55:14 +0100612 channel_add_input(req, htx->data);
613
William Lallemand933fe392021-11-04 09:45:58 +0100614 if (htx->flags & HTX_FL_EOM) /* check if a body need to be added */
615 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
616 else
617 appctx->st0 = HTTPCLIENT_S_REQ_BODY;
618
William Lallemand33b0d092021-08-13 16:05:53 +0200619 goto more; /* we need to leave the IO handler once we wrote the request */
620 break;
William Lallemand0da616e2021-10-28 15:34:26 +0200621 case HTTPCLIENT_S_REQ_BODY:
622 /* call the payload callback */
623 {
624 if (hc->ops.req_payload) {
William Lallemand0da616e2021-10-28 15:34:26 +0200625
William Lallemand0da616e2021-10-28 15:34:26 +0200626 /* call the request callback */
627 hc->ops.req_payload(hc);
William Lallemanddb8a1f32021-11-08 16:55:14 +0100628 /* check if the request buffer is empty */
629
630 htx = htx_from_buf(&req->buf);
631 if (!htx_is_empty(htx))
632 goto more;
633 /* Here htx_to_buf() will set buffer data to 0 because
634 * the HTX is empty, and allow us to do an xfer.
635 */
636 htx_to_buf(htx, &req->buf);
637
638 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
639 if (!ret)
640 goto more;
Christopher Faulet600985d2022-01-12 11:14:08 +0100641
642 if (!b_data(&hc->req.buf))
643 b_free(&hc->req.buf);
644
William Lallemanddb8a1f32021-11-08 16:55:14 +0100645 htx = htx_from_buf(&req->buf);
646 if (!htx)
647 goto more;
648
649 channel_add_input(req, htx->data);
William Lallemand0da616e2021-10-28 15:34:26 +0200650 }
651
William Lallemanddb8a1f32021-11-08 16:55:14 +0100652 htx = htx_from_buf(&req->buf);
William Lallemand0da616e2021-10-28 15:34:26 +0200653 if (!htx)
654 goto more;
655
656 /* if the request contains the HTX_FL_EOM, we finished the request part. */
657 if (htx->flags & HTX_FL_EOM)
658 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
659
660 goto more; /* we need to leave the IO handler once we wrote the request */
661 }
662 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200663
664 case HTTPCLIENT_S_RES_STLINE:
665 /* copy the start line in the hc structure,then remove the htx block */
666 if (!b_data(&res->buf))
667 goto more;
668 htx = htxbuf(&res->buf);
669 if (!htx)
670 goto more;
671 blk = htx_get_first_blk(htx);
672 if (blk && (htx_get_blk_type(blk) == HTX_BLK_RES_SL))
673 sl = htx_get_blk_ptr(htx, blk);
674 if (!sl || (!(sl->flags & HTX_SL_F_IS_RESP)))
675 goto more;
676
677 /* copy the status line in the httpclient */
678 hc->res.status = sl->info.res.status;
679 hc->res.vsn = istdup(htx_sl_res_vsn(sl));
680 hc->res.reason = istdup(htx_sl_res_reason(sl));
681 co_htx_remove_blk(res, htx, blk);
682 /* caller callback */
683 if (hc->ops.res_stline)
684 hc->ops.res_stline(hc);
685
686 /* if there is no HTX data anymore and the EOM flag is
687 * set, leave (no body) */
688 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM)
689 appctx->st0 = HTTPCLIENT_S_RES_END;
690 else
691 appctx->st0 = HTTPCLIENT_S_RES_HDR;
692 break;
693
694 case HTTPCLIENT_S_RES_HDR:
695 /* first copy the headers in a local hdrs
696 * structure, once we the total numbers of the
697 * header we allocate the right size and copy
698 * them. The htx block of the headers are
699 * removed each time one is read */
700 {
701 struct http_hdr hdrs[global.tune.max_http_hdr];
702
703 if (!b_data(&res->buf))
704 goto more;
705 htx = htxbuf(&res->buf);
706 if (!htx)
707 goto more;
708
709 hdr_num = 0;
710
711 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
712 struct htx_blk *blk = htx_get_blk(htx, pos);
713 enum htx_blk_type type = htx_get_blk_type(blk);
714
715 if (type == HTX_BLK_EOH) {
716 hdrs[hdr_num].n = IST_NULL;
717 hdrs[hdr_num].v = IST_NULL;
718 co_htx_remove_blk(res, htx, blk);
719 break;
720 }
721
722 if (type != HTX_BLK_HDR)
723 continue;
724
725 hdrs[hdr_num].n = istdup(htx_get_blk_name(htx, blk));
726 hdrs[hdr_num].v = istdup(htx_get_blk_value(htx, blk));
727 if (!isttest(hdrs[hdr_num].v) || !isttest(hdrs[hdr_num].n))
728 goto end;
729 co_htx_remove_blk(res, htx, blk);
730 hdr_num++;
731 }
732
William Lallemand0d6f7792021-08-20 11:59:49 +0200733 if (hdr_num) {
734 /* alloc and copy the headers in the httpclient struct */
735 hc->res.hdrs = calloc((hdr_num + 1), sizeof(*hc->res.hdrs));
736 if (!hc->res.hdrs)
737 goto end;
738 memcpy(hc->res.hdrs, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
William Lallemand33b0d092021-08-13 16:05:53 +0200739
William Lallemand0d6f7792021-08-20 11:59:49 +0200740 /* caller callback */
741 if (hc->ops.res_headers)
742 hc->ops.res_headers(hc);
743 }
William Lallemand33b0d092021-08-13 16:05:53 +0200744
745 /* if there is no HTX data anymore and the EOM flag is
746 * set, leave (no body) */
William Lallemand1123dde2021-09-21 10:58:10 +0200747 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM) {
William Lallemand33b0d092021-08-13 16:05:53 +0200748 appctx->st0 = HTTPCLIENT_S_RES_END;
William Lallemand1123dde2021-09-21 10:58:10 +0200749 } else {
William Lallemand33b0d092021-08-13 16:05:53 +0200750 appctx->st0 = HTTPCLIENT_S_RES_BODY;
William Lallemand1123dde2021-09-21 10:58:10 +0200751 }
William Lallemand33b0d092021-08-13 16:05:53 +0200752 }
753 break;
754
755 case HTTPCLIENT_S_RES_BODY:
756 /*
757 * The IO handler removes the htx blocks in the response buffer and
758 * push them in the hc->res.buf buffer in a raw format.
759 */
760 htx = htxbuf(&res->buf);
761 if (!htx || htx_is_empty(htx))
762 goto more;
763
Christopher Faulet600985d2022-01-12 11:14:08 +0100764 if (!b_alloc(&hc->res.buf))
765 goto more;
766
William Lallemand33b0d092021-08-13 16:05:53 +0200767 if (b_full(&hc->res.buf))
Christopher Faulet600985d2022-01-12 11:14:08 +0100768 goto process_data;
William Lallemand33b0d092021-08-13 16:05:53 +0200769
770 /* decapsule the htx data to raw data */
771 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
772 enum htx_blk_type type;
773
774 blk = htx_get_blk(htx, pos);
775 type = htx_get_blk_type(blk);
776 if (type == HTX_BLK_DATA) {
777 struct ist v = htx_get_blk_value(htx, blk);
778
779 if ((b_room(&hc->res.buf) < v.len) )
780 goto process_data;
781
782 __b_putblk(&hc->res.buf, v.ptr, v.len);
783 co_htx_remove_blk(res, htx, blk);
784 /* the data must be processed by the caller in the receive phase */
785 if (hc->ops.res_payload)
786 hc->ops.res_payload(hc);
787 } else {
788 /* remove any block which is not a data block */
789 co_htx_remove_blk(res, htx, blk);
790 }
791 }
792 /* if not finished, should be called again */
793 if (!(htx->flags & HTX_FL_EOM))
794 goto more;
795
796 /* end of message, we should quit */
797 appctx->st0 = HTTPCLIENT_S_RES_END;
798 break;
799
800 case HTTPCLIENT_S_RES_END:
801 goto end;
802 break;
803 }
804 }
805
806process_data:
807
808 si_rx_chan_rdy(si);
809
810 return;
811more:
812 /* There was not enough data in the response channel */
813
814 si_rx_room_blk(si);
815
816 if (appctx->st0 == HTTPCLIENT_S_RES_END)
817 goto end;
818
819 /* The state machine tries to handle as much data as possible, if there
820 * isn't any data to handle and a shutdown is detected, let's stop
821 * everything */
822 if ((req->flags & (CF_SHUTR|CF_SHUTR_NOW)) ||
823 (res->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
824 goto end;
825 }
826 return;
827
828end:
829 if (hc->ops.res_end)
830 hc->ops.res_end(hc);
831 si_shutw(si);
832 si_shutr(si);
833 return;
834}
835
836static void httpclient_applet_release(struct appctx *appctx)
837{
838 struct httpclient *hc = appctx->ctx.httpclient.ptr;
839
William Lallemand1123dde2021-09-21 10:58:10 +0200840 /* mark the httpclient as ended */
William Lallemandecb83e12021-09-28 11:00:43 +0200841 hc->flags |= HTTPCLIENT_FS_ENDED;
William Lallemand33b0d092021-08-13 16:05:53 +0200842 /* the applet is leaving, remove the ptr so we don't try to call it
843 * again from the caller */
844 hc->appctx = NULL;
845
William Lallemandecb83e12021-09-28 11:00:43 +0200846
847 /* destroy the httpclient when set to autotokill */
848 if (hc->flags & HTTPCLIENT_FA_AUTOKILL) {
849 httpclient_destroy(hc);
850 }
851
William Lallemand33b0d092021-08-13 16:05:53 +0200852 return;
853}
854
855/* HTTP client applet */
856static struct applet httpclient_applet = {
857 .obj_type = OBJ_TYPE_APPLET,
858 .name = "<HTTPCLIENT>",
859 .fct = httpclient_applet_io_handler,
860 .release = httpclient_applet_release,
861};
862
William Lallemand83614a92021-08-13 14:47:57 +0200863/*
864 * Initialize the proxy for the HTTP client with 2 servers, one for raw HTTP,
865 * the other for HTTPS.
866 */
867
868static int httpclient_init()
869{
870 int err_code = 0;
871 char *errmsg = NULL;
872
873 httpclient_proxy = alloc_new_proxy("<HTTPCLIENT>", PR_CAP_LISTEN|PR_CAP_INT, &errmsg);
874 if (!httpclient_proxy) {
875 err_code |= ERR_ALERT | ERR_FATAL;
876 goto err;
877 }
878
Willy Tarreau0e72e402021-08-20 10:23:12 +0200879 proxy_preset_defaults(httpclient_proxy);
880
William Lallemand83614a92021-08-13 14:47:57 +0200881 httpclient_proxy->options2 |= PR_O2_INDEPSTR;
882 httpclient_proxy->mode = PR_MODE_HTTP;
883 httpclient_proxy->maxconn = 0;
884 httpclient_proxy->accept = NULL;
885 httpclient_proxy->timeout.client = TICK_ETERNITY;
886 /* The HTTP Client use the "option httplog" with the global log server */
887 httpclient_proxy->conf.logformat_string = default_http_log_format;
888 httpclient_proxy->http_needed = 1;
889
890 /* clear HTTP server */
891 httpclient_srv_raw = new_server(httpclient_proxy);
892 if (!httpclient_srv_raw) {
893 err_code |= ERR_ALERT | ERR_FATAL;
894 memprintf(&errmsg, "out of memory.");
895 goto err;
896 }
897
898 httpclient_srv_raw->iweight = 0;
899 httpclient_srv_raw->uweight = 0;
900 httpclient_srv_raw->xprt = xprt_get(XPRT_RAW);
901 httpclient_srv_raw->id = strdup("<HTTPCLIENT>");
902 if (!httpclient_srv_raw->id)
903 goto err;
904
William Lallemand957ab132021-08-24 18:33:28 +0200905#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +0200906 /* SSL HTTP server */
907 httpclient_srv_ssl = new_server(httpclient_proxy);
908 if (!httpclient_srv_ssl) {
909 memprintf(&errmsg, "out of memory.");
910 err_code |= ERR_ALERT | ERR_FATAL;
911 goto err;
912 }
913 httpclient_srv_ssl->iweight = 0;
914 httpclient_srv_ssl->uweight = 0;
915 httpclient_srv_ssl->xprt = xprt_get(XPRT_SSL);
916 httpclient_srv_ssl->use_ssl = 1;
William Lallemand211c9672021-08-24 17:18:13 +0200917 httpclient_srv_ssl->id = strdup("<HTTPSCLIENT>");
William Lallemand83614a92021-08-13 14:47:57 +0200918 if (!httpclient_srv_ssl->id)
919 goto err;
920
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200921 httpclient_srv_ssl->ssl_ctx.verify = SSL_SOCK_VERIFY_NONE;
William Lallemand957ab132021-08-24 18:33:28 +0200922#endif
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200923
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500924 /* add the proxy in the proxy list only if everything is successful */
William Lallemand83614a92021-08-13 14:47:57 +0200925 httpclient_proxy->next = proxies_list;
926 proxies_list = httpclient_proxy;
927
William Lallemand211c9672021-08-24 17:18:13 +0200928 /* link the 2 servers in the proxy */
929 httpclient_srv_raw->next = httpclient_proxy->srv;
William Lallemand957ab132021-08-24 18:33:28 +0200930 httpclient_proxy->srv = httpclient_srv_raw;
931
932#ifdef USE_OPENSSL
933 httpclient_srv_ssl->next = httpclient_proxy->srv;
William Lallemand211c9672021-08-24 17:18:13 +0200934 httpclient_proxy->srv = httpclient_srv_ssl;
William Lallemand957ab132021-08-24 18:33:28 +0200935#endif
936
William Lallemand211c9672021-08-24 17:18:13 +0200937
William Lallemand83614a92021-08-13 14:47:57 +0200938 return 0;
939
940err:
941 ha_alert("httpclient: cannot initialize.\n");
942 free(errmsg);
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200943 srv_drop(httpclient_srv_raw);
William Lallemand957ab132021-08-24 18:33:28 +0200944#ifdef USE_OPENSSL
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200945 srv_drop(httpclient_srv_ssl);
William Lallemand957ab132021-08-24 18:33:28 +0200946#endif
William Lallemand83614a92021-08-13 14:47:57 +0200947 free_proxy(httpclient_proxy);
948 return err_code;
949}
950
William Lallemand83614a92021-08-13 14:47:57 +0200951static int httpclient_cfg_postparser()
952{
953 struct logsrv *logsrv;
954 struct proxy *curproxy = httpclient_proxy;
955
956 /* copy logs from "global" log list */
957 list_for_each_entry(logsrv, &global.logsrvs, list) {
958 struct logsrv *node = malloc(sizeof(*node));
959
960 if (!node) {
961 ha_alert("httpclient: cannot allocate memory.\n");
962 goto err;
963 }
964
965 memcpy(node, logsrv, sizeof(*node));
966 LIST_INIT(&node->list);
967 LIST_APPEND(&curproxy->logsrvs, &node->list);
968 }
969 if (curproxy->conf.logformat_string) {
970 char *err = NULL;
971
972 curproxy->conf.args.ctx = ARGC_LOG;
973 if (!parse_logformat_string(curproxy->conf.logformat_string, curproxy, &curproxy->logformat,
974 LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
975 SMP_VAL_FE_LOG_END, &err)) {
976 ha_alert("httpclient: failed to parse log-format : %s.\n", err);
977 free(err);
978 goto err;
979 }
980 curproxy->conf.args.file = NULL;
981 curproxy->conf.args.line = 0;
982 }
983 return 0;
984err:
985 return 1;
986}
987
William Lallemand83614a92021-08-13 14:47:57 +0200988/* initialize the proxy and servers for the HTTP client */
989
990INITCALL0(STG_REGISTER, httpclient_init);
991REGISTER_CONFIG_POSTPARSER("httpclient", httpclient_cfg_postparser);