diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-10-06 23:01:42 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-10-08 01:57:42 +0200 |
| commit | e77bd93693a74ce872d4c13fb45537c34518d84f (patch) | |
| tree | 8fb265064ebf9debed2e797cbdef42187524d158 | |
| parent | db9cb051c4ee05c07ab32dfed5bae8b7dc0916bd (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.7 | 63 | ||||
| -rw-r--r-- | examples/async/main.c | 6 | ||||
| -rw-r--r-- | handler.c | 10 | ||||
| -rw-r--r-- | http.c | 138 | ||||
| -rw-r--r-- | include/libweb/http.h | 7 |
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; @@ -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; @@ -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; }; |
