From 3e4c7c993bbbe2bdeb563fa888b900d01c4be4a1 Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Mon, 6 Oct 2025 01:23:20 +0200 Subject: Fix design issues with async responses, add async example struct http_response did not provide users any void * that could be used to maintain a state between calls to an asynchronous HTTP response. On the other hand, the user pointer could not be used for this purpose, since it is shared among all HTTP clients for a given struct handler instance. Moreover, the length callback was still not supporting this feature, which in fact might be required by some users. Implementing this was particularly challenging, as this broke the current assumption that all bytes on a call to http_read were being processed. Now, since a client request can only be partially processed because of the length callback, http_read must take this into account so that the remaining bytes are still available for future calls, before reading again from the file descriptor. --- http.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 42 deletions(-) (limited to 'http.c') diff --git a/http.c b/http.c index 67aa62e..c173c3d 100644 --- a/http.c +++ b/http.c @@ -103,6 +103,8 @@ struct http_ctx struct http_header *headers; bool has_length, expect_continue; int (*response)(struct http_ctx *); + void *buf; + size_t buflen; } ctx; struct write_ctx @@ -653,6 +655,7 @@ static void ctx_free(struct ctx *const c) free(c->headers); free(c->args); + free(c->buf); *c = (const struct ctx){0}; } @@ -1220,7 +1223,7 @@ static int process_payload(struct http_ctx *const h) ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { h->wctx.op = c->op; ctx_free(c); @@ -1475,29 +1478,15 @@ static int check_length(struct http_ctx *const h) return h->cfg.length(c->u2.payload.len, &cookie, &h->wctx.r, h->cfg.user); } -static int process_expect(struct http_ctx *const h) +static int process_payload_expect(struct http_ctx *const h) { struct ctx *const c = &h->ctx; struct write_ctx *const w = &h->wctx; - const int res = check_length(h); - - if (res) - { - if (res < 0) - { - fprintf(stderr, "%s: check_length failed\n", __func__); - return res; - } - - w->close = true; - return start_response(h); - } - + struct http_response *const r = &h->wctx.r; struct http_payload p = ctx_to_payload(c); p.expect_continue = true; - struct http_response *const r = &h->wctx.r; const int ret = h->cfg.payload(&p, r, h->cfg.user); if (ret) @@ -1505,7 +1494,7 @@ static int process_expect(struct http_ctx *const h) ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { w->op = c->op; c->state = BODY_LINE; @@ -1515,6 +1504,30 @@ static int process_expect(struct http_ctx *const h) return 0; } +static int process_expect(struct http_ctx *const h) +{ + struct ctx *const c = &h->ctx; + struct write_ctx *const w = &h->wctx; + struct http_response *const r = &h->wctx.r; + const int res = check_length(h); + + if (res) + { + if (res < 0) + { + fprintf(stderr, "%s: check_length failed\n", __func__); + return res; + } + + w->close = true; + return start_response(h); + } + else if (!r->step.length) + c->response = process_payload_expect; + + return 0; +} + static int process_get(struct http_ctx *const h) { int ret; @@ -1530,7 +1543,7 @@ static int process_get(struct http_ctx *const h) ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { w->op = c->op; ctx_free(c); @@ -1544,6 +1557,34 @@ static int process_get(struct http_ctx *const h) return 0; } +static int process_boundary(struct http_ctx *const h) +{ + struct ctx *const c = &h->ctx; + struct write_ctx *const w = &h->wctx; + const int res = check_length(h); + + if (res) + { + if (res < 0) + { + fprintf(stderr, "%s: check_length failed\n", + __func__); + return res; + } + + w->close = true; + ctx_free(c); + return start_response(h); + } + else if (!w->r.step.length) + { + c->response = NULL; + c->state = BODY_LINE; + } + + return 0; +} + static int header_cr_line(struct http_ctx *const h) { const char *const line = (const char *)h->line; @@ -1575,20 +1616,8 @@ static int header_cr_line(struct http_ctx *const h) } else if (c->boundary) { - const int res = check_length(h); - - if (res) - { - if (res < 0) - { - fprintf(stderr, "%s: check_length failed\n", - __func__); - return res; - } - - h->wctx.close = true; - return start_response(h); - } + c->response = process_boundary; + return 0; } c->state = BODY_LINE; @@ -1640,7 +1669,7 @@ static int send_payload(struct http_ctx *const h, ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { ctx_free(c); return start_response(h); @@ -2524,28 +2553,67 @@ static int read_buf(struct http_ctx *const h, const char *const buf, return -1; } +static int storebuf(struct http_ctx *const h, const void *const src, + const size_t n) +{ + struct ctx *const c = &h->ctx; + void *const dst = malloc(n); + + if (!dst) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + return -1; + } + + memcpy((c->buf = dst), src, (c->buflen = n)); + return 0; +} + +static int loadbuf(struct http_ctx *const h, void *const dst, const size_t n) +{ + struct ctx *const c = &h->ctx; + + memcpy(dst, c->buf, c->buflen); + free(c->buf); + c->buf = NULL; + return c->buflen; +} + int http_read(struct http_ctx *const h, bool *const close) { + struct ctx *const c = &h->ctx; char buf[BUFSIZ]; - const int r = h->cfg.read(buf, sizeof buf, h->cfg.user); + int ret, r; - if (r <= 0) + if (c->buf) + r = loadbuf(h, buf, sizeof buf); + else if ((r = h->cfg.read(buf, sizeof buf, h->cfg.user)) <= 0) return rw_error(r, close); size_t rem = r; while (rem) { - const int ret = read_buf(h, &buf[r - rem], &rem); - - if (ret) + if ((ret = read_buf(h, &buf[r - rem], &rem))) + goto failure; + else if (c->response && rem) { - ctx_free(&h->ctx); - return ret; + if (storebuf(h, &buf[r - rem], rem)) + { + fprintf(stderr, "%s: storebuf failed\n", __func__); + ret = -1; + goto failure; + } + + break; } } return 0; + +failure: + ctx_free(&h->ctx); + return ret; } static int append_expire(struct dynstr *const d) @@ -2615,7 +2683,7 @@ int http_update(struct http_ctx *const h, bool *const write, bool *const close) else ret = w->pending ? http_write(h, close) : http_read(h, close); - *write = c->response || w->pending; + *write = c->buf || c->response || w->pending; return ret; } -- cgit v1.2.3