aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-01-20 01:05:05 +0100
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-01-20 01:19:57 +0100
commit43be8d95f681c8240847b4f69a524a0ab20d5c83 (patch)
treea4e45ad7784219fe5a43a2feae572827735a619e
parentb0accd099fa8c5110d4c3c68830ad6fd810ca3ec (diff)
http.c: Solve performance issues on POST uploads
Profiling showed that reading multipart/form POST uploads byte-by-byte was too slow and typically led to maximum CPU usage. Therefore, the older approach (as done up to commit 7efc2b3a) was more efficient, even if the resulting code was a bit uglier.
-rw-r--r--http.c138
1 files changed, 91 insertions, 47 deletions
diff --git a/http.c b/http.c
index 99677e9..478541e 100644
--- a/http.c
+++ b/http.c
@@ -1313,10 +1313,12 @@ static int send_payload(struct http_ctx *const h,
}
static int update_lstate(struct http_ctx *const h, bool *const close,
- int (*const f)(struct http_ctx *), const char b)
+ int (*const f)(struct http_ctx *), const char *const buf,
+ size_t *const sz)
{
int ret = 1;
struct ctx *const c = &h->ctx;
+ const char b = *buf;
switch (c->lstate)
{
@@ -1358,6 +1360,7 @@ static int update_lstate(struct http_ctx *const h, bool *const close,
break;
}
+ (*sz)--;
return 0;
failure:
@@ -1642,24 +1645,27 @@ end:
return ret;
}
-static int read_mf_body_to_mem(struct http_ctx *const h, const char b)
+static int read_mf_body_to_mem(struct http_ctx *const h,
+ const char *const buf, const size_t n)
{
struct ctx *const c = &h->ctx;
struct multiform *const m = &c->u.mf;
- if (m->written + 1 > sizeof h->line - 1)
+ if (m->written + n > sizeof h->line - 1)
{
fprintf(stderr, "%s: maximum length exceeded\n", __func__);
return 1;
}
- h->line[m->written++] = b;
- m->len++;
- c->payload.read++;
+ memcpy(h->line, buf, n);
+ m->written += n;
+ m->len += n;
+ c->payload.read += n;
return 0;
}
-static int read_mf_body_to_file(struct http_ctx *const h, const char b)
+static int read_mf_body_to_file(struct http_ctx *const h,
+ const char *const buf, const size_t n)
{
struct ctx *const c = &h->ctx;
struct multiform *const m = &c->u.mf;
@@ -1669,16 +1675,16 @@ static int read_mf_body_to_file(struct http_ctx *const h, const char b)
fprintf(stderr, "%s: generate_mf_file failed\n", __func__);
return -1;
}
- else if (!fwrite(&b, sizeof b, 1, m->f))
+ else if (n && !fwrite(buf, n, 1, m->f))
{
fprintf(stderr, "%s: fwrite(3) failed, feof=%d, ferror=%d\n",
__func__, feof(m->f), ferror(m->f));
return -1;
}
- m->written++;
- m->len++;
- c->payload.read++;
+ m->written += n;
+ m->len += n;
+ c->payload.read += n;
return 0;
}
@@ -1686,31 +1692,28 @@ static int reset_boundary(struct http_ctx *const h)
{
struct multiform *const m = &h->ctx.u.mf;
struct form *const f = &m->forms[m->nforms - 1];
- int (*const read_mf)(struct http_ctx *, char) =
+ int (*const read_mf)(struct http_ctx *, const char *, size_t) =
f->filename ? read_mf_body_to_file : read_mf_body_to_mem;
const size_t len = strlen(m->boundary);
+ const int res = read_mf(h, m->boundary, len);
- for (size_t i = 0; i < len; i++)
- {
- const int res = read_mf(h, m->boundary[i]);
-
- if (res)
- return res;
- }
+ if (res)
+ return res;
memset(m->boundary, '\0', len);
m->blen = 0;
return 0;
}
-static int dump_body(struct http_ctx *const h, const char b)
+static int dump_body(struct http_ctx *const h, const char *const buf,
+ const size_t n)
{
struct multiform *const m = &h->ctx.u.mf;
struct form *const f = &m->forms[m->nforms - 1];
- int (*const read_mf)(struct http_ctx *, char) =
+ int (*const read_mf)(struct http_ctx *, const char *, size_t) =
f->filename ? read_mf_body_to_file : read_mf_body_to_mem;
- return read_mf(h, b);
+ return read_mf(h, buf, n);
}
static int apply_from_file(struct http_ctx *const h, struct form *const f)
@@ -1798,7 +1801,8 @@ static int apply_from_mem(struct http_ctx *const h, struct form *const f)
return 0;
}
-static int read_mf_body_boundary_byte(struct http_ctx *const h, const char b)
+static int read_mf_body_boundary_byte(struct http_ctx *const h, const char b,
+ const size_t len)
{
struct ctx *const c = &h->ctx;
struct multiform *const m = &c->u.mf;
@@ -1865,26 +1869,58 @@ static const char *http_memmem(const char *const a, const void *const b,
return st;
}
-static int read_mf_body_boundary(struct http_ctx *const h, const char b)
+static int read_mf_body_boundary(struct http_ctx *const h,
+ const char *buf, size_t *const n)
{
struct ctx *const c = &h->ctx;
struct multiform *const m = &c->u.mf;
int res;
- if (b != c->boundary[m->blen])
+ if (m->blen
+ && *buf != c->boundary[m->blen]
+ && (res = reset_boundary(h)))
+ return res;
+
+ const char *const boundary = http_memmem(&c->boundary[m->blen], buf, *n);
+
+ if (!boundary)
{
if ((res = reset_boundary(h))
- || (res = dump_body(h, b)))
+ || (res = dump_body(h, buf, *n)))
return res;
+ *n = 0;
return 0;
}
- return read_mf_body_boundary_byte(h, b);
+ const size_t prev = boundary - buf;
+
+ if (prev && (res = reset_boundary(h)))
+ return res;
+ else if ((res = dump_body(h, buf, prev)))
+ return res;
+
+ buf += prev;
+ *n -= prev;
+
+ const size_t len = strlen(m->boundary),
+ rem = strlen(c->boundary) - len,
+ r = rem > *n ? *n : rem;
+
+ for (size_t i = 0; i < r; i++)
+ {
+ const char *const b = buf;
+
+ if ((res = read_mf_body_boundary_byte(h, b[i], len)))
+ return res;
+ }
+
+ *n -= r;
+ return 0;
}
static int read_multiform(struct http_ctx *const h, bool *const close,
- const char b)
+ const char *const buf, size_t *const sz)
{
struct multiform *const m = &h->ctx.u.mf;
int res;
@@ -1896,26 +1932,27 @@ static int read_multiform(struct http_ctx *const h, bool *const close,
case MF_HEADER_CR_LINE:
/* Fall through. */
case MF_END_BOUNDARY_CR_LINE:
- {
- if ((res = update_lstate(h, close, process_mf_line, b)))
+ if ((res = update_lstate(h, close, process_mf_line, buf, sz)))
return res;
- }
break;
case MF_BODY_BOUNDARY_LINE:
- if ((res = read_mf_body_boundary(h, b)))
+ if ((res = read_mf_body_boundary(h, buf, sz)))
return res;
+
+ break;
}
return 0;
}
static int read_body_to_mem(struct http_ctx *const h, bool *const close,
- const char b)
+ const char *const buf, size_t *const sz)
{
struct ctx *const c = &h->ctx;
struct payload *const p = &c->payload;
+ const char b = *buf;
if (p->read >= sizeof h->line - 1)
{
@@ -1924,6 +1961,7 @@ static int read_body_to_mem(struct http_ctx *const h, bool *const close,
}
h->line[p->read++] = b;
+ (*sz)--;
if (p->read >= p->len)
{
@@ -2006,7 +2044,8 @@ end:
return ret;
}
-static int read_put_body_to_file(struct http_ctx *const h, const char b)
+static int read_put_body_to_file(struct http_ctx *const h,
+ const void *const buf, size_t *const sz)
{
struct ctx *const c = &h->ctx;
struct put *const put = &c->u.put;
@@ -2021,24 +2060,25 @@ static int read_put_body_to_file(struct http_ctx *const h, const char b)
fprintf(stderr, "%s: ensure_put_file failed\n", __func__);
return -1;
}
- else if (!fwrite(&b, sizeof b, 1, put->f))
+ else if (!fwrite(buf, *sz, 1, put->f))
{
fprintf(stderr, "%s: fwrite(3) failed, feof=%d ferror=%d\n",
__func__, feof(put->f), ferror(put->f));
return -1;
}
- c->payload.read++;
+ c->payload.read += *sz;
+ *sz = 0;
return 0;
}
static int read_to_file(struct http_ctx *const h, bool *const close,
- const char b)
+ const char *const buf, size_t *const sz)
{
struct ctx *const c = &h->ctx;
struct payload *const p = &c->payload;
- if (read_put_body_to_file(h, b))
+ if (read_put_body_to_file(h, buf, sz))
return -1;
else if (p->read >= p->len)
{
@@ -2064,18 +2104,19 @@ static int read_to_file(struct http_ctx *const h, bool *const close,
return 0;
}
-static int read_body(struct http_ctx *const h, bool *const close, const char b)
+static int read_body(struct http_ctx *const h, bool *const close,
+ const char *const buf, size_t *const sz)
{
const struct ctx *const c = &h->ctx;
switch (c->op)
{
case HTTP_OP_POST:
- return c->boundary ? read_multiform(h, close, b)
- : read_body_to_mem(h, close, b);
+ return c->boundary ? read_multiform(h, close, buf, sz)
+ : read_body_to_mem(h, close, buf, sz);
case HTTP_OP_PUT:
- return read_to_file(h, close, b);
+ return read_to_file(h, close, buf, sz);
case HTTP_OP_GET:
/* Fall through. */
@@ -2098,17 +2139,18 @@ static int process_line(struct http_ctx *const h)
return state[h->ctx.state](h);
}
-static int read_byte(struct http_ctx *const h, bool *const close, const char b)
+static int read_buf(struct http_ctx *const h, bool *const close,
+ const char *const buf, size_t *const sz)
{
switch (h->ctx.state)
{
case START_LINE:
/* Fall through. */
case HEADER_CR_LINE:
- return update_lstate(h, close, process_line, b);
+ return update_lstate(h, close, process_line, buf, sz);
case BODY_LINE:
- return read_body(h, close, b);
+ return read_body(h, close, buf, sz);
}
fprintf(stderr, "%s: unexpected state %d\n", __func__, h->ctx.state);
@@ -2123,9 +2165,11 @@ int http_read(struct http_ctx *const h, bool *const close)
if (r <= 0)
return rw_error(r, close);
- for (int i = 0; i < r; i++)
+ size_t rem = r;
+
+ while (rem)
{
- const int ret = read_byte(h, close, buf[i]);
+ const int ret = read_buf(h, close, &buf[r - rem], &rem);
if (ret)
return ret;