blob: 674c919cc248e5441a3c4f8988e3d3e838988755 [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 Lallemand33b0d092021-08-13 16:05:53 +020022#include <haproxy/h1_htx.h>
23#include <haproxy/http.h>
24#include <haproxy/http_client.h>
25#include <haproxy/http_htx.h>
26#include <haproxy/htx.h>
William Lallemand83614a92021-08-13 14:47:57 +020027#include <haproxy/log.h>
28#include <haproxy/proxy.h>
William Lallemand2a8fe8b2021-08-20 14:25:15 +020029#include <haproxy/server.h>
Willy Tarreau1df20422021-10-06 11:28:24 +020030#include <haproxy/ssl_sock-t.h>
William Lallemand33b0d092021-08-13 16:05:53 +020031#include <haproxy/stream_interface.h>
William Lallemand83614a92021-08-13 14:47:57 +020032#include <haproxy/tools.h>
33
34#include <string.h>
35
36
37static struct proxy *httpclient_proxy;
38static struct server *httpclient_srv_raw;
William Lallemand957ab132021-08-24 18:33:28 +020039#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +020040static struct server *httpclient_srv_ssl;
William Lallemand957ab132021-08-24 18:33:28 +020041#endif
William Lallemand33b0d092021-08-13 16:05:53 +020042static struct applet httpclient_applet;
43
William Lallemand03a4eb12021-08-18 16:46:21 +020044/* --- This part of the file implement an HTTP client over the CLI ---
45 * The functions will be starting by "hc_cli" for "httpclient cli"
46 */
47
48static struct http_hdr default_httpclient_hdrs[2] = {
William Lallemand2484da52021-08-19 15:55:19 +020049 { .n = IST("User-Agent"), .v = IST("HAProxy") },
William Lallemand03a4eb12021-08-18 16:46:21 +020050 { .n = IST_NULL, .v = IST_NULL },
51};
52
53
54/* What kind of data we need to read */
55#define HC_CLI_F_RES_STLINE 0x01
56#define HC_CLI_F_RES_HDR 0x02
57#define HC_CLI_F_RES_BODY 0x04
58#define HC_CLI_F_RES_END 0x08
59
60
61/* These are the callback used by the HTTP Client when it needs to notify new
62 * data, we only sets a flag in the IO handler */
63
64void hc_cli_res_stline_cb(struct httpclient *hc)
65{
66 struct appctx *appctx = hc->caller;
67
William Lallemanddfc3f892021-08-20 11:35:29 +020068 if (!appctx)
69 return;
70
William Lallemand03a4eb12021-08-18 16:46:21 +020071 appctx->ctx.cli.i0 |= HC_CLI_F_RES_STLINE;
William Lallemanddfc3f892021-08-20 11:35:29 +020072 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020073}
74
75void hc_cli_res_headers_cb(struct httpclient *hc)
76{
77 struct appctx *appctx = hc->caller;
78
William Lallemanddfc3f892021-08-20 11:35:29 +020079 if (!appctx)
80 return;
81
William Lallemand03a4eb12021-08-18 16:46:21 +020082 appctx->ctx.cli.i0 |= HC_CLI_F_RES_HDR;
William Lallemanddfc3f892021-08-20 11:35:29 +020083 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020084}
85
86void hc_cli_res_body_cb(struct httpclient *hc)
87{
88 struct appctx *appctx = hc->caller;
89
William Lallemanddfc3f892021-08-20 11:35:29 +020090 if (!appctx)
91 return;
92
William Lallemand03a4eb12021-08-18 16:46:21 +020093 appctx->ctx.cli.i0 |= HC_CLI_F_RES_BODY;
William Lallemanddfc3f892021-08-20 11:35:29 +020094 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020095}
96
97void hc_cli_res_end_cb(struct httpclient *hc)
98{
99 struct appctx *appctx = hc->caller;
100
William Lallemanddfc3f892021-08-20 11:35:29 +0200101 if (!appctx)
102 return;
103
William Lallemand03a4eb12021-08-18 16:46:21 +0200104 appctx->ctx.cli.i0 |= HC_CLI_F_RES_END;
William Lallemanddfc3f892021-08-20 11:35:29 +0200105 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +0200106}
107
108/*
109 * Parse an httpclient keyword on the cli:
110 * httpclient <ID> <method> <URI>
111 */
112static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
113{
114 struct httpclient *hc;
115 char *err = NULL;
116 enum http_meth_t meth;
117 char *meth_str;
118 struct ist uri;
119
120 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
121 return 1;
122
123 if (!*args[1] || !*args[2]) {
124 memprintf(&err, ": not enough parameters");
125 goto err;
126 }
127
128 meth_str = args[1];
129 uri = ist(args[2]);
130
131 meth = find_http_meth(meth_str, strlen(meth_str));
132
133 hc = httpclient_new(appctx, meth, uri);
134 if (!hc) {
135 goto err;
136 }
137
138 /* update the httpclient callbacks */
139 hc->ops.res_stline = hc_cli_res_stline_cb;
140 hc->ops.res_headers = hc_cli_res_headers_cb;
141 hc->ops.res_payload = hc_cli_res_body_cb;
142 hc->ops.res_end = hc_cli_res_end_cb;
143
144 appctx->ctx.cli.p0 = hc; /* store the httpclient ptr in the applet */
145 appctx->ctx.cli.i0 = 0;
146
147 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, default_httpclient_hdrs) != ERR_NONE)
148 goto err;
149
150
151 if (!httpclient_start(hc))
152 goto err;
153
154 return 0;
155
156err:
157 memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : "");
158 return cli_err(appctx, err);
159}
160
161/* This function dumps the content of the httpclient receive buffer
162 * on the CLI output
163 *
164 * Return 1 when the processing is finished
165 * return 0 if it needs to be called again
166 */
167static int hc_cli_io_handler(struct appctx *appctx)
168{
169 struct stream_interface *si = appctx->owner;
170 struct buffer *trash = alloc_trash_chunk();
171 struct httpclient *hc = appctx->ctx.cli.p0;
172 struct http_hdr *hdrs, *hdr;
173
174 if (!trash)
175 goto out;
176 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_STLINE) {
William Lallemand614e6832021-09-26 18:12:43 +0200177 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 +0200178 if (ci_putchk(si_ic(si), trash) == -1)
179 si_rx_room_blk(si);
180 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_STLINE;
181 goto out;
182 }
183
184 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_HDR) {
185 hdrs = hc->res.hdrs;
186 for (hdr = hdrs; isttest(hdr->v); hdr++) {
187 if (!h1_format_htx_hdr(hdr->n, hdr->v, trash))
188 goto out;
189 }
190 if (!chunk_memcat(trash, "\r\n", 2))
191 goto out;
192 if (ci_putchk(si_ic(si), trash) == -1)
193 si_rx_room_blk(si);
194 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_HDR;
195 goto out;
196 }
197
198 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_BODY) {
199 int ret;
200
201 ret = httpclient_res_xfer(hc, &si_ic(si)->buf);
202 channel_add_input(si_ic(si), ret); /* forward what we put in the buffer channel */
203
William Lallemand518878e2021-09-21 10:45:34 +0200204 if (!httpclient_data(hc)) {/* remove the flag if the buffer was emptied */
William Lallemand03a4eb12021-08-18 16:46:21 +0200205 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_BODY;
206 }
207 goto out;
208 }
209
210 /* we must close only if F_END is the last flag */
211 if (appctx->ctx.cli.i0 == HC_CLI_F_RES_END) {
212 si_shutw(si);
213 si_shutr(si);
214 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_END;
215 goto out;
216 }
217
218out:
219 /* we didn't clear every flags, we should come back to finish things */
220 if (appctx->ctx.cli.i0)
221 si_rx_room_blk(si);
222
223 free_trash_chunk(trash);
224 return 0;
225}
226
227static void hc_cli_release(struct appctx *appctx)
228{
229 struct httpclient *hc = appctx->ctx.cli.p0;
230
231 /* Everything possible was printed on the CLI, we can destroy the client */
William Lallemandecb83e12021-09-28 11:00:43 +0200232 httpclient_stop_and_destroy(hc);
William Lallemand03a4eb12021-08-18 16:46:21 +0200233
234 return;
235}
236
237/* register cli keywords */
238static struct cli_kw_list cli_kws = {{ },{
239 { { "httpclient", NULL }, "httpclient <method> <URI> : launch an HTTP request", hc_cli_parse, hc_cli_io_handler, hc_cli_release},
240 { { NULL }, NULL, NULL, NULL }
241}};
242
243INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
244
245
246/* --- This part of the file implements the actual HTTP client API --- */
247
William Lallemand33b0d092021-08-13 16:05:53 +0200248/*
249 * Generate a simple request and fill the httpclient request buffer with it.
250 * The request contains a request line generated from the absolute <url> and
251 * <meth> as well as list of headers <hdrs>.
252 *
253 * If the buffer was filled correctly the function returns 0, if not it returns
254 * an error_code but there is no guarantee that the buffer wasn't modified.
255 */
256int httpclient_req_gen(struct httpclient *hc, const struct ist url, enum http_meth_t meth, const struct http_hdr *hdrs)
257{
258 struct htx_sl *sl;
259 struct htx *htx;
260 int err_code = 0;
261 struct ist meth_ist, vsn;
262 unsigned int flags = HTX_SL_F_VER_11 | HTX_SL_F_BODYLESS | HTX_SL_F_XFER_LEN | HTX_SL_F_NORMALIZED_URI | HTX_SL_F_HAS_SCHM;
263
264 if (meth >= HTTP_METH_OTHER)
265 goto error;
266
267 meth_ist = http_known_methods[meth];
268
269 vsn = ist("HTTP/1.1");
270
271 htx = htx_from_buf(&hc->req.buf);
272 if (!htx)
273 goto error;
274 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_ist, url, vsn);
275 if (!sl) {
276 goto error;
277 }
278 sl->info.req.meth = meth;
279
280 /* Add Host Header from URL */
William Lallemand4463b172021-08-24 17:53:03 +0200281 if (!htx_add_header(htx, ist("Host"), IST_NULL))
282 goto error;
William Lallemand33b0d092021-08-13 16:05:53 +0200283 if (!http_update_host(htx, sl, url))
284 goto error;
285
286 /* add the headers and EOH */
William Lallemand79a34782021-09-20 16:19:15 +0200287 if (hdrs) {
288 if (!htx_add_all_headers(htx, hdrs))
289 goto error;
290 } else {
291 if (!htx_add_endof(htx, HTX_BLK_EOH))
292 goto error;
293 }
William Lallemand33b0d092021-08-13 16:05:53 +0200294
295 htx->flags |= HTX_FL_EOM;
296
297 htx_to_buf(htx, &hc->req.buf);
298
299 return 0;
300error:
301 err_code |= ERR_ALERT | ERR_ABORT;
302 return err_code;
303}
304
305/*
306 * transfer the response to the destination buffer and wakeup the HTTP client
307 * applet so it could fill again its buffer.
308 *
309 * Return the number of bytes transfered.
310 */
311int httpclient_res_xfer(struct httpclient *hc, struct buffer *dst)
312{
313 int ret;
314
William Lallemand4d601842021-09-30 10:07:57 +0200315 ret = b_force_xfer(dst, &hc->res.buf, MIN(1024, b_data(&hc->res.buf)));
William Lallemand33b0d092021-08-13 16:05:53 +0200316 /* call the client once we consumed all data */
317 if (!b_data(&hc->res.buf) && hc->appctx)
318 appctx_wakeup(hc->appctx);
319 return ret;
320}
321
322/*
323 * Start the HTTP client
324 * Create the appctx, session, stream and wakeup the applet
325 *
326 * FIXME: It also fill the sockaddr with the IP address, but currently only IP
327 * in the URL are supported, it lacks a resolver.
328 *
329 * Return the <appctx> or NULL if it failed
330 */
331struct appctx *httpclient_start(struct httpclient *hc)
332{
333 struct applet *applet = &httpclient_applet;
334 struct appctx *appctx;
335 struct session *sess;
336 struct stream *s;
337 int len;
338 struct split_url out;
339
340 /* parse URI and fill sockaddr_storage */
341 /* FIXME: use a resolver */
William Lallemand614e6832021-09-26 18:12:43 +0200342 len = url2sa(istptr(hc->req.url), istlen(hc->req.url), &hc->dst, &out);
William Lallemand33b0d092021-08-13 16:05:53 +0200343 if (len == -1) {
William Lallemand614e6832021-09-26 18:12:43 +0200344 ha_alert("httpclient: cannot parse uri '%s'.\n", istptr(hc->req.url));
William Lallemand33b0d092021-08-13 16:05:53 +0200345 goto out;
346 }
347
348 /* The HTTP client will be created in the same thread as the caller,
349 * avoiding threading issues */
Willy Tarreaue6124462021-09-13 10:07:38 +0200350 appctx = appctx_new(applet);
William Lallemand33b0d092021-08-13 16:05:53 +0200351 if (!appctx)
352 goto out;
353
354 sess = session_new(httpclient_proxy, NULL, &appctx->obj_type);
355 if (!sess) {
356 ha_alert("httpclient: out of memory in %s:%d.\n", __FUNCTION__, __LINE__);
357 goto out_free_appctx;
358 }
359 if ((s = stream_new(sess, &appctx->obj_type, &BUF_NULL)) == NULL) {
360 ha_alert("httpclient: Failed to initialize stream %s:%d.\n", __FUNCTION__, __LINE__);
361 goto out_free_appctx;
362 }
363
364 if (!sockaddr_alloc(&s->target_addr, &hc->dst, sizeof(hc->dst))) {
365 ha_alert("httpclient: Failed to initialize stream in %s:%d.\n", __FUNCTION__, __LINE__);
366 goto out_free_stream;
367 }
368
369 /* choose the SSL server or not */
370 switch (out.scheme) {
371 case SCH_HTTP:
372 s->target = &httpclient_srv_raw->obj_type;
373 break;
374 case SCH_HTTPS:
William Lallemand957ab132021-08-24 18:33:28 +0200375#ifdef USE_OPENSSL
William Lallemand33b0d092021-08-13 16:05:53 +0200376 s->target = &httpclient_srv_ssl->obj_type;
William Lallemand957ab132021-08-24 18:33:28 +0200377#else
378 ha_alert("httpclient: OpenSSL is not available %s:%d.\n", __FUNCTION__, __LINE__);
379 goto out_free_stream;
380#endif
William Lallemand33b0d092021-08-13 16:05:53 +0200381 break;
382 }
383
384 s->flags |= SF_ASSIGNED|SF_ADDR_SET;
385 s->si[1].flags |= SI_FL_NOLINGER;
386 s->res.flags |= CF_READ_DONTWAIT;
387
388 /* applet is waiting for data */
389 si_cant_get(&s->si[0]);
390 appctx_wakeup(appctx);
391
392 task_wakeup(s->task, TASK_WOKEN_INIT);
393 hc->appctx = appctx;
William Lallemandb8b13702021-09-28 12:15:37 +0200394 hc->flags |= HTTPCLIENT_FS_STARTED;
William Lallemand33b0d092021-08-13 16:05:53 +0200395 appctx->ctx.httpclient.ptr = hc;
396 appctx->st0 = HTTPCLIENT_S_REQ;
397
398 return appctx;
399
400out_free_stream:
401 LIST_DELETE(&s->list);
402 pool_free(pool_head_stream, s);
403out_free_sess:
404 session_free(sess);
405out_free_appctx:
406 appctx_free(appctx);
407out:
408
409 return NULL;
410}
411
William Lallemandecb83e12021-09-28 11:00:43 +0200412/*
413 * This function tries to destroy the httpclient if it wasn't running.
414 * If it was running, stop the client and ask it to autodestroy itself.
415 *
416 * Once this fonction is used, all pointer sto the client must be removed
417 *
418 */
419void httpclient_stop_and_destroy(struct httpclient *hc)
420{
421
William Lallemandb8b13702021-09-28 12:15:37 +0200422 /* The httpclient was already stopped or never started, we can safely destroy it */
423 if (hc->flags & HTTPCLIENT_FS_ENDED || !(hc->flags & HTTPCLIENT_FS_STARTED)) {
William Lallemandecb83e12021-09-28 11:00:43 +0200424 httpclient_destroy(hc);
425 } else {
426 /* if the client wasn't stopped, ask for a stop and destroy */
427 hc->flags |= (HTTPCLIENT_FA_AUTOKILL | HTTPCLIENT_FA_STOP);
428 if (hc->appctx)
429 appctx_wakeup(hc->appctx);
430 }
431}
432
William Lallemand33b0d092021-08-13 16:05:53 +0200433/* Free the httpclient */
434void httpclient_destroy(struct httpclient *hc)
435{
William Lallemand03f5a1c2021-09-27 15:17:47 +0200436 struct http_hdr *hdrs;
437
438
William Lallemand33b0d092021-08-13 16:05:53 +0200439 if (!hc)
440 return;
William Lallemandecb83e12021-09-28 11:00:43 +0200441
William Lallemand2a879002021-10-05 15:50:45 +0200442 /* we should never destroy a client which was started but not stopped */
443 BUG_ON(httpclient_started(hc) && !httpclient_ended(hc));
William Lallemandecb83e12021-09-28 11:00:43 +0200444
William Lallemand03f5a1c2021-09-27 15:17:47 +0200445 /* request */
446 istfree(&hc->req.url);
William Lallemand33b0d092021-08-13 16:05:53 +0200447 b_free(&hc->req.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200448 /* response */
449 istfree(&hc->res.vsn);
450 istfree(&hc->res.reason);
451 hdrs = hc->res.hdrs;
452 while (hdrs && isttest(hdrs->n)) {
453 istfree(&hdrs->n);
454 istfree(&hdrs->v);
455 hdrs++;
456 }
457 ha_free(&hc->res.hdrs);
William Lallemand33b0d092021-08-13 16:05:53 +0200458 b_free(&hc->res.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200459
460
William Lallemand33b0d092021-08-13 16:05:53 +0200461 free(hc);
462
463 return;
464}
465
466/* Allocate an httpclient and its buffers
467 * Return NULL on failure */
468struct httpclient *httpclient_new(void *caller, enum http_meth_t meth, struct ist url)
469{
470 struct httpclient *hc;
471 struct buffer *b;
472
473 hc = calloc(1, sizeof(*hc));
474 if (!hc)
475 goto err;
476
477 b = b_alloc(&hc->req.buf);
478 if (!b)
479 goto err;
480 b = b_alloc(&hc->res.buf);
481 if (!b)
482 goto err;
483
484 hc->caller = caller;
485 hc->req.url = url;
486 hc->req.meth = meth;
487
488 return hc;
489
490err:
491 httpclient_destroy(hc);
492 return NULL;
493}
494
495static void httpclient_applet_io_handler(struct appctx *appctx)
496{
497 struct httpclient *hc = appctx->ctx.httpclient.ptr;
498 struct stream_interface *si = appctx->owner;
499 struct stream *s = si_strm(si);
500 struct channel *req = &s->req;
501 struct channel *res = &s->res;
502 struct htx_blk *blk = NULL;
503 struct htx *htx;
William Lallemandb7020302021-08-20 11:24:13 +0200504 struct htx_sl *sl = NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200505 int32_t pos;
506 uint32_t hdr_num;
507
508
509 while (1) {
William Lallemandecb83e12021-09-28 11:00:43 +0200510
511 /* required to stop */
512 if (hc->flags & HTTPCLIENT_FA_STOP)
513 goto end;
514
William Lallemand33b0d092021-08-13 16:05:53 +0200515 switch(appctx->st0) {
516
517 case HTTPCLIENT_S_REQ:
518 /* copy the request from the hc->req.buf buffer */
519 htx = htx_from_buf(&req->buf);
520 /* We now that it fits the content of a buffer so can
521 * just push this entirely */
522 b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
523 channel_add_input(req, b_data(&req->buf));
524 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
525 goto more; /* we need to leave the IO handler once we wrote the request */
526 break;
527
528 case HTTPCLIENT_S_RES_STLINE:
529 /* copy the start line in the hc structure,then remove the htx block */
530 if (!b_data(&res->buf))
531 goto more;
532 htx = htxbuf(&res->buf);
533 if (!htx)
534 goto more;
535 blk = htx_get_first_blk(htx);
536 if (blk && (htx_get_blk_type(blk) == HTX_BLK_RES_SL))
537 sl = htx_get_blk_ptr(htx, blk);
538 if (!sl || (!(sl->flags & HTX_SL_F_IS_RESP)))
539 goto more;
540
541 /* copy the status line in the httpclient */
542 hc->res.status = sl->info.res.status;
543 hc->res.vsn = istdup(htx_sl_res_vsn(sl));
544 hc->res.reason = istdup(htx_sl_res_reason(sl));
545 co_htx_remove_blk(res, htx, blk);
546 /* caller callback */
547 if (hc->ops.res_stline)
548 hc->ops.res_stline(hc);
549
550 /* if there is no HTX data anymore and the EOM flag is
551 * set, leave (no body) */
552 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM)
553 appctx->st0 = HTTPCLIENT_S_RES_END;
554 else
555 appctx->st0 = HTTPCLIENT_S_RES_HDR;
556 break;
557
558 case HTTPCLIENT_S_RES_HDR:
559 /* first copy the headers in a local hdrs
560 * structure, once we the total numbers of the
561 * header we allocate the right size and copy
562 * them. The htx block of the headers are
563 * removed each time one is read */
564 {
565 struct http_hdr hdrs[global.tune.max_http_hdr];
566
567 if (!b_data(&res->buf))
568 goto more;
569 htx = htxbuf(&res->buf);
570 if (!htx)
571 goto more;
572
573 hdr_num = 0;
574
575 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
576 struct htx_blk *blk = htx_get_blk(htx, pos);
577 enum htx_blk_type type = htx_get_blk_type(blk);
578
579 if (type == HTX_BLK_EOH) {
580 hdrs[hdr_num].n = IST_NULL;
581 hdrs[hdr_num].v = IST_NULL;
582 co_htx_remove_blk(res, htx, blk);
583 break;
584 }
585
586 if (type != HTX_BLK_HDR)
587 continue;
588
589 hdrs[hdr_num].n = istdup(htx_get_blk_name(htx, blk));
590 hdrs[hdr_num].v = istdup(htx_get_blk_value(htx, blk));
591 if (!isttest(hdrs[hdr_num].v) || !isttest(hdrs[hdr_num].n))
592 goto end;
593 co_htx_remove_blk(res, htx, blk);
594 hdr_num++;
595 }
596
William Lallemand0d6f7792021-08-20 11:59:49 +0200597 if (hdr_num) {
598 /* alloc and copy the headers in the httpclient struct */
599 hc->res.hdrs = calloc((hdr_num + 1), sizeof(*hc->res.hdrs));
600 if (!hc->res.hdrs)
601 goto end;
602 memcpy(hc->res.hdrs, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
William Lallemand33b0d092021-08-13 16:05:53 +0200603
William Lallemand0d6f7792021-08-20 11:59:49 +0200604 /* caller callback */
605 if (hc->ops.res_headers)
606 hc->ops.res_headers(hc);
607 }
William Lallemand33b0d092021-08-13 16:05:53 +0200608
609 /* if there is no HTX data anymore and the EOM flag is
610 * set, leave (no body) */
William Lallemand1123dde2021-09-21 10:58:10 +0200611 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM) {
William Lallemand33b0d092021-08-13 16:05:53 +0200612 appctx->st0 = HTTPCLIENT_S_RES_END;
William Lallemand1123dde2021-09-21 10:58:10 +0200613 } else {
William Lallemand33b0d092021-08-13 16:05:53 +0200614 appctx->st0 = HTTPCLIENT_S_RES_BODY;
William Lallemand1123dde2021-09-21 10:58:10 +0200615 }
William Lallemand33b0d092021-08-13 16:05:53 +0200616 }
617 break;
618
619 case HTTPCLIENT_S_RES_BODY:
620 /*
621 * The IO handler removes the htx blocks in the response buffer and
622 * push them in the hc->res.buf buffer in a raw format.
623 */
624 htx = htxbuf(&res->buf);
625 if (!htx || htx_is_empty(htx))
626 goto more;
627
628 if (b_full(&hc->res.buf))
629 goto process_data;
630
631 /* decapsule the htx data to raw data */
632 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
633 enum htx_blk_type type;
634
635 blk = htx_get_blk(htx, pos);
636 type = htx_get_blk_type(blk);
637 if (type == HTX_BLK_DATA) {
638 struct ist v = htx_get_blk_value(htx, blk);
639
640 if ((b_room(&hc->res.buf) < v.len) )
641 goto process_data;
642
643 __b_putblk(&hc->res.buf, v.ptr, v.len);
644 co_htx_remove_blk(res, htx, blk);
645 /* the data must be processed by the caller in the receive phase */
646 if (hc->ops.res_payload)
647 hc->ops.res_payload(hc);
648 } else {
649 /* remove any block which is not a data block */
650 co_htx_remove_blk(res, htx, blk);
651 }
652 }
653 /* if not finished, should be called again */
654 if (!(htx->flags & HTX_FL_EOM))
655 goto more;
656
657 /* end of message, we should quit */
658 appctx->st0 = HTTPCLIENT_S_RES_END;
659 break;
660
661 case HTTPCLIENT_S_RES_END:
662 goto end;
663 break;
664 }
665 }
666
667process_data:
668
669 si_rx_chan_rdy(si);
670
671 return;
672more:
673 /* There was not enough data in the response channel */
674
675 si_rx_room_blk(si);
676
677 if (appctx->st0 == HTTPCLIENT_S_RES_END)
678 goto end;
679
680 /* The state machine tries to handle as much data as possible, if there
681 * isn't any data to handle and a shutdown is detected, let's stop
682 * everything */
683 if ((req->flags & (CF_SHUTR|CF_SHUTR_NOW)) ||
684 (res->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
685 goto end;
686 }
687 return;
688
689end:
690 if (hc->ops.res_end)
691 hc->ops.res_end(hc);
692 si_shutw(si);
693 si_shutr(si);
694 return;
695}
696
697static void httpclient_applet_release(struct appctx *appctx)
698{
699 struct httpclient *hc = appctx->ctx.httpclient.ptr;
700
William Lallemand1123dde2021-09-21 10:58:10 +0200701 /* mark the httpclient as ended */
William Lallemandecb83e12021-09-28 11:00:43 +0200702 hc->flags |= HTTPCLIENT_FS_ENDED;
William Lallemand33b0d092021-08-13 16:05:53 +0200703 /* the applet is leaving, remove the ptr so we don't try to call it
704 * again from the caller */
705 hc->appctx = NULL;
706
William Lallemandecb83e12021-09-28 11:00:43 +0200707
708 /* destroy the httpclient when set to autotokill */
709 if (hc->flags & HTTPCLIENT_FA_AUTOKILL) {
710 httpclient_destroy(hc);
711 }
712
William Lallemand33b0d092021-08-13 16:05:53 +0200713 return;
714}
715
716/* HTTP client applet */
717static struct applet httpclient_applet = {
718 .obj_type = OBJ_TYPE_APPLET,
719 .name = "<HTTPCLIENT>",
720 .fct = httpclient_applet_io_handler,
721 .release = httpclient_applet_release,
722};
723
William Lallemand83614a92021-08-13 14:47:57 +0200724/*
725 * Initialize the proxy for the HTTP client with 2 servers, one for raw HTTP,
726 * the other for HTTPS.
727 */
728
729static int httpclient_init()
730{
731 int err_code = 0;
732 char *errmsg = NULL;
733
734 httpclient_proxy = alloc_new_proxy("<HTTPCLIENT>", PR_CAP_LISTEN|PR_CAP_INT, &errmsg);
735 if (!httpclient_proxy) {
736 err_code |= ERR_ALERT | ERR_FATAL;
737 goto err;
738 }
739
Willy Tarreau0e72e402021-08-20 10:23:12 +0200740 proxy_preset_defaults(httpclient_proxy);
741
William Lallemand83614a92021-08-13 14:47:57 +0200742 httpclient_proxy->options2 |= PR_O2_INDEPSTR;
743 httpclient_proxy->mode = PR_MODE_HTTP;
744 httpclient_proxy->maxconn = 0;
745 httpclient_proxy->accept = NULL;
746 httpclient_proxy->timeout.client = TICK_ETERNITY;
747 /* The HTTP Client use the "option httplog" with the global log server */
748 httpclient_proxy->conf.logformat_string = default_http_log_format;
749 httpclient_proxy->http_needed = 1;
750
751 /* clear HTTP server */
752 httpclient_srv_raw = new_server(httpclient_proxy);
753 if (!httpclient_srv_raw) {
754 err_code |= ERR_ALERT | ERR_FATAL;
755 memprintf(&errmsg, "out of memory.");
756 goto err;
757 }
758
759 httpclient_srv_raw->iweight = 0;
760 httpclient_srv_raw->uweight = 0;
761 httpclient_srv_raw->xprt = xprt_get(XPRT_RAW);
762 httpclient_srv_raw->id = strdup("<HTTPCLIENT>");
763 if (!httpclient_srv_raw->id)
764 goto err;
765
William Lallemand957ab132021-08-24 18:33:28 +0200766#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +0200767 /* SSL HTTP server */
768 httpclient_srv_ssl = new_server(httpclient_proxy);
769 if (!httpclient_srv_ssl) {
770 memprintf(&errmsg, "out of memory.");
771 err_code |= ERR_ALERT | ERR_FATAL;
772 goto err;
773 }
774 httpclient_srv_ssl->iweight = 0;
775 httpclient_srv_ssl->uweight = 0;
776 httpclient_srv_ssl->xprt = xprt_get(XPRT_SSL);
777 httpclient_srv_ssl->use_ssl = 1;
William Lallemand211c9672021-08-24 17:18:13 +0200778 httpclient_srv_ssl->id = strdup("<HTTPSCLIENT>");
William Lallemand83614a92021-08-13 14:47:57 +0200779 if (!httpclient_srv_ssl->id)
780 goto err;
781
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200782 httpclient_srv_ssl->ssl_ctx.verify = SSL_SOCK_VERIFY_NONE;
William Lallemand957ab132021-08-24 18:33:28 +0200783#endif
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200784
William Lallemand83614a92021-08-13 14:47:57 +0200785 /* add the proxy in the proxy list only if everything successed */
786 httpclient_proxy->next = proxies_list;
787 proxies_list = httpclient_proxy;
788
William Lallemand211c9672021-08-24 17:18:13 +0200789 /* link the 2 servers in the proxy */
790 httpclient_srv_raw->next = httpclient_proxy->srv;
William Lallemand957ab132021-08-24 18:33:28 +0200791 httpclient_proxy->srv = httpclient_srv_raw;
792
793#ifdef USE_OPENSSL
794 httpclient_srv_ssl->next = httpclient_proxy->srv;
William Lallemand211c9672021-08-24 17:18:13 +0200795 httpclient_proxy->srv = httpclient_srv_ssl;
William Lallemand957ab132021-08-24 18:33:28 +0200796#endif
797
William Lallemand211c9672021-08-24 17:18:13 +0200798
William Lallemand83614a92021-08-13 14:47:57 +0200799 return 0;
800
801err:
802 ha_alert("httpclient: cannot initialize.\n");
803 free(errmsg);
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200804 srv_drop(httpclient_srv_raw);
William Lallemand957ab132021-08-24 18:33:28 +0200805#ifdef USE_OPENSSL
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200806 srv_drop(httpclient_srv_ssl);
William Lallemand957ab132021-08-24 18:33:28 +0200807#endif
William Lallemand83614a92021-08-13 14:47:57 +0200808 free_proxy(httpclient_proxy);
809 return err_code;
810}
811
William Lallemand83614a92021-08-13 14:47:57 +0200812static int httpclient_cfg_postparser()
813{
814 struct logsrv *logsrv;
815 struct proxy *curproxy = httpclient_proxy;
816
817 /* copy logs from "global" log list */
818 list_for_each_entry(logsrv, &global.logsrvs, list) {
819 struct logsrv *node = malloc(sizeof(*node));
820
821 if (!node) {
822 ha_alert("httpclient: cannot allocate memory.\n");
823 goto err;
824 }
825
826 memcpy(node, logsrv, sizeof(*node));
827 LIST_INIT(&node->list);
828 LIST_APPEND(&curproxy->logsrvs, &node->list);
829 }
830 if (curproxy->conf.logformat_string) {
831 char *err = NULL;
832
833 curproxy->conf.args.ctx = ARGC_LOG;
834 if (!parse_logformat_string(curproxy->conf.logformat_string, curproxy, &curproxy->logformat,
835 LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
836 SMP_VAL_FE_LOG_END, &err)) {
837 ha_alert("httpclient: failed to parse log-format : %s.\n", err);
838 free(err);
839 goto err;
840 }
841 curproxy->conf.args.file = NULL;
842 curproxy->conf.args.line = 0;
843 }
844 return 0;
845err:
846 return 1;
847}
848
William Lallemand83614a92021-08-13 14:47:57 +0200849/* initialize the proxy and servers for the HTTP client */
850
851INITCALL0(STG_REGISTER, httpclient_init);
852REGISTER_CONFIG_POSTPARSER("httpclient", httpclient_cfg_postparser);