aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-10-06 23:01:42 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-10-08 01:57:42 +0200
commite77bd93693a74ce872d4c13fb45537c34518d84f (patch)
tree8fb265064ebf9debed2e797cbdef42187524d158
parentdb9cb051c4ee05c07ab32dfed5bae8b7dc0916bd (diff)
Implement HTTP chunk encoding
A new function pointer, namely chunk, has been added to struct http_response so that library users can generate their message bodies dynamically.
-rw-r--r--doc/man7/libweb_http.763
-rw-r--r--examples/async/main.c6
-rw-r--r--handler.c10
-rw-r--r--http.c138
-rw-r--r--include/libweb/http.h7
5 files changed, 198 insertions, 26 deletions
diff --git a/doc/man7/libweb_http.7 b/doc/man7/libweb_http.7
index 63a68da..5c01549 100644
--- a/doc/man7/libweb_http.7
+++ b/doc/man7/libweb_http.7
@@ -1,4 +1,4 @@
-.TH LIBWEB_HTTP 7 2024-08-22 0.4.0 "libweb Library Reference"
+.TH LIBWEB_HTTP 7 2025-10-07 0.5.0 "libweb Library Reference"
.SH NAME
libweb_http \- libweb HTTP connection handling and utilities
@@ -562,12 +562,13 @@ struct http_response
unsigned long long \fIn\fP;
size_t \fIn_headers\fP;
void (*\fIfree\fP)(void *);
- void *\fIstep_args\fP;
+ void *\fIargs\fP;
+ int (*\fIchunk\fP)(void *\fIbuf\fP, size_t \fIn\fP, bool *\fIdone\fP, void *\fIuser\fP, void *\fIargs\fP);
union http_step
{
- int (*\fIlength\fP)(unsigned long long \fIlen\fP, const struct http_cookie *\fIc\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIstep_args\fP);
- int (*\fIpayload\fP)(const struct http_payload *\fIp\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIstep_args\fP);
+ int (*\fIlength\fP)(unsigned long long \fIlen\fP, const struct http_cookie *\fIc\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIargs\fP);
+ int (*\fIpayload\fP)(const struct http_payload *\fIp\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIargs\fP);
} \fIstep\fP;
};
.EE
@@ -604,10 +605,11 @@ defined by
.I libweb
shall select
.I ro
-as the output payload if both
-.I f
-and
+as the output payload if
+.IR f ,
.I free
+and
+.I chunk
are null pointers, and
.I n
is non-zero.
@@ -619,9 +621,11 @@ is an opaque pointer to a buffer in memory, whose length is defined by
.I libweb
shall select
.I rw
-as the output payload if both
+as the output payload if
+.I chunk
+and
.I f
-is a null pointer and
+are null pointers,
.I free
is a valid pointer to a function that frees the memory used by
.IR rw ,
@@ -639,6 +643,7 @@ client, whose length is defined by
shall select
.I f
as the output payload if
+.IR chunk ,
.IR ro ,
.I rw
and
@@ -676,6 +681,36 @@ is a valid pointer. Otherwise,
.I free
must be a null pointer.
+.I args
+is an opaque pointer to user-defined data, as consumed by
+.I chunk
+and
+.IR step .
+
+.I chunk
+allows library users to generate the response body dynamically,
+therefore relying on the
+.I Transfer-Encoding: chunked
+HTTP header.
+When used,
+.I chunk
+must point to a function that reads up to
+.I n
+bytes into a buffer provided by
+.IR libweb ,
+namely
+.IR buf .
+The function must return how many bytes were written into
+.IR buf ,
+and must be between zero and
+.IR n .
+In case of error, a negative integer must be returned.
+When no more chunks are to be sent, users must assign the flag
+pointed to by
+.I done
+to
+.IR true .
+
.I step
allows implementations to deal with responses asynchronously
i.e., without blocking other clients.
@@ -688,6 +723,14 @@ must be assigned to a function that can generate it later.
.I libweb
shall then call this function immediately later,
without blocking other clients.
+Do not confuse
+.I step
+with
+.IR chunk ,
+where
+.I chunk
+is meant for dynamically generated message bodies only, where the response
+HTTP status and headers have already been defined.
Assigning
.I step.length
@@ -857,7 +900,7 @@ applications no longer need to decode payload data by themselves.
.BR http_decode_url (3).
.SH COPYRIGHT
-Copyright (C) 2023-2024 libweb contributors
+Copyright (C) 2023-2025 libweb contributors
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
diff --git a/examples/async/main.c b/examples/async/main.c
index dbf69a8..fbd766e 100644
--- a/examples/async/main.c
+++ b/examples/async/main.c
@@ -86,9 +86,9 @@ end:
}
static int step(const struct http_payload *const pl,
- struct http_response *const r, void *const user, void *step_args)
+ struct http_response *const r, void *const user, void *args)
{
- unsigned *const cnt = step_args;
+ unsigned *const cnt = args;
const unsigned max = 10;
fprintf(stderr, "%s: step %u\n", __func__, *cnt);
@@ -113,7 +113,7 @@ static int hello(const struct http_payload *const pl,
*r = (const struct http_response)
{
.step.payload = step,
- .step_args = cnt
+ .args = cnt
};
*cnt = 0;
diff --git a/handler.c b/handler.c
index 63ef726..f30cfc3 100644
--- a/handler.c
+++ b/handler.c
@@ -29,7 +29,7 @@ struct handler
struct server_client *c;
struct http_ctx *http;
union http_step step;
- void *step_args;
+ void *args;
struct client *next;
} *clients;
@@ -66,14 +66,14 @@ static int on_payload(const struct http_payload *const p,
int ret;
if (s->payload)
- ret = s->payload(p, r, e->user, c->step_args);
+ ret = s->payload(p, r, e->user, c->args);
else
ret = e->f(p, r, e->user);
if (!ret)
{
s->payload = r->step.payload;
- c->step_args = r->step_args;
+ c->args = r->args;
}
return ret;
@@ -104,14 +104,14 @@ static int on_length(const unsigned long long len,
int ret;
if (s->length)
- ret = s->length(len, c, r, h->cfg.user, cl->step_args);
+ ret = s->length(len, c, r, h->cfg.user, cl->args);
else
ret = h->cfg.length(len, c, r, h->cfg.user);
if (!ret)
{
s->length = r->step.length;
- cl->step_args = r->step_args;
+ cl->args = r->args;
}
return ret;
diff --git a/http.c b/http.c
index c173c3d..21966bc 100644
--- a/http.c
+++ b/http.c
@@ -109,13 +109,16 @@ struct http_ctx
struct write_ctx
{
- bool pending, close;
+ bool pending, close, chunk_done;
enum state state;
struct http_response r;
struct http_get_range gr;
off_t n;
struct dynstr d;
enum http_op op;
+ void *chunk;
+ int (*next)(struct http_ctx *, bool *close);
+ size_t written, chunklen;
} wctx;
/* From RFC9112, section 3 (Request line):
@@ -125,6 +128,8 @@ struct http_ctx
struct http_cfg cfg;
};
+static int read_chunk(struct http_ctx *, bool *);
+
static int adjust_partial(struct write_ctx *const w)
{
int ret = -1;
@@ -226,6 +231,16 @@ static int send_length(struct write_ctx *const w)
return res;
}
}
+ else if (r->chunk)
+ {
+ if (http_response_add_header(r, "Transfer-Encoding", "chunked"))
+ {
+ fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
+ return -1;
+ }
+
+ w->next = read_chunk;
+ }
else
{
char len[sizeof "18446744073709551615"];
@@ -243,7 +258,7 @@ static int send_length(struct write_ctx *const w)
}
}
- if (http_response_add_header(r, "Accept-Ranges", "bytes"))
+ if (!r->chunk && http_response_add_header(r, "Accept-Ranges", "bytes"))
{
fprintf(stderr, "%s: http_response_add_header length failed\n",
__func__);
@@ -782,6 +797,7 @@ static int write_ctx_free(struct write_ctx *const w)
if (r->f && (ret = fclose(r->f)))
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
+ free(w->chunk);
dynstr_free(&w->d);
free_response_headers(&w->r);
*w = (const struct write_ctx){0};
@@ -808,7 +824,7 @@ static int write_header_cr_line(struct http_ctx *const h, bool *const close)
dynstr_free(d);
- if (w->r.n && must_write_body(w))
+ if (w->r.chunk || (w->r.n && must_write_body(w)))
{
w->state = BODY_LINE;
w->n = 0;
@@ -825,6 +841,116 @@ static int write_header_cr_line(struct http_ctx *const h, bool *const close)
return 0;
}
+static int write_chunkend(struct http_ctx *const h, bool *const close)
+{
+ static const char end[] = "\r\n";
+ struct write_ctx *const w = &h->wctx;
+ const void *const src = end + w->written;
+ const int r = h->cfg.write(src, strlen(end) - w->written, h->cfg.user);
+
+ if (r < 0)
+ return rw_error(r, close);
+ else if ((w->written += r) >= strlen(end))
+ {
+ if (w->chunk_done)
+ {
+ const bool close_pending = w->close;
+
+ if (write_ctx_free(w))
+ {
+ fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
+ return -1;
+ }
+ else if (close_pending)
+ *close = true;
+ }
+ else
+ w->next = read_chunk;
+ }
+
+ return 0;
+}
+
+static int write_chunkdata(struct http_ctx *const h, bool *const close)
+{
+ struct write_ctx *const w = &h->wctx;
+ const void *const src = (const char *)w->chunk + w->written;
+ const int r = h->cfg.write(src, w->chunklen - w->written, h->cfg.user);
+
+ if (r < 0)
+ return rw_error(r, close);
+ else if ((w->written += r) >= w->chunklen)
+ {
+ w->written = 0;
+ w->next = write_chunkend;
+ }
+
+ return 0;
+}
+
+static int write_chunklen(struct http_ctx *const h, bool *const close)
+{
+ struct write_ctx *const w = &h->wctx;
+ struct dynstr *const d = &w->d;
+ const void *const src = d->str + w->written;
+ const int r = h->cfg.write(src, d->len - w->written, h->cfg.user);
+
+ if (r < 0)
+ return rw_error(r, close);
+ else if ((w->written += r) >= d->len)
+ {
+ dynstr_free(d);
+ w->written = 0;
+ w->next = write_chunkdata;
+ }
+
+ return 0;
+}
+
+static int read_chunk(struct http_ctx *const h, bool *const close)
+{
+ bool done = false;
+ struct write_ctx *const w = &h->wctx;
+ struct dynstr *const d = &w->d;
+ const struct http_response *const r = &w->r;
+ const int n = r->chunk(w->chunk, BUFSIZ, &done, h->cfg.user, r->args);
+
+ if (n < 0)
+ {
+ fprintf(stderr, "%s: user callback failed\n", __func__);
+ return -1;
+ }
+ else if (!n && !done)
+ return 0;
+
+ dynstr_init(d);
+
+ if (dynstr_append(d, "%X\r\n", n))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ return -1;
+ }
+
+ w->written = 0;
+ w->chunklen = n;
+ w->chunk_done = done;
+ w->next = write_chunklen;
+ return 0;
+}
+
+static int write_chunk(struct http_ctx *const h, bool *const close)
+{
+ struct write_ctx *const w = &h->wctx;
+
+ if (!w->chunk && !(w->chunk = malloc(BUFSIZ)))
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ return w->next(h, close);
+}
+
static int write_body_mem(struct http_ctx *const h, bool *const close)
{
struct write_ctx *const w = &h->wctx;
@@ -892,12 +1018,14 @@ static int write_body_line(struct http_ctx *const h, bool *const close)
{
const struct http_response *const r = &h->wctx.r;
- if (r->buf.ro)
+ if (r->chunk)
+ return write_chunk(h, close);
+ else if (r->buf.ro)
return write_body_mem(h, close);
else if (r->f)
return write_body_file(h, close);
- fprintf(stderr, "%s: expected either buffer or file path\n", __func__);
+ fprintf(stderr, "%s: expected chunk, buffer or file path\n", __func__);
return -1;
}
diff --git a/include/libweb/http.h b/include/libweb/http.h
index 87edf8a..dab19df 100644
--- a/include/libweb/http.h
+++ b/include/libweb/http.h
@@ -109,14 +109,15 @@ struct http_response
unsigned long long n;
size_t n_headers;
void (*free)(void *);
- void *step_args;
+ void *args;
+ int (*chunk)(void *buf, size_t n, bool *done, void *user, void *args);
union http_step
{
int (*length)(unsigned long long len, const struct http_cookie *c,
- struct http_response *r, void *user, void *step_args);
+ struct http_response *r, void *user, void *args);
int (*payload)(const struct http_payload *p, struct http_response *r,
- void *user, void *step_args);
+ void *user, void *args);
} step;
};