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.
This commit is contained in:
parent
b0accd099f
commit
99f76d17ba
132
http.c
132
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,
|
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;
|
int ret = 1;
|
||||||
struct ctx *const c = &h->ctx;
|
struct ctx *const c = &h->ctx;
|
||||||
|
const char b = *buf;
|
||||||
|
|
||||||
switch (c->lstate)
|
switch (c->lstate)
|
||||||
{
|
{
|
||||||
|
@ -1358,6 +1360,7 @@ static int update_lstate(struct http_ctx *const h, bool *const close,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(*sz)--;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
failure:
|
failure:
|
||||||
|
@ -1642,24 +1645,27 @@ end:
|
||||||
return ret;
|
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 ctx *const c = &h->ctx;
|
||||||
struct multiform *const m = &c->u.mf;
|
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__);
|
fprintf(stderr, "%s: maximum length exceeded\n", __func__);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
h->line[m->written++] = b;
|
memcpy(h->line, buf, n);
|
||||||
m->len++;
|
m->written += n;
|
||||||
c->payload.read++;
|
m->len += n;
|
||||||
|
c->payload.read += n;
|
||||||
return 0;
|
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 ctx *const c = &h->ctx;
|
||||||
struct multiform *const m = &c->u.mf;
|
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__);
|
fprintf(stderr, "%s: generate_mf_file failed\n", __func__);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else if (!fwrite(&b, sizeof b, 1, m->f))
|
else if (!fwrite(buf, n, 1, m->f))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: fwrite(3) failed, feof=%d, ferror=%d\n",
|
fprintf(stderr, "%s: fwrite(3) failed, feof=%d, ferror=%d\n",
|
||||||
__func__, feof(m->f), ferror(m->f));
|
__func__, feof(m->f), ferror(m->f));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
m->written++;
|
m->written += n;
|
||||||
m->len++;
|
m->len += n;
|
||||||
c->payload.read++;
|
c->payload.read += n;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1686,13 +1692,13 @@ static int reset_boundary(struct http_ctx *const h)
|
||||||
{
|
{
|
||||||
struct multiform *const m = &h->ctx.u.mf;
|
struct multiform *const m = &h->ctx.u.mf;
|
||||||
struct form *const f = &m->forms[m->nforms - 1];
|
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;
|
f->filename ? read_mf_body_to_file : read_mf_body_to_mem;
|
||||||
const size_t len = strlen(m->boundary);
|
const size_t len = strlen(m->boundary);
|
||||||
|
|
||||||
for (size_t i = 0; i < len; i++)
|
if (len)
|
||||||
{
|
{
|
||||||
const int res = read_mf(h, m->boundary[i]);
|
const int res = read_mf(h, m->boundary, len);
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
return res;
|
return res;
|
||||||
|
@ -1703,14 +1709,15 @@ static int reset_boundary(struct http_ctx *const h)
|
||||||
return 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 multiform *const m = &h->ctx.u.mf;
|
||||||
struct form *const f = &m->forms[m->nforms - 1];
|
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;
|
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)
|
static int apply_from_file(struct http_ctx *const h, struct form *const f)
|
||||||
|
@ -1798,7 +1805,8 @@ static int apply_from_mem(struct http_ctx *const h, struct form *const f)
|
||||||
return 0;
|
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 ctx *const c = &h->ctx;
|
||||||
struct multiform *const m = &c->u.mf;
|
struct multiform *const m = &c->u.mf;
|
||||||
|
@ -1865,26 +1873,58 @@ static const char *http_memmem(const char *const a, const void *const b,
|
||||||
return st;
|
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 ctx *const c = &h->ctx;
|
||||||
struct multiform *const m = &c->u.mf;
|
struct multiform *const m = &c->u.mf;
|
||||||
int res;
|
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))
|
if ((res = reset_boundary(h))
|
||||||
|| (res = dump_body(h, b)))
|
|| (res = dump_body(h, buf, *n)))
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
|
*n = 0;
|
||||||
return 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,
|
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;
|
struct multiform *const m = &h->ctx.u.mf;
|
||||||
int res;
|
int res;
|
||||||
|
@ -1896,26 +1936,27 @@ static int read_multiform(struct http_ctx *const h, bool *const close,
|
||||||
case MF_HEADER_CR_LINE:
|
case MF_HEADER_CR_LINE:
|
||||||
/* Fall through. */
|
/* Fall through. */
|
||||||
case MF_END_BOUNDARY_CR_LINE:
|
case MF_END_BOUNDARY_CR_LINE:
|
||||||
{
|
if ((res = update_lstate(h, close, process_mf_line, buf, sz)))
|
||||||
if ((res = update_lstate(h, close, process_mf_line, b)))
|
|
||||||
return res;
|
return res;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MF_BODY_BOUNDARY_LINE:
|
case MF_BODY_BOUNDARY_LINE:
|
||||||
if ((res = read_mf_body_boundary(h, b)))
|
if ((res = read_mf_body_boundary(h, buf, sz)))
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int read_body_to_mem(struct http_ctx *const h, bool *const close,
|
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 ctx *const c = &h->ctx;
|
||||||
struct payload *const p = &c->payload;
|
struct payload *const p = &c->payload;
|
||||||
|
const char b = *buf;
|
||||||
|
|
||||||
if (p->read >= sizeof h->line - 1)
|
if (p->read >= sizeof h->line - 1)
|
||||||
{
|
{
|
||||||
|
@ -1924,6 +1965,7 @@ static int read_body_to_mem(struct http_ctx *const h, bool *const close,
|
||||||
}
|
}
|
||||||
|
|
||||||
h->line[p->read++] = b;
|
h->line[p->read++] = b;
|
||||||
|
(*sz)--;
|
||||||
|
|
||||||
if (p->read >= p->len)
|
if (p->read >= p->len)
|
||||||
{
|
{
|
||||||
|
@ -2006,7 +2048,8 @@ end:
|
||||||
return ret;
|
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 ctx *const c = &h->ctx;
|
||||||
struct put *const put = &c->u.put;
|
struct put *const put = &c->u.put;
|
||||||
|
@ -2021,24 +2064,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__);
|
fprintf(stderr, "%s: ensure_put_file failed\n", __func__);
|
||||||
return -1;
|
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",
|
fprintf(stderr, "%s: fwrite(3) failed, feof=%d ferror=%d\n",
|
||||||
__func__, feof(put->f), ferror(put->f));
|
__func__, feof(put->f), ferror(put->f));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->payload.read++;
|
c->payload.read += *sz;
|
||||||
|
*sz = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int read_to_file(struct http_ctx *const h, bool *const close,
|
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 ctx *const c = &h->ctx;
|
||||||
struct payload *const p = &c->payload;
|
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;
|
return -1;
|
||||||
else if (p->read >= p->len)
|
else if (p->read >= p->len)
|
||||||
{
|
{
|
||||||
|
@ -2064,18 +2108,19 @@ static int read_to_file(struct http_ctx *const h, bool *const close,
|
||||||
return 0;
|
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;
|
const struct ctx *const c = &h->ctx;
|
||||||
|
|
||||||
switch (c->op)
|
switch (c->op)
|
||||||
{
|
{
|
||||||
case HTTP_OP_POST:
|
case HTTP_OP_POST:
|
||||||
return c->boundary ? read_multiform(h, close, b)
|
return c->boundary ? read_multiform(h, close, buf, sz)
|
||||||
: read_body_to_mem(h, close, b);
|
: read_body_to_mem(h, close, buf, sz);
|
||||||
|
|
||||||
case HTTP_OP_PUT:
|
case HTTP_OP_PUT:
|
||||||
return read_to_file(h, close, b);
|
return read_to_file(h, close, buf, sz);
|
||||||
|
|
||||||
case HTTP_OP_GET:
|
case HTTP_OP_GET:
|
||||||
/* Fall through. */
|
/* Fall through. */
|
||||||
|
@ -2098,17 +2143,18 @@ static int process_line(struct http_ctx *const h)
|
||||||
return state[h->ctx.state](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)
|
switch (h->ctx.state)
|
||||||
{
|
{
|
||||||
case START_LINE:
|
case START_LINE:
|
||||||
/* Fall through. */
|
/* Fall through. */
|
||||||
case HEADER_CR_LINE:
|
case HEADER_CR_LINE:
|
||||||
return update_lstate(h, close, process_line, b);
|
return update_lstate(h, close, process_line, buf, sz);
|
||||||
|
|
||||||
case BODY_LINE:
|
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);
|
fprintf(stderr, "%s: unexpected state %d\n", __func__, h->ctx.state);
|
||||||
|
@ -2123,9 +2169,11 @@ int http_read(struct http_ctx *const h, bool *const close)
|
||||||
if (r <= 0)
|
if (r <= 0)
|
||||||
return rw_error(r, close);
|
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)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
Loading…
Reference in New Issue