blob: d41d59a7b112f1765407e8ed4167f3c3f80ca39a [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 }
Christopher Faulet6ced61d2022-01-12 15:27:41 +0100444 if ((s = stream_new(sess, &appctx->obj_type, &hc->req.buf)) == NULL) {
William Lallemand33b0d092021-08-13 16:05:53 +0200445 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;
Christopher Faulet6ced61d2022-01-12 15:27:41 +0100481
482 /* The request was transferred when the stream was created. So switch
483 * directly to REQ_BODY or RES_STLINE state
484 */
485 appctx->st0 = (hc->ops.req_payload ? HTTPCLIENT_S_REQ_BODY : HTTPCLIENT_S_RES_STLINE);
William Lallemand33b0d092021-08-13 16:05:53 +0200486
487 return appctx;
488
489out_free_stream:
490 LIST_DELETE(&s->list);
491 pool_free(pool_head_stream, s);
492out_free_sess:
493 session_free(sess);
494out_free_appctx:
495 appctx_free(appctx);
496out:
497
498 return NULL;
499}
500
William Lallemandecb83e12021-09-28 11:00:43 +0200501/*
502 * This function tries to destroy the httpclient if it wasn't running.
503 * If it was running, stop the client and ask it to autodestroy itself.
504 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500505 * Once this function is used, all pointer sto the client must be removed
William Lallemandecb83e12021-09-28 11:00:43 +0200506 *
507 */
508void httpclient_stop_and_destroy(struct httpclient *hc)
509{
510
William Lallemandb8b13702021-09-28 12:15:37 +0200511 /* The httpclient was already stopped or never started, we can safely destroy it */
512 if (hc->flags & HTTPCLIENT_FS_ENDED || !(hc->flags & HTTPCLIENT_FS_STARTED)) {
William Lallemandecb83e12021-09-28 11:00:43 +0200513 httpclient_destroy(hc);
514 } else {
515 /* if the client wasn't stopped, ask for a stop and destroy */
516 hc->flags |= (HTTPCLIENT_FA_AUTOKILL | HTTPCLIENT_FA_STOP);
517 if (hc->appctx)
518 appctx_wakeup(hc->appctx);
519 }
520}
521
William Lallemand33b0d092021-08-13 16:05:53 +0200522/* Free the httpclient */
523void httpclient_destroy(struct httpclient *hc)
524{
William Lallemand03f5a1c2021-09-27 15:17:47 +0200525 struct http_hdr *hdrs;
526
527
William Lallemand33b0d092021-08-13 16:05:53 +0200528 if (!hc)
529 return;
William Lallemandecb83e12021-09-28 11:00:43 +0200530
William Lallemand2a879002021-10-05 15:50:45 +0200531 /* we should never destroy a client which was started but not stopped */
532 BUG_ON(httpclient_started(hc) && !httpclient_ended(hc));
William Lallemandecb83e12021-09-28 11:00:43 +0200533
William Lallemand03f5a1c2021-09-27 15:17:47 +0200534 /* request */
535 istfree(&hc->req.url);
William Lallemand33b0d092021-08-13 16:05:53 +0200536 b_free(&hc->req.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200537 /* response */
538 istfree(&hc->res.vsn);
539 istfree(&hc->res.reason);
540 hdrs = hc->res.hdrs;
541 while (hdrs && isttest(hdrs->n)) {
542 istfree(&hdrs->n);
543 istfree(&hdrs->v);
544 hdrs++;
545 }
546 ha_free(&hc->res.hdrs);
William Lallemand33b0d092021-08-13 16:05:53 +0200547 b_free(&hc->res.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200548
549
William Lallemand33b0d092021-08-13 16:05:53 +0200550 free(hc);
551
552 return;
553}
554
555/* Allocate an httpclient and its buffers
556 * Return NULL on failure */
557struct httpclient *httpclient_new(void *caller, enum http_meth_t meth, struct ist url)
558{
559 struct httpclient *hc;
William Lallemand33b0d092021-08-13 16:05:53 +0200560
561 hc = calloc(1, sizeof(*hc));
562 if (!hc)
563 goto err;
564
Christopher Faulet600985d2022-01-12 11:14:08 +0100565 hc->req.buf = BUF_NULL;
566 hc->res.buf = BUF_NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200567 hc->caller = caller;
William Lallemand67b77842021-11-10 16:57:25 +0100568 hc->req.url = istdup(url);
William Lallemand33b0d092021-08-13 16:05:53 +0200569 hc->req.meth = meth;
570
571 return hc;
572
573err:
574 httpclient_destroy(hc);
575 return NULL;
576}
577
578static void httpclient_applet_io_handler(struct appctx *appctx)
579{
580 struct httpclient *hc = appctx->ctx.httpclient.ptr;
581 struct stream_interface *si = appctx->owner;
582 struct stream *s = si_strm(si);
583 struct channel *req = &s->req;
584 struct channel *res = &s->res;
585 struct htx_blk *blk = NULL;
586 struct htx *htx;
William Lallemandb7020302021-08-20 11:24:13 +0200587 struct htx_sl *sl = NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200588 int32_t pos;
589 uint32_t hdr_num;
William Lallemand933fe392021-11-04 09:45:58 +0100590 int ret;
William Lallemand33b0d092021-08-13 16:05:53 +0200591
592
593 while (1) {
William Lallemandecb83e12021-09-28 11:00:43 +0200594
595 /* required to stop */
596 if (hc->flags & HTTPCLIENT_FA_STOP)
597 goto end;
598
William Lallemand33b0d092021-08-13 16:05:53 +0200599 switch(appctx->st0) {
600
601 case HTTPCLIENT_S_REQ:
William Lallemanddb8a1f32021-11-08 16:55:14 +0100602 /* we know that the buffer is empty here, since
603 * it's the first call, we can freely copy the
604 * request from the httpclient buffer */
William Lallemand933fe392021-11-04 09:45:58 +0100605 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
William Lallemanddb8a1f32021-11-08 16:55:14 +0100606 if (!ret)
607 goto more;
William Lallemand933fe392021-11-04 09:45:58 +0100608
Christopher Faulet600985d2022-01-12 11:14:08 +0100609 if (!b_data(&hc->req.buf))
610 b_free(&hc->req.buf);
611
William Lallemanddb8a1f32021-11-08 16:55:14 +0100612 htx = htx_from_buf(&req->buf);
William Lallemand933fe392021-11-04 09:45:58 +0100613 if (!htx)
614 goto more;
615
William Lallemanddb8a1f32021-11-08 16:55:14 +0100616 channel_add_input(req, htx->data);
617
William Lallemand933fe392021-11-04 09:45:58 +0100618 if (htx->flags & HTX_FL_EOM) /* check if a body need to be added */
619 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
620 else
621 appctx->st0 = HTTPCLIENT_S_REQ_BODY;
622
William Lallemand33b0d092021-08-13 16:05:53 +0200623 goto more; /* we need to leave the IO handler once we wrote the request */
624 break;
William Lallemand0da616e2021-10-28 15:34:26 +0200625 case HTTPCLIENT_S_REQ_BODY:
626 /* call the payload callback */
627 {
628 if (hc->ops.req_payload) {
William Lallemand0da616e2021-10-28 15:34:26 +0200629
William Lallemand0da616e2021-10-28 15:34:26 +0200630 /* call the request callback */
631 hc->ops.req_payload(hc);
William Lallemanddb8a1f32021-11-08 16:55:14 +0100632 /* check if the request buffer is empty */
633
634 htx = htx_from_buf(&req->buf);
635 if (!htx_is_empty(htx))
636 goto more;
637 /* Here htx_to_buf() will set buffer data to 0 because
638 * the HTX is empty, and allow us to do an xfer.
639 */
640 htx_to_buf(htx, &req->buf);
641
642 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
643 if (!ret)
644 goto more;
Christopher Faulet600985d2022-01-12 11:14:08 +0100645
646 if (!b_data(&hc->req.buf))
647 b_free(&hc->req.buf);
648
William Lallemanddb8a1f32021-11-08 16:55:14 +0100649 htx = htx_from_buf(&req->buf);
650 if (!htx)
651 goto more;
652
653 channel_add_input(req, htx->data);
William Lallemand0da616e2021-10-28 15:34:26 +0200654 }
655
William Lallemanddb8a1f32021-11-08 16:55:14 +0100656 htx = htx_from_buf(&req->buf);
William Lallemand0da616e2021-10-28 15:34:26 +0200657 if (!htx)
658 goto more;
659
660 /* if the request contains the HTX_FL_EOM, we finished the request part. */
661 if (htx->flags & HTX_FL_EOM)
662 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
663
664 goto more; /* we need to leave the IO handler once we wrote the request */
665 }
666 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200667
668 case HTTPCLIENT_S_RES_STLINE:
669 /* copy the start line in the hc structure,then remove the htx block */
670 if (!b_data(&res->buf))
671 goto more;
672 htx = htxbuf(&res->buf);
673 if (!htx)
674 goto more;
675 blk = htx_get_first_blk(htx);
676 if (blk && (htx_get_blk_type(blk) == HTX_BLK_RES_SL))
677 sl = htx_get_blk_ptr(htx, blk);
678 if (!sl || (!(sl->flags & HTX_SL_F_IS_RESP)))
679 goto more;
680
681 /* copy the status line in the httpclient */
682 hc->res.status = sl->info.res.status;
683 hc->res.vsn = istdup(htx_sl_res_vsn(sl));
684 hc->res.reason = istdup(htx_sl_res_reason(sl));
685 co_htx_remove_blk(res, htx, blk);
686 /* caller callback */
687 if (hc->ops.res_stline)
688 hc->ops.res_stline(hc);
689
690 /* if there is no HTX data anymore and the EOM flag is
691 * set, leave (no body) */
692 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM)
693 appctx->st0 = HTTPCLIENT_S_RES_END;
694 else
695 appctx->st0 = HTTPCLIENT_S_RES_HDR;
696 break;
697
698 case HTTPCLIENT_S_RES_HDR:
699 /* first copy the headers in a local hdrs
700 * structure, once we the total numbers of the
701 * header we allocate the right size and copy
702 * them. The htx block of the headers are
703 * removed each time one is read */
704 {
705 struct http_hdr hdrs[global.tune.max_http_hdr];
706
707 if (!b_data(&res->buf))
708 goto more;
709 htx = htxbuf(&res->buf);
710 if (!htx)
711 goto more;
712
713 hdr_num = 0;
714
715 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
716 struct htx_blk *blk = htx_get_blk(htx, pos);
717 enum htx_blk_type type = htx_get_blk_type(blk);
718
719 if (type == HTX_BLK_EOH) {
720 hdrs[hdr_num].n = IST_NULL;
721 hdrs[hdr_num].v = IST_NULL;
722 co_htx_remove_blk(res, htx, blk);
723 break;
724 }
725
726 if (type != HTX_BLK_HDR)
727 continue;
728
729 hdrs[hdr_num].n = istdup(htx_get_blk_name(htx, blk));
730 hdrs[hdr_num].v = istdup(htx_get_blk_value(htx, blk));
731 if (!isttest(hdrs[hdr_num].v) || !isttest(hdrs[hdr_num].n))
732 goto end;
733 co_htx_remove_blk(res, htx, blk);
734 hdr_num++;
735 }
736
William Lallemand0d6f7792021-08-20 11:59:49 +0200737 if (hdr_num) {
738 /* alloc and copy the headers in the httpclient struct */
739 hc->res.hdrs = calloc((hdr_num + 1), sizeof(*hc->res.hdrs));
740 if (!hc->res.hdrs)
741 goto end;
742 memcpy(hc->res.hdrs, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
William Lallemand33b0d092021-08-13 16:05:53 +0200743
William Lallemand0d6f7792021-08-20 11:59:49 +0200744 /* caller callback */
745 if (hc->ops.res_headers)
746 hc->ops.res_headers(hc);
747 }
William Lallemand33b0d092021-08-13 16:05:53 +0200748
749 /* if there is no HTX data anymore and the EOM flag is
750 * set, leave (no body) */
William Lallemand1123dde2021-09-21 10:58:10 +0200751 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM) {
William Lallemand33b0d092021-08-13 16:05:53 +0200752 appctx->st0 = HTTPCLIENT_S_RES_END;
William Lallemand1123dde2021-09-21 10:58:10 +0200753 } else {
William Lallemand33b0d092021-08-13 16:05:53 +0200754 appctx->st0 = HTTPCLIENT_S_RES_BODY;
William Lallemand1123dde2021-09-21 10:58:10 +0200755 }
William Lallemand33b0d092021-08-13 16:05:53 +0200756 }
757 break;
758
759 case HTTPCLIENT_S_RES_BODY:
760 /*
761 * The IO handler removes the htx blocks in the response buffer and
762 * push them in the hc->res.buf buffer in a raw format.
763 */
764 htx = htxbuf(&res->buf);
765 if (!htx || htx_is_empty(htx))
766 goto more;
767
Christopher Faulet600985d2022-01-12 11:14:08 +0100768 if (!b_alloc(&hc->res.buf))
769 goto more;
770
William Lallemand33b0d092021-08-13 16:05:53 +0200771 if (b_full(&hc->res.buf))
Christopher Faulet600985d2022-01-12 11:14:08 +0100772 goto process_data;
William Lallemand33b0d092021-08-13 16:05:53 +0200773
774 /* decapsule the htx data to raw data */
775 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
776 enum htx_blk_type type;
777
778 blk = htx_get_blk(htx, pos);
779 type = htx_get_blk_type(blk);
780 if (type == HTX_BLK_DATA) {
781 struct ist v = htx_get_blk_value(htx, blk);
782
783 if ((b_room(&hc->res.buf) < v.len) )
784 goto process_data;
785
786 __b_putblk(&hc->res.buf, v.ptr, v.len);
787 co_htx_remove_blk(res, htx, blk);
788 /* the data must be processed by the caller in the receive phase */
789 if (hc->ops.res_payload)
790 hc->ops.res_payload(hc);
791 } else {
792 /* remove any block which is not a data block */
793 co_htx_remove_blk(res, htx, blk);
794 }
795 }
796 /* if not finished, should be called again */
797 if (!(htx->flags & HTX_FL_EOM))
798 goto more;
799
800 /* end of message, we should quit */
801 appctx->st0 = HTTPCLIENT_S_RES_END;
802 break;
803
804 case HTTPCLIENT_S_RES_END:
805 goto end;
806 break;
807 }
808 }
809
810process_data:
811
812 si_rx_chan_rdy(si);
813
814 return;
815more:
816 /* There was not enough data in the response channel */
817
818 si_rx_room_blk(si);
819
820 if (appctx->st0 == HTTPCLIENT_S_RES_END)
821 goto end;
822
823 /* The state machine tries to handle as much data as possible, if there
824 * isn't any data to handle and a shutdown is detected, let's stop
825 * everything */
826 if ((req->flags & (CF_SHUTR|CF_SHUTR_NOW)) ||
827 (res->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
828 goto end;
829 }
830 return;
831
832end:
833 if (hc->ops.res_end)
834 hc->ops.res_end(hc);
835 si_shutw(si);
836 si_shutr(si);
837 return;
838}
839
840static void httpclient_applet_release(struct appctx *appctx)
841{
842 struct httpclient *hc = appctx->ctx.httpclient.ptr;
843
William Lallemand1123dde2021-09-21 10:58:10 +0200844 /* mark the httpclient as ended */
William Lallemandecb83e12021-09-28 11:00:43 +0200845 hc->flags |= HTTPCLIENT_FS_ENDED;
William Lallemand33b0d092021-08-13 16:05:53 +0200846 /* the applet is leaving, remove the ptr so we don't try to call it
847 * again from the caller */
848 hc->appctx = NULL;
849
William Lallemandecb83e12021-09-28 11:00:43 +0200850
851 /* destroy the httpclient when set to autotokill */
852 if (hc->flags & HTTPCLIENT_FA_AUTOKILL) {
853 httpclient_destroy(hc);
854 }
855
William Lallemand33b0d092021-08-13 16:05:53 +0200856 return;
857}
858
859/* HTTP client applet */
860static struct applet httpclient_applet = {
861 .obj_type = OBJ_TYPE_APPLET,
862 .name = "<HTTPCLIENT>",
863 .fct = httpclient_applet_io_handler,
864 .release = httpclient_applet_release,
865};
866
William Lallemand83614a92021-08-13 14:47:57 +0200867/*
868 * Initialize the proxy for the HTTP client with 2 servers, one for raw HTTP,
869 * the other for HTTPS.
870 */
871
872static int httpclient_init()
873{
874 int err_code = 0;
875 char *errmsg = NULL;
876
877 httpclient_proxy = alloc_new_proxy("<HTTPCLIENT>", PR_CAP_LISTEN|PR_CAP_INT, &errmsg);
878 if (!httpclient_proxy) {
879 err_code |= ERR_ALERT | ERR_FATAL;
880 goto err;
881 }
882
Willy Tarreau0e72e402021-08-20 10:23:12 +0200883 proxy_preset_defaults(httpclient_proxy);
884
William Lallemand83614a92021-08-13 14:47:57 +0200885 httpclient_proxy->options2 |= PR_O2_INDEPSTR;
886 httpclient_proxy->mode = PR_MODE_HTTP;
887 httpclient_proxy->maxconn = 0;
888 httpclient_proxy->accept = NULL;
889 httpclient_proxy->timeout.client = TICK_ETERNITY;
890 /* The HTTP Client use the "option httplog" with the global log server */
891 httpclient_proxy->conf.logformat_string = default_http_log_format;
892 httpclient_proxy->http_needed = 1;
893
894 /* clear HTTP server */
895 httpclient_srv_raw = new_server(httpclient_proxy);
896 if (!httpclient_srv_raw) {
897 err_code |= ERR_ALERT | ERR_FATAL;
898 memprintf(&errmsg, "out of memory.");
899 goto err;
900 }
901
902 httpclient_srv_raw->iweight = 0;
903 httpclient_srv_raw->uweight = 0;
904 httpclient_srv_raw->xprt = xprt_get(XPRT_RAW);
905 httpclient_srv_raw->id = strdup("<HTTPCLIENT>");
906 if (!httpclient_srv_raw->id)
907 goto err;
908
William Lallemand957ab132021-08-24 18:33:28 +0200909#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +0200910 /* SSL HTTP server */
911 httpclient_srv_ssl = new_server(httpclient_proxy);
912 if (!httpclient_srv_ssl) {
913 memprintf(&errmsg, "out of memory.");
914 err_code |= ERR_ALERT | ERR_FATAL;
915 goto err;
916 }
917 httpclient_srv_ssl->iweight = 0;
918 httpclient_srv_ssl->uweight = 0;
919 httpclient_srv_ssl->xprt = xprt_get(XPRT_SSL);
920 httpclient_srv_ssl->use_ssl = 1;
William Lallemand211c9672021-08-24 17:18:13 +0200921 httpclient_srv_ssl->id = strdup("<HTTPSCLIENT>");
William Lallemand83614a92021-08-13 14:47:57 +0200922 if (!httpclient_srv_ssl->id)
923 goto err;
924
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200925 httpclient_srv_ssl->ssl_ctx.verify = SSL_SOCK_VERIFY_NONE;
William Lallemand957ab132021-08-24 18:33:28 +0200926#endif
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200927
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500928 /* add the proxy in the proxy list only if everything is successful */
William Lallemand83614a92021-08-13 14:47:57 +0200929 httpclient_proxy->next = proxies_list;
930 proxies_list = httpclient_proxy;
931
William Lallemand211c9672021-08-24 17:18:13 +0200932 /* link the 2 servers in the proxy */
933 httpclient_srv_raw->next = httpclient_proxy->srv;
William Lallemand957ab132021-08-24 18:33:28 +0200934 httpclient_proxy->srv = httpclient_srv_raw;
935
936#ifdef USE_OPENSSL
937 httpclient_srv_ssl->next = httpclient_proxy->srv;
William Lallemand211c9672021-08-24 17:18:13 +0200938 httpclient_proxy->srv = httpclient_srv_ssl;
William Lallemand957ab132021-08-24 18:33:28 +0200939#endif
940
William Lallemand211c9672021-08-24 17:18:13 +0200941
William Lallemand83614a92021-08-13 14:47:57 +0200942 return 0;
943
944err:
945 ha_alert("httpclient: cannot initialize.\n");
946 free(errmsg);
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200947 srv_drop(httpclient_srv_raw);
William Lallemand957ab132021-08-24 18:33:28 +0200948#ifdef USE_OPENSSL
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200949 srv_drop(httpclient_srv_ssl);
William Lallemand957ab132021-08-24 18:33:28 +0200950#endif
William Lallemand83614a92021-08-13 14:47:57 +0200951 free_proxy(httpclient_proxy);
952 return err_code;
953}
954
William Lallemand83614a92021-08-13 14:47:57 +0200955static int httpclient_cfg_postparser()
956{
957 struct logsrv *logsrv;
958 struct proxy *curproxy = httpclient_proxy;
959
960 /* copy logs from "global" log list */
961 list_for_each_entry(logsrv, &global.logsrvs, list) {
962 struct logsrv *node = malloc(sizeof(*node));
963
964 if (!node) {
965 ha_alert("httpclient: cannot allocate memory.\n");
966 goto err;
967 }
968
969 memcpy(node, logsrv, sizeof(*node));
970 LIST_INIT(&node->list);
971 LIST_APPEND(&curproxy->logsrvs, &node->list);
972 }
973 if (curproxy->conf.logformat_string) {
974 char *err = NULL;
975
976 curproxy->conf.args.ctx = ARGC_LOG;
977 if (!parse_logformat_string(curproxy->conf.logformat_string, curproxy, &curproxy->logformat,
978 LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
979 SMP_VAL_FE_LOG_END, &err)) {
980 ha_alert("httpclient: failed to parse log-format : %s.\n", err);
981 free(err);
982 goto err;
983 }
984 curproxy->conf.args.file = NULL;
985 curproxy->conf.args.line = 0;
986 }
987 return 0;
988err:
989 return 1;
990}
991
William Lallemand83614a92021-08-13 14:47:57 +0200992/* initialize the proxy and servers for the HTTP client */
993
994INITCALL0(STG_REGISTER, httpclient_init);
995REGISTER_CONFIG_POSTPARSER("httpclient", httpclient_cfg_postparser);