blob: 34e27c661a7c3fdf3ce538904d269ca87675aa4e [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
49static struct http_hdr default_httpclient_hdrs[2] = {
William Lallemand2484da52021-08-19 15:55:19 +020050 { .n = IST("User-Agent"), .v = IST("HAProxy") },
William Lallemand03a4eb12021-08-18 16:46:21 +020051 { .n = IST_NULL, .v = IST_NULL },
52};
53
54
55/* What kind of data we need to read */
56#define HC_CLI_F_RES_STLINE 0x01
57#define HC_CLI_F_RES_HDR 0x02
58#define HC_CLI_F_RES_BODY 0x04
59#define HC_CLI_F_RES_END 0x08
60
61
62/* These are the callback used by the HTTP Client when it needs to notify new
63 * data, we only sets a flag in the IO handler */
64
65void hc_cli_res_stline_cb(struct httpclient *hc)
66{
67 struct appctx *appctx = hc->caller;
68
William Lallemanddfc3f892021-08-20 11:35:29 +020069 if (!appctx)
70 return;
71
William Lallemand03a4eb12021-08-18 16:46:21 +020072 appctx->ctx.cli.i0 |= HC_CLI_F_RES_STLINE;
William Lallemanddfc3f892021-08-20 11:35:29 +020073 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020074}
75
76void hc_cli_res_headers_cb(struct httpclient *hc)
77{
78 struct appctx *appctx = hc->caller;
79
William Lallemanddfc3f892021-08-20 11:35:29 +020080 if (!appctx)
81 return;
82
William Lallemand03a4eb12021-08-18 16:46:21 +020083 appctx->ctx.cli.i0 |= HC_CLI_F_RES_HDR;
William Lallemanddfc3f892021-08-20 11:35:29 +020084 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020085}
86
87void hc_cli_res_body_cb(struct httpclient *hc)
88{
89 struct appctx *appctx = hc->caller;
90
William Lallemanddfc3f892021-08-20 11:35:29 +020091 if (!appctx)
92 return;
93
William Lallemand03a4eb12021-08-18 16:46:21 +020094 appctx->ctx.cli.i0 |= HC_CLI_F_RES_BODY;
William Lallemanddfc3f892021-08-20 11:35:29 +020095 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +020096}
97
98void hc_cli_res_end_cb(struct httpclient *hc)
99{
100 struct appctx *appctx = hc->caller;
101
William Lallemanddfc3f892021-08-20 11:35:29 +0200102 if (!appctx)
103 return;
104
William Lallemand03a4eb12021-08-18 16:46:21 +0200105 appctx->ctx.cli.i0 |= HC_CLI_F_RES_END;
William Lallemanddfc3f892021-08-20 11:35:29 +0200106 appctx_wakeup(appctx);
William Lallemand03a4eb12021-08-18 16:46:21 +0200107}
108
109/*
110 * Parse an httpclient keyword on the cli:
111 * httpclient <ID> <method> <URI>
112 */
113static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
114{
115 struct httpclient *hc;
116 char *err = NULL;
117 enum http_meth_t meth;
118 char *meth_str;
119 struct ist uri;
William Lallemanddec25c32021-10-25 19:48:37 +0200120 struct ist body = IST_NULL;
William Lallemand03a4eb12021-08-18 16:46:21 +0200121
122 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
123 return 1;
124
125 if (!*args[1] || !*args[2]) {
126 memprintf(&err, ": not enough parameters");
127 goto err;
128 }
129
130 meth_str = args[1];
131 uri = ist(args[2]);
132
William Lallemanddec25c32021-10-25 19:48:37 +0200133 if (payload)
134 body = ist(payload);
135
William Lallemand03a4eb12021-08-18 16:46:21 +0200136 meth = find_http_meth(meth_str, strlen(meth_str));
137
138 hc = httpclient_new(appctx, meth, uri);
139 if (!hc) {
140 goto err;
141 }
142
143 /* update the httpclient callbacks */
144 hc->ops.res_stline = hc_cli_res_stline_cb;
145 hc->ops.res_headers = hc_cli_res_headers_cb;
146 hc->ops.res_payload = hc_cli_res_body_cb;
147 hc->ops.res_end = hc_cli_res_end_cb;
148
149 appctx->ctx.cli.p0 = hc; /* store the httpclient ptr in the applet */
150 appctx->ctx.cli.i0 = 0;
151
William Lallemanddec25c32021-10-25 19:48:37 +0200152 if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, default_httpclient_hdrs, body) != ERR_NONE)
William Lallemand03a4eb12021-08-18 16:46:21 +0200153 goto err;
154
155
156 if (!httpclient_start(hc))
157 goto err;
158
159 return 0;
160
161err:
162 memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : "");
163 return cli_err(appctx, err);
164}
165
166/* This function dumps the content of the httpclient receive buffer
167 * on the CLI output
168 *
169 * Return 1 when the processing is finished
170 * return 0 if it needs to be called again
171 */
172static int hc_cli_io_handler(struct appctx *appctx)
173{
174 struct stream_interface *si = appctx->owner;
175 struct buffer *trash = alloc_trash_chunk();
176 struct httpclient *hc = appctx->ctx.cli.p0;
177 struct http_hdr *hdrs, *hdr;
178
179 if (!trash)
180 goto out;
181 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_STLINE) {
William Lallemand614e6832021-09-26 18:12:43 +0200182 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 +0200183 if (ci_putchk(si_ic(si), trash) == -1)
184 si_rx_room_blk(si);
185 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_STLINE;
186 goto out;
187 }
188
189 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_HDR) {
190 hdrs = hc->res.hdrs;
191 for (hdr = hdrs; isttest(hdr->v); hdr++) {
192 if (!h1_format_htx_hdr(hdr->n, hdr->v, trash))
193 goto out;
194 }
195 if (!chunk_memcat(trash, "\r\n", 2))
196 goto out;
197 if (ci_putchk(si_ic(si), trash) == -1)
198 si_rx_room_blk(si);
199 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_HDR;
200 goto out;
201 }
202
203 if (appctx->ctx.cli.i0 & HC_CLI_F_RES_BODY) {
204 int ret;
205
206 ret = httpclient_res_xfer(hc, &si_ic(si)->buf);
207 channel_add_input(si_ic(si), ret); /* forward what we put in the buffer channel */
208
William Lallemand518878e2021-09-21 10:45:34 +0200209 if (!httpclient_data(hc)) {/* remove the flag if the buffer was emptied */
William Lallemand03a4eb12021-08-18 16:46:21 +0200210 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_BODY;
211 }
212 goto out;
213 }
214
215 /* we must close only if F_END is the last flag */
216 if (appctx->ctx.cli.i0 == HC_CLI_F_RES_END) {
217 si_shutw(si);
218 si_shutr(si);
219 appctx->ctx.cli.i0 &= ~HC_CLI_F_RES_END;
220 goto out;
221 }
222
223out:
224 /* we didn't clear every flags, we should come back to finish things */
225 if (appctx->ctx.cli.i0)
226 si_rx_room_blk(si);
227
228 free_trash_chunk(trash);
229 return 0;
230}
231
232static void hc_cli_release(struct appctx *appctx)
233{
234 struct httpclient *hc = appctx->ctx.cli.p0;
235
236 /* Everything possible was printed on the CLI, we can destroy the client */
William Lallemandecb83e12021-09-28 11:00:43 +0200237 httpclient_stop_and_destroy(hc);
William Lallemand03a4eb12021-08-18 16:46:21 +0200238
239 return;
240}
241
242/* register cli keywords */
243static struct cli_kw_list cli_kws = {{ },{
William Lallemand34b3a932021-10-19 10:58:30 +0200244 { { "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 +0200245 { { NULL }, NULL, NULL, NULL }
246}};
247
248INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
249
250
251/* --- This part of the file implements the actual HTTP client API --- */
252
William Lallemand33b0d092021-08-13 16:05:53 +0200253/*
254 * Generate a simple request and fill the httpclient request buffer with it.
255 * The request contains a request line generated from the absolute <url> and
256 * <meth> as well as list of headers <hdrs>.
257 *
258 * If the buffer was filled correctly the function returns 0, if not it returns
259 * an error_code but there is no guarantee that the buffer wasn't modified.
260 */
William Lallemanddec25c32021-10-25 19:48:37 +0200261int 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 +0200262{
263 struct htx_sl *sl;
264 struct htx *htx;
265 int err_code = 0;
266 struct ist meth_ist, vsn;
William Lallemanddec25c32021-10-25 19:48:37 +0200267 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 +0100268 int i;
269 int foundhost = 0;
William Lallemand33b0d092021-08-13 16:05:53 +0200270
271 if (meth >= HTTP_METH_OTHER)
272 goto error;
273
274 meth_ist = http_known_methods[meth];
275
276 vsn = ist("HTTP/1.1");
277
278 htx = htx_from_buf(&hc->req.buf);
279 if (!htx)
280 goto error;
281 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_ist, url, vsn);
282 if (!sl) {
283 goto error;
284 }
285 sl->info.req.meth = meth;
286
William Lallemandf03b53c2021-11-24 15:38:17 +0100287 for (i = 0; hdrs && hdrs[i].n.len; i++) {
288 /* Don't check the value length because a header value may be empty */
289 if (isttest(hdrs[i].v) == 0)
290 continue;
291
292 if (isteqi(hdrs[i].n, ist("host")))
293 foundhost = 1;
294
295 if (!htx_add_header(htx, hdrs[i].n, hdrs[i].v))
296 goto error;
297 }
William Lallemand33b0d092021-08-13 16:05:53 +0200298
William Lallemandf03b53c2021-11-24 15:38:17 +0100299 if (!foundhost) {
300 /* Add Host Header from URL */
301 if (!htx_add_header(htx, ist("Host"), ist("h")))
William Lallemand79a34782021-09-20 16:19:15 +0200302 goto error;
William Lallemandf03b53c2021-11-24 15:38:17 +0100303 if (!http_update_host(htx, sl, url))
William Lallemand79a34782021-09-20 16:19:15 +0200304 goto error;
305 }
William Lallemand33b0d092021-08-13 16:05:53 +0200306
William Lallemandf03b53c2021-11-24 15:38:17 +0100307 if (!htx_add_endof(htx, HTX_BLK_EOH))
308 goto error;
309
William Lallemanddec25c32021-10-25 19:48:37 +0200310 if (isttest(payload)) {
311 /* add the payload if it can feat in the buffer, no need to set
312 * the Content-Length, the data will be sent chunked */
313 if (!htx_add_data_atonce(htx, payload))
314 goto error;
315 }
316
William Lallemand0da616e2021-10-28 15:34:26 +0200317 /* If req.payload was set, does not set the end of stream which *MUST*
318 * be set in the callback */
319 if (!hc->ops.req_payload)
320 htx->flags |= HTX_FL_EOM;
William Lallemand33b0d092021-08-13 16:05:53 +0200321
322 htx_to_buf(htx, &hc->req.buf);
323
324 return 0;
325error:
326 err_code |= ERR_ALERT | ERR_ABORT;
327 return err_code;
328}
329
330/*
331 * transfer the response to the destination buffer and wakeup the HTTP client
332 * applet so it could fill again its buffer.
333 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500334 * Return the number of bytes transferred.
William Lallemand33b0d092021-08-13 16:05:53 +0200335 */
336int httpclient_res_xfer(struct httpclient *hc, struct buffer *dst)
337{
338 int ret;
339
William Lallemand4d601842021-09-30 10:07:57 +0200340 ret = b_force_xfer(dst, &hc->res.buf, MIN(1024, b_data(&hc->res.buf)));
William Lallemand33b0d092021-08-13 16:05:53 +0200341 /* call the client once we consumed all data */
342 if (!b_data(&hc->res.buf) && hc->appctx)
343 appctx_wakeup(hc->appctx);
344 return ret;
345}
346
347/*
William Lallemand0da616e2021-10-28 15:34:26 +0200348 * Transfer raw HTTP payload from src, and insert it into HTX format in the
349 * httpclient.
350 *
351 * Must be used to transfer the request body.
352 * Then wakeup the httpclient so it can transfer it.
353 *
354 * <end> tries to add the ending data flag if it succeed to copy all data.
355 *
356 * Return the number of bytes copied from src.
357 */
358int httpclient_req_xfer(struct httpclient *hc, struct ist src, int end)
359{
360 int ret = 0;
361 struct htx *htx;
362
363 htx = htx_from_buf(&hc->req.buf);
364 if (!htx)
365 goto error;
366
367 if (hc->appctx)
368 appctx_wakeup(hc->appctx);
369
370 ret += htx_add_data(htx, src);
371
372
373 /* if we copied all the data and the end flag is set */
374 if ((istlen(src) == ret) && end) {
375 htx->flags |= HTX_FL_EOM;
376 }
377 htx_to_buf(htx, &hc->req.buf);
378
379error:
380
381 return ret;
382}
383
384
385/*
William Lallemand33b0d092021-08-13 16:05:53 +0200386 * Start the HTTP client
387 * Create the appctx, session, stream and wakeup the applet
388 *
389 * FIXME: It also fill the sockaddr with the IP address, but currently only IP
390 * in the URL are supported, it lacks a resolver.
391 *
392 * Return the <appctx> or NULL if it failed
393 */
394struct appctx *httpclient_start(struct httpclient *hc)
395{
396 struct applet *applet = &httpclient_applet;
397 struct appctx *appctx;
398 struct session *sess;
399 struct stream *s;
400 int len;
401 struct split_url out;
402
403 /* parse URI and fill sockaddr_storage */
404 /* FIXME: use a resolver */
William Lallemand614e6832021-09-26 18:12:43 +0200405 len = url2sa(istptr(hc->req.url), istlen(hc->req.url), &hc->dst, &out);
William Lallemand33b0d092021-08-13 16:05:53 +0200406 if (len == -1) {
William Lallemand614e6832021-09-26 18:12:43 +0200407 ha_alert("httpclient: cannot parse uri '%s'.\n", istptr(hc->req.url));
William Lallemand33b0d092021-08-13 16:05:53 +0200408 goto out;
409 }
410
411 /* The HTTP client will be created in the same thread as the caller,
412 * avoiding threading issues */
Willy Tarreaue6124462021-09-13 10:07:38 +0200413 appctx = appctx_new(applet);
William Lallemand33b0d092021-08-13 16:05:53 +0200414 if (!appctx)
415 goto out;
416
417 sess = session_new(httpclient_proxy, NULL, &appctx->obj_type);
418 if (!sess) {
419 ha_alert("httpclient: out of memory in %s:%d.\n", __FUNCTION__, __LINE__);
420 goto out_free_appctx;
421 }
422 if ((s = stream_new(sess, &appctx->obj_type, &BUF_NULL)) == NULL) {
423 ha_alert("httpclient: Failed to initialize stream %s:%d.\n", __FUNCTION__, __LINE__);
424 goto out_free_appctx;
425 }
426
Christopher Faulet16f16af2021-10-27 09:34:56 +0200427 if (!sockaddr_alloc(&s->si[1].dst, &hc->dst, sizeof(hc->dst))) {
William Lallemand33b0d092021-08-13 16:05:53 +0200428 ha_alert("httpclient: Failed to initialize stream in %s:%d.\n", __FUNCTION__, __LINE__);
429 goto out_free_stream;
430 }
431
432 /* choose the SSL server or not */
433 switch (out.scheme) {
434 case SCH_HTTP:
435 s->target = &httpclient_srv_raw->obj_type;
436 break;
437 case SCH_HTTPS:
William Lallemand957ab132021-08-24 18:33:28 +0200438#ifdef USE_OPENSSL
William Lallemand33b0d092021-08-13 16:05:53 +0200439 s->target = &httpclient_srv_ssl->obj_type;
William Lallemand957ab132021-08-24 18:33:28 +0200440#else
441 ha_alert("httpclient: OpenSSL is not available %s:%d.\n", __FUNCTION__, __LINE__);
442 goto out_free_stream;
443#endif
William Lallemand33b0d092021-08-13 16:05:53 +0200444 break;
445 }
446
447 s->flags |= SF_ASSIGNED|SF_ADDR_SET;
448 s->si[1].flags |= SI_FL_NOLINGER;
449 s->res.flags |= CF_READ_DONTWAIT;
450
451 /* applet is waiting for data */
452 si_cant_get(&s->si[0]);
453 appctx_wakeup(appctx);
454
455 task_wakeup(s->task, TASK_WOKEN_INIT);
456 hc->appctx = appctx;
William Lallemandb8b13702021-09-28 12:15:37 +0200457 hc->flags |= HTTPCLIENT_FS_STARTED;
William Lallemand33b0d092021-08-13 16:05:53 +0200458 appctx->ctx.httpclient.ptr = hc;
459 appctx->st0 = HTTPCLIENT_S_REQ;
460
461 return appctx;
462
463out_free_stream:
464 LIST_DELETE(&s->list);
465 pool_free(pool_head_stream, s);
466out_free_sess:
467 session_free(sess);
468out_free_appctx:
469 appctx_free(appctx);
470out:
471
472 return NULL;
473}
474
William Lallemandecb83e12021-09-28 11:00:43 +0200475/*
476 * This function tries to destroy the httpclient if it wasn't running.
477 * If it was running, stop the client and ask it to autodestroy itself.
478 *
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500479 * Once this function is used, all pointer sto the client must be removed
William Lallemandecb83e12021-09-28 11:00:43 +0200480 *
481 */
482void httpclient_stop_and_destroy(struct httpclient *hc)
483{
484
William Lallemandb8b13702021-09-28 12:15:37 +0200485 /* The httpclient was already stopped or never started, we can safely destroy it */
486 if (hc->flags & HTTPCLIENT_FS_ENDED || !(hc->flags & HTTPCLIENT_FS_STARTED)) {
William Lallemandecb83e12021-09-28 11:00:43 +0200487 httpclient_destroy(hc);
488 } else {
489 /* if the client wasn't stopped, ask for a stop and destroy */
490 hc->flags |= (HTTPCLIENT_FA_AUTOKILL | HTTPCLIENT_FA_STOP);
491 if (hc->appctx)
492 appctx_wakeup(hc->appctx);
493 }
494}
495
William Lallemand33b0d092021-08-13 16:05:53 +0200496/* Free the httpclient */
497void httpclient_destroy(struct httpclient *hc)
498{
William Lallemand03f5a1c2021-09-27 15:17:47 +0200499 struct http_hdr *hdrs;
500
501
William Lallemand33b0d092021-08-13 16:05:53 +0200502 if (!hc)
503 return;
William Lallemandecb83e12021-09-28 11:00:43 +0200504
William Lallemand2a879002021-10-05 15:50:45 +0200505 /* we should never destroy a client which was started but not stopped */
506 BUG_ON(httpclient_started(hc) && !httpclient_ended(hc));
William Lallemandecb83e12021-09-28 11:00:43 +0200507
William Lallemand03f5a1c2021-09-27 15:17:47 +0200508 /* request */
509 istfree(&hc->req.url);
William Lallemand33b0d092021-08-13 16:05:53 +0200510 b_free(&hc->req.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200511 /* response */
512 istfree(&hc->res.vsn);
513 istfree(&hc->res.reason);
514 hdrs = hc->res.hdrs;
515 while (hdrs && isttest(hdrs->n)) {
516 istfree(&hdrs->n);
517 istfree(&hdrs->v);
518 hdrs++;
519 }
520 ha_free(&hc->res.hdrs);
William Lallemand33b0d092021-08-13 16:05:53 +0200521 b_free(&hc->res.buf);
William Lallemand03f5a1c2021-09-27 15:17:47 +0200522
523
William Lallemand33b0d092021-08-13 16:05:53 +0200524 free(hc);
525
526 return;
527}
528
529/* Allocate an httpclient and its buffers
530 * Return NULL on failure */
531struct httpclient *httpclient_new(void *caller, enum http_meth_t meth, struct ist url)
532{
533 struct httpclient *hc;
534 struct buffer *b;
535
536 hc = calloc(1, sizeof(*hc));
537 if (!hc)
538 goto err;
539
540 b = b_alloc(&hc->req.buf);
541 if (!b)
542 goto err;
543 b = b_alloc(&hc->res.buf);
544 if (!b)
545 goto err;
546
547 hc->caller = caller;
William Lallemand67b77842021-11-10 16:57:25 +0100548 hc->req.url = istdup(url);
William Lallemand33b0d092021-08-13 16:05:53 +0200549 hc->req.meth = meth;
550
551 return hc;
552
553err:
554 httpclient_destroy(hc);
555 return NULL;
556}
557
558static void httpclient_applet_io_handler(struct appctx *appctx)
559{
560 struct httpclient *hc = appctx->ctx.httpclient.ptr;
561 struct stream_interface *si = appctx->owner;
562 struct stream *s = si_strm(si);
563 struct channel *req = &s->req;
564 struct channel *res = &s->res;
565 struct htx_blk *blk = NULL;
566 struct htx *htx;
William Lallemandb7020302021-08-20 11:24:13 +0200567 struct htx_sl *sl = NULL;
William Lallemand33b0d092021-08-13 16:05:53 +0200568 int32_t pos;
569 uint32_t hdr_num;
William Lallemand933fe392021-11-04 09:45:58 +0100570 int ret;
William Lallemand33b0d092021-08-13 16:05:53 +0200571
572
573 while (1) {
William Lallemandecb83e12021-09-28 11:00:43 +0200574
575 /* required to stop */
576 if (hc->flags & HTTPCLIENT_FA_STOP)
577 goto end;
578
William Lallemand33b0d092021-08-13 16:05:53 +0200579 switch(appctx->st0) {
580
581 case HTTPCLIENT_S_REQ:
William Lallemanddb8a1f32021-11-08 16:55:14 +0100582 /* we know that the buffer is empty here, since
583 * it's the first call, we can freely copy the
584 * request from the httpclient buffer */
William Lallemand933fe392021-11-04 09:45:58 +0100585 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
William Lallemanddb8a1f32021-11-08 16:55:14 +0100586 if (!ret)
587 goto more;
William Lallemand933fe392021-11-04 09:45:58 +0100588
William Lallemanddb8a1f32021-11-08 16:55:14 +0100589 htx = htx_from_buf(&req->buf);
William Lallemand933fe392021-11-04 09:45:58 +0100590 if (!htx)
591 goto more;
592
William Lallemanddb8a1f32021-11-08 16:55:14 +0100593 channel_add_input(req, htx->data);
594
William Lallemand933fe392021-11-04 09:45:58 +0100595 if (htx->flags & HTX_FL_EOM) /* check if a body need to be added */
596 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
597 else
598 appctx->st0 = HTTPCLIENT_S_REQ_BODY;
599
William Lallemand33b0d092021-08-13 16:05:53 +0200600 goto more; /* we need to leave the IO handler once we wrote the request */
601 break;
William Lallemand0da616e2021-10-28 15:34:26 +0200602 case HTTPCLIENT_S_REQ_BODY:
603 /* call the payload callback */
604 {
605 if (hc->ops.req_payload) {
William Lallemand0da616e2021-10-28 15:34:26 +0200606
William Lallemand0da616e2021-10-28 15:34:26 +0200607 /* call the request callback */
608 hc->ops.req_payload(hc);
William Lallemanddb8a1f32021-11-08 16:55:14 +0100609 /* check if the request buffer is empty */
610
611 htx = htx_from_buf(&req->buf);
612 if (!htx_is_empty(htx))
613 goto more;
614 /* Here htx_to_buf() will set buffer data to 0 because
615 * the HTX is empty, and allow us to do an xfer.
616 */
617 htx_to_buf(htx, &req->buf);
618
619 ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf));
620 if (!ret)
621 goto more;
622 htx = htx_from_buf(&req->buf);
623 if (!htx)
624 goto more;
625
626 channel_add_input(req, htx->data);
William Lallemand0da616e2021-10-28 15:34:26 +0200627 }
628
William Lallemanddb8a1f32021-11-08 16:55:14 +0100629 htx = htx_from_buf(&req->buf);
William Lallemand0da616e2021-10-28 15:34:26 +0200630 if (!htx)
631 goto more;
632
633 /* if the request contains the HTX_FL_EOM, we finished the request part. */
634 if (htx->flags & HTX_FL_EOM)
635 appctx->st0 = HTTPCLIENT_S_RES_STLINE;
636
637 goto more; /* we need to leave the IO handler once we wrote the request */
638 }
639 break;
William Lallemand33b0d092021-08-13 16:05:53 +0200640
641 case HTTPCLIENT_S_RES_STLINE:
642 /* copy the start line in the hc structure,then remove the htx block */
643 if (!b_data(&res->buf))
644 goto more;
645 htx = htxbuf(&res->buf);
646 if (!htx)
647 goto more;
648 blk = htx_get_first_blk(htx);
649 if (blk && (htx_get_blk_type(blk) == HTX_BLK_RES_SL))
650 sl = htx_get_blk_ptr(htx, blk);
651 if (!sl || (!(sl->flags & HTX_SL_F_IS_RESP)))
652 goto more;
653
654 /* copy the status line in the httpclient */
655 hc->res.status = sl->info.res.status;
656 hc->res.vsn = istdup(htx_sl_res_vsn(sl));
657 hc->res.reason = istdup(htx_sl_res_reason(sl));
658 co_htx_remove_blk(res, htx, blk);
659 /* caller callback */
660 if (hc->ops.res_stline)
661 hc->ops.res_stline(hc);
662
663 /* if there is no HTX data anymore and the EOM flag is
664 * set, leave (no body) */
665 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM)
666 appctx->st0 = HTTPCLIENT_S_RES_END;
667 else
668 appctx->st0 = HTTPCLIENT_S_RES_HDR;
669 break;
670
671 case HTTPCLIENT_S_RES_HDR:
672 /* first copy the headers in a local hdrs
673 * structure, once we the total numbers of the
674 * header we allocate the right size and copy
675 * them. The htx block of the headers are
676 * removed each time one is read */
677 {
678 struct http_hdr hdrs[global.tune.max_http_hdr];
679
680 if (!b_data(&res->buf))
681 goto more;
682 htx = htxbuf(&res->buf);
683 if (!htx)
684 goto more;
685
686 hdr_num = 0;
687
688 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
689 struct htx_blk *blk = htx_get_blk(htx, pos);
690 enum htx_blk_type type = htx_get_blk_type(blk);
691
692 if (type == HTX_BLK_EOH) {
693 hdrs[hdr_num].n = IST_NULL;
694 hdrs[hdr_num].v = IST_NULL;
695 co_htx_remove_blk(res, htx, blk);
696 break;
697 }
698
699 if (type != HTX_BLK_HDR)
700 continue;
701
702 hdrs[hdr_num].n = istdup(htx_get_blk_name(htx, blk));
703 hdrs[hdr_num].v = istdup(htx_get_blk_value(htx, blk));
704 if (!isttest(hdrs[hdr_num].v) || !isttest(hdrs[hdr_num].n))
705 goto end;
706 co_htx_remove_blk(res, htx, blk);
707 hdr_num++;
708 }
709
William Lallemand0d6f7792021-08-20 11:59:49 +0200710 if (hdr_num) {
711 /* alloc and copy the headers in the httpclient struct */
712 hc->res.hdrs = calloc((hdr_num + 1), sizeof(*hc->res.hdrs));
713 if (!hc->res.hdrs)
714 goto end;
715 memcpy(hc->res.hdrs, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
William Lallemand33b0d092021-08-13 16:05:53 +0200716
William Lallemand0d6f7792021-08-20 11:59:49 +0200717 /* caller callback */
718 if (hc->ops.res_headers)
719 hc->ops.res_headers(hc);
720 }
William Lallemand33b0d092021-08-13 16:05:53 +0200721
722 /* if there is no HTX data anymore and the EOM flag is
723 * set, leave (no body) */
William Lallemand1123dde2021-09-21 10:58:10 +0200724 if (htx_is_empty(htx) && htx->flags & HTX_FL_EOM) {
William Lallemand33b0d092021-08-13 16:05:53 +0200725 appctx->st0 = HTTPCLIENT_S_RES_END;
William Lallemand1123dde2021-09-21 10:58:10 +0200726 } else {
William Lallemand33b0d092021-08-13 16:05:53 +0200727 appctx->st0 = HTTPCLIENT_S_RES_BODY;
William Lallemand1123dde2021-09-21 10:58:10 +0200728 }
William Lallemand33b0d092021-08-13 16:05:53 +0200729 }
730 break;
731
732 case HTTPCLIENT_S_RES_BODY:
733 /*
734 * The IO handler removes the htx blocks in the response buffer and
735 * push them in the hc->res.buf buffer in a raw format.
736 */
737 htx = htxbuf(&res->buf);
738 if (!htx || htx_is_empty(htx))
739 goto more;
740
741 if (b_full(&hc->res.buf))
742 goto process_data;
743
744 /* decapsule the htx data to raw data */
745 for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
746 enum htx_blk_type type;
747
748 blk = htx_get_blk(htx, pos);
749 type = htx_get_blk_type(blk);
750 if (type == HTX_BLK_DATA) {
751 struct ist v = htx_get_blk_value(htx, blk);
752
753 if ((b_room(&hc->res.buf) < v.len) )
754 goto process_data;
755
756 __b_putblk(&hc->res.buf, v.ptr, v.len);
757 co_htx_remove_blk(res, htx, blk);
758 /* the data must be processed by the caller in the receive phase */
759 if (hc->ops.res_payload)
760 hc->ops.res_payload(hc);
761 } else {
762 /* remove any block which is not a data block */
763 co_htx_remove_blk(res, htx, blk);
764 }
765 }
766 /* if not finished, should be called again */
767 if (!(htx->flags & HTX_FL_EOM))
768 goto more;
769
770 /* end of message, we should quit */
771 appctx->st0 = HTTPCLIENT_S_RES_END;
772 break;
773
774 case HTTPCLIENT_S_RES_END:
775 goto end;
776 break;
777 }
778 }
779
780process_data:
781
782 si_rx_chan_rdy(si);
783
784 return;
785more:
786 /* There was not enough data in the response channel */
787
788 si_rx_room_blk(si);
789
790 if (appctx->st0 == HTTPCLIENT_S_RES_END)
791 goto end;
792
793 /* The state machine tries to handle as much data as possible, if there
794 * isn't any data to handle and a shutdown is detected, let's stop
795 * everything */
796 if ((req->flags & (CF_SHUTR|CF_SHUTR_NOW)) ||
797 (res->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
798 goto end;
799 }
800 return;
801
802end:
803 if (hc->ops.res_end)
804 hc->ops.res_end(hc);
805 si_shutw(si);
806 si_shutr(si);
807 return;
808}
809
810static void httpclient_applet_release(struct appctx *appctx)
811{
812 struct httpclient *hc = appctx->ctx.httpclient.ptr;
813
William Lallemand1123dde2021-09-21 10:58:10 +0200814 /* mark the httpclient as ended */
William Lallemandecb83e12021-09-28 11:00:43 +0200815 hc->flags |= HTTPCLIENT_FS_ENDED;
William Lallemand33b0d092021-08-13 16:05:53 +0200816 /* the applet is leaving, remove the ptr so we don't try to call it
817 * again from the caller */
818 hc->appctx = NULL;
819
William Lallemandecb83e12021-09-28 11:00:43 +0200820
821 /* destroy the httpclient when set to autotokill */
822 if (hc->flags & HTTPCLIENT_FA_AUTOKILL) {
823 httpclient_destroy(hc);
824 }
825
William Lallemand33b0d092021-08-13 16:05:53 +0200826 return;
827}
828
829/* HTTP client applet */
830static struct applet httpclient_applet = {
831 .obj_type = OBJ_TYPE_APPLET,
832 .name = "<HTTPCLIENT>",
833 .fct = httpclient_applet_io_handler,
834 .release = httpclient_applet_release,
835};
836
William Lallemand83614a92021-08-13 14:47:57 +0200837/*
838 * Initialize the proxy for the HTTP client with 2 servers, one for raw HTTP,
839 * the other for HTTPS.
840 */
841
842static int httpclient_init()
843{
844 int err_code = 0;
845 char *errmsg = NULL;
846
847 httpclient_proxy = alloc_new_proxy("<HTTPCLIENT>", PR_CAP_LISTEN|PR_CAP_INT, &errmsg);
848 if (!httpclient_proxy) {
849 err_code |= ERR_ALERT | ERR_FATAL;
850 goto err;
851 }
852
Willy Tarreau0e72e402021-08-20 10:23:12 +0200853 proxy_preset_defaults(httpclient_proxy);
854
William Lallemand83614a92021-08-13 14:47:57 +0200855 httpclient_proxy->options2 |= PR_O2_INDEPSTR;
856 httpclient_proxy->mode = PR_MODE_HTTP;
857 httpclient_proxy->maxconn = 0;
858 httpclient_proxy->accept = NULL;
859 httpclient_proxy->timeout.client = TICK_ETERNITY;
860 /* The HTTP Client use the "option httplog" with the global log server */
861 httpclient_proxy->conf.logformat_string = default_http_log_format;
862 httpclient_proxy->http_needed = 1;
863
864 /* clear HTTP server */
865 httpclient_srv_raw = new_server(httpclient_proxy);
866 if (!httpclient_srv_raw) {
867 err_code |= ERR_ALERT | ERR_FATAL;
868 memprintf(&errmsg, "out of memory.");
869 goto err;
870 }
871
872 httpclient_srv_raw->iweight = 0;
873 httpclient_srv_raw->uweight = 0;
874 httpclient_srv_raw->xprt = xprt_get(XPRT_RAW);
875 httpclient_srv_raw->id = strdup("<HTTPCLIENT>");
876 if (!httpclient_srv_raw->id)
877 goto err;
878
William Lallemand957ab132021-08-24 18:33:28 +0200879#ifdef USE_OPENSSL
William Lallemand83614a92021-08-13 14:47:57 +0200880 /* SSL HTTP server */
881 httpclient_srv_ssl = new_server(httpclient_proxy);
882 if (!httpclient_srv_ssl) {
883 memprintf(&errmsg, "out of memory.");
884 err_code |= ERR_ALERT | ERR_FATAL;
885 goto err;
886 }
887 httpclient_srv_ssl->iweight = 0;
888 httpclient_srv_ssl->uweight = 0;
889 httpclient_srv_ssl->xprt = xprt_get(XPRT_SSL);
890 httpclient_srv_ssl->use_ssl = 1;
William Lallemand211c9672021-08-24 17:18:13 +0200891 httpclient_srv_ssl->id = strdup("<HTTPSCLIENT>");
William Lallemand83614a92021-08-13 14:47:57 +0200892 if (!httpclient_srv_ssl->id)
893 goto err;
894
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200895 httpclient_srv_ssl->ssl_ctx.verify = SSL_SOCK_VERIFY_NONE;
William Lallemand957ab132021-08-24 18:33:28 +0200896#endif
William Lallemandcfcbe9e2021-08-24 17:15:58 +0200897
Ilya Shipitsinbd6b4be2021-10-15 16:18:21 +0500898 /* add the proxy in the proxy list only if everything is successful */
William Lallemand83614a92021-08-13 14:47:57 +0200899 httpclient_proxy->next = proxies_list;
900 proxies_list = httpclient_proxy;
901
William Lallemand211c9672021-08-24 17:18:13 +0200902 /* link the 2 servers in the proxy */
903 httpclient_srv_raw->next = httpclient_proxy->srv;
William Lallemand957ab132021-08-24 18:33:28 +0200904 httpclient_proxy->srv = httpclient_srv_raw;
905
906#ifdef USE_OPENSSL
907 httpclient_srv_ssl->next = httpclient_proxy->srv;
William Lallemand211c9672021-08-24 17:18:13 +0200908 httpclient_proxy->srv = httpclient_srv_ssl;
William Lallemand957ab132021-08-24 18:33:28 +0200909#endif
910
William Lallemand211c9672021-08-24 17:18:13 +0200911
William Lallemand83614a92021-08-13 14:47:57 +0200912 return 0;
913
914err:
915 ha_alert("httpclient: cannot initialize.\n");
916 free(errmsg);
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200917 srv_drop(httpclient_srv_raw);
William Lallemand957ab132021-08-24 18:33:28 +0200918#ifdef USE_OPENSSL
Amaury Denoyellebc2ebfa2021-08-25 15:34:53 +0200919 srv_drop(httpclient_srv_ssl);
William Lallemand957ab132021-08-24 18:33:28 +0200920#endif
William Lallemand83614a92021-08-13 14:47:57 +0200921 free_proxy(httpclient_proxy);
922 return err_code;
923}
924
William Lallemand83614a92021-08-13 14:47:57 +0200925static int httpclient_cfg_postparser()
926{
927 struct logsrv *logsrv;
928 struct proxy *curproxy = httpclient_proxy;
929
930 /* copy logs from "global" log list */
931 list_for_each_entry(logsrv, &global.logsrvs, list) {
932 struct logsrv *node = malloc(sizeof(*node));
933
934 if (!node) {
935 ha_alert("httpclient: cannot allocate memory.\n");
936 goto err;
937 }
938
939 memcpy(node, logsrv, sizeof(*node));
940 LIST_INIT(&node->list);
941 LIST_APPEND(&curproxy->logsrvs, &node->list);
942 }
943 if (curproxy->conf.logformat_string) {
944 char *err = NULL;
945
946 curproxy->conf.args.ctx = ARGC_LOG;
947 if (!parse_logformat_string(curproxy->conf.logformat_string, curproxy, &curproxy->logformat,
948 LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
949 SMP_VAL_FE_LOG_END, &err)) {
950 ha_alert("httpclient: failed to parse log-format : %s.\n", err);
951 free(err);
952 goto err;
953 }
954 curproxy->conf.args.file = NULL;
955 curproxy->conf.args.line = 0;
956 }
957 return 0;
958err:
959 return 1;
960}
961
William Lallemand83614a92021-08-13 14:47:57 +0200962/* initialize the proxy and servers for the HTTP client */
963
964INITCALL0(STG_REGISTER, httpclient_init);
965REGISTER_CONFIG_POSTPARSER("httpclient", httpclient_cfg_postparser);