mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-05 14:47:07 +02:00
This patch split the httpclient code to prevent confusion between the httpclient CLI command and the actual httpclient API. Indeed there was a confusion between the flag used internally by the CLI command, and the actual httpclient API. hc_cli_* functions as well as HC_C_F_* defines were moved to httpclient_cli.c.
223 lines
5.1 KiB
C
223 lines
5.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/* "httpclient" CLI command */
|
|
|
|
|
|
#include <haproxy/cli.h>
|
|
#include <haproxy/cfgparse.h>
|
|
#include <haproxy/global.h>
|
|
#include <haproxy/istbuf.h>
|
|
#include <haproxy/h1_htx.h>
|
|
#include <haproxy/http_client.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
#include <string.h>
|
|
|
|
/* --- This part of the file implement an HTTP client over the CLI ---
|
|
* The functions will be starting by "hc_cli" for "httpclient cli"
|
|
*/
|
|
|
|
#define HC_F_RES_STLINE 0x01
|
|
#define HC_F_RES_HDR 0x02
|
|
#define HC_F_RES_BODY 0x04
|
|
#define HC_F_RES_END 0x08
|
|
|
|
/* the CLI context for the httpclient command */
|
|
struct hcli_svc_ctx {
|
|
struct httpclient *hc; /* the httpclient instance */
|
|
uint flags; /* flags from HC_CLI_F_* above */
|
|
};
|
|
|
|
/* These are the callback used by the HTTP Client when it needs to notify new
|
|
* data, we only sets a flag in the IO handler via the svcctx.
|
|
*/
|
|
void hc_cli_res_stline_cb(struct httpclient *hc)
|
|
{
|
|
struct appctx *appctx = hc->caller;
|
|
struct hcli_svc_ctx *ctx;
|
|
|
|
if (!appctx)
|
|
return;
|
|
|
|
ctx = appctx->svcctx;
|
|
ctx->flags |= HC_F_RES_STLINE;
|
|
appctx_wakeup(appctx);
|
|
}
|
|
|
|
void hc_cli_res_headers_cb(struct httpclient *hc)
|
|
{
|
|
struct appctx *appctx = hc->caller;
|
|
struct hcli_svc_ctx *ctx;
|
|
|
|
if (!appctx)
|
|
return;
|
|
|
|
ctx = appctx->svcctx;
|
|
ctx->flags |= HC_F_RES_HDR;
|
|
appctx_wakeup(appctx);
|
|
}
|
|
|
|
void hc_cli_res_body_cb(struct httpclient *hc)
|
|
{
|
|
struct appctx *appctx = hc->caller;
|
|
struct hcli_svc_ctx *ctx;
|
|
|
|
if (!appctx)
|
|
return;
|
|
|
|
ctx = appctx->svcctx;
|
|
ctx->flags |= HC_F_RES_BODY;
|
|
appctx_wakeup(appctx);
|
|
}
|
|
|
|
void hc_cli_res_end_cb(struct httpclient *hc)
|
|
{
|
|
struct appctx *appctx = hc->caller;
|
|
struct hcli_svc_ctx *ctx;
|
|
|
|
if (!appctx)
|
|
return;
|
|
|
|
ctx = appctx->svcctx;
|
|
ctx->flags |= HC_F_RES_END;
|
|
appctx_wakeup(appctx);
|
|
}
|
|
|
|
/*
|
|
* Parse an httpclient keyword on the cli:
|
|
* httpclient <ID> <method> <URI>
|
|
*/
|
|
static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
|
|
{
|
|
struct hcli_svc_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
|
|
struct httpclient *hc;
|
|
char *err = NULL;
|
|
enum http_meth_t meth;
|
|
char *meth_str;
|
|
struct ist uri;
|
|
struct ist body = IST_NULL;
|
|
|
|
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
|
|
return 1;
|
|
|
|
if (!*args[1] || !*args[2]) {
|
|
memprintf(&err, ": not enough parameters");
|
|
goto err;
|
|
}
|
|
|
|
meth_str = args[1];
|
|
uri = ist(args[2]);
|
|
|
|
if (payload)
|
|
body = ist(payload);
|
|
|
|
meth = find_http_meth(meth_str, strlen(meth_str));
|
|
|
|
hc = httpclient_new(appctx, meth, uri);
|
|
if (!hc) {
|
|
goto err;
|
|
}
|
|
|
|
/* update the httpclient callbacks */
|
|
hc->ops.res_stline = hc_cli_res_stline_cb;
|
|
hc->ops.res_headers = hc_cli_res_headers_cb;
|
|
hc->ops.res_payload = hc_cli_res_body_cb;
|
|
hc->ops.res_end = hc_cli_res_end_cb;
|
|
|
|
ctx->hc = hc; /* store the httpclient ptr in the applet */
|
|
ctx->flags = 0;
|
|
|
|
if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, body) != ERR_NONE)
|
|
goto err;
|
|
|
|
|
|
if (!httpclient_start(hc))
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : "");
|
|
return cli_err(appctx, err);
|
|
}
|
|
|
|
/* This function dumps the content of the httpclient receive buffer
|
|
* on the CLI output
|
|
*
|
|
* Return 1 when the processing is finished
|
|
* return 0 if it needs to be called again
|
|
*/
|
|
static int hc_cli_io_handler(struct appctx *appctx)
|
|
{
|
|
struct hcli_svc_ctx *ctx = appctx->svcctx;
|
|
struct httpclient *hc = ctx->hc;
|
|
struct http_hdr *hdrs, *hdr;
|
|
|
|
if (ctx->flags & HC_F_RES_STLINE) {
|
|
chunk_printf(&trash, "%.*s %d %.*s\n", (unsigned int)istlen(hc->res.vsn), istptr(hc->res.vsn),
|
|
hc->res.status, (unsigned int)istlen(hc->res.reason), istptr(hc->res.reason));
|
|
if (applet_putchk(appctx, &trash) == -1)
|
|
goto more;
|
|
ctx->flags &= ~HC_F_RES_STLINE;
|
|
}
|
|
|
|
if (ctx->flags & HC_F_RES_HDR) {
|
|
chunk_reset(&trash);
|
|
hdrs = hc->res.hdrs;
|
|
for (hdr = hdrs; isttest(hdr->v); hdr++) {
|
|
if (!h1_format_htx_hdr(hdr->n, hdr->v, &trash))
|
|
goto too_many_hdrs;
|
|
}
|
|
if (!chunk_memcat(&trash, "\r\n", 2))
|
|
goto too_many_hdrs;
|
|
if (applet_putchk(appctx, &trash) == -1)
|
|
goto more;
|
|
ctx->flags &= ~HC_F_RES_HDR;
|
|
}
|
|
|
|
if (ctx->flags & HC_F_RES_BODY) {
|
|
httpclient_res_xfer(hc, &appctx->outbuf);
|
|
|
|
/* remove the flag if the buffer was emptied */
|
|
if (httpclient_data(hc))
|
|
goto more;
|
|
ctx->flags &= ~HC_F_RES_BODY;
|
|
}
|
|
|
|
/* we must close only if F_END is the last flag */
|
|
if (ctx->flags == HC_F_RES_END) {
|
|
ctx->flags &= ~HC_F_RES_END;
|
|
goto end;
|
|
}
|
|
|
|
more:
|
|
if (!ctx->flags)
|
|
applet_have_no_more_data(appctx);
|
|
return 0;
|
|
end:
|
|
return 1;
|
|
|
|
too_many_hdrs:
|
|
return cli_err(appctx, "Too many headers.\n");
|
|
}
|
|
|
|
static void hc_cli_release(struct appctx *appctx)
|
|
{
|
|
struct hcli_svc_ctx *ctx = appctx->svcctx;
|
|
struct httpclient *hc = ctx->hc;
|
|
|
|
/* Everything possible was printed on the CLI, we can destroy the client */
|
|
httpclient_stop_and_destroy(hc);
|
|
|
|
return;
|
|
}
|
|
|
|
/* register cli keywords */
|
|
static struct cli_kw_list cli_kws = {{ },{
|
|
{ { "httpclient", NULL }, "httpclient <method> <URI> : launch an HTTP request", hc_cli_parse, hc_cli_io_handler, hc_cli_release, NULL, ACCESS_EXPERT},
|
|
{ { NULL }, NULL, NULL, NULL }
|
|
}};
|
|
|
|
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
|
|
|