Commit daf0e2c1 authored by Poul-Henning Kamp's avatar Poul-Henning Kamp

Revamp the req.body handling code.

There now is an undocumented facility for buffering the req.body,
but I'm not done testing it, and in particular the error handling
has not been exercised to any extent.
parent fc092590
...@@ -77,6 +77,14 @@ body_status(enum body_status e) ...@@ -77,6 +77,14 @@ body_status(enum body_status e)
/*--------------------------------------------------------------------*/ /*--------------------------------------------------------------------*/
enum req_body_state_e {
#define REQ_BODY(U) REQ_BODY_##U,
#include <tbl/req_body.h>
#undef REQ_BODY
};
/*--------------------------------------------------------------------*/
enum sess_close { enum sess_close {
SC_NULL = 0, SC_NULL = 0,
#define SESS_CLOSE(nm, desc) SC_##nm, #define SESS_CLOSE(nm, desc) SC_##nm,
...@@ -582,14 +590,6 @@ struct object { ...@@ -582,14 +590,6 @@ struct object {
/*--------------------------------------------------------------------*/ /*--------------------------------------------------------------------*/
enum req_body_state_e {
REQ_BODY_INIT = 0,
REQ_BODY_CL,
// REQ_BODY_CHUNKED,
REQ_BODY_DONE,
REQ_BODY_NONE
};
struct req { struct req {
unsigned magic; unsigned magic;
#define REQ_MAGIC 0x2751aaa1 #define REQ_MAGIC 0x2751aaa1
...@@ -605,6 +605,8 @@ struct req { ...@@ -605,6 +605,8 @@ struct req {
enum req_step req_step; enum req_step req_step;
VTAILQ_ENTRY(req) w_list; VTAILQ_ENTRY(req) w_list;
struct storagehead body;
/* The busy objhead we sleep on */ /* The busy objhead we sleep on */
struct objhead *hash_objhead; struct objhead *hash_objhead;
struct busyobj *busyobj; struct busyobj *busyobj;
...@@ -777,9 +779,11 @@ void VBO_DerefBusyObj(struct worker *wrk, struct busyobj **busyobj); ...@@ -777,9 +779,11 @@ void VBO_DerefBusyObj(struct worker *wrk, struct busyobj **busyobj);
void VBO_Free(struct busyobj **vbo); void VBO_Free(struct busyobj **vbo);
/* cache_http1_fsm.c [HTTP1] */ /* cache_http1_fsm.c [HTTP1] */
ssize_t HTTP1_GetReqBody(struct req *, void *buf, ssize_t len); typedef int (req_body_iter_f)(struct req *, void *priv, void *ptr, size_t);
int HTTP1_DiscardReqBody(struct req *req);
void HTTP1_Session(struct worker *, struct req *); void HTTP1_Session(struct worker *, struct req *);
int HTTP1_DiscardReqBody(struct req *req);
int HTTP1_CacheReqBody(struct req *req, ssize_t maxsize);
int HTTP1_IterateReqBody(struct req *req, req_body_iter_f *func, void *priv);
/* cache_req_fsm.c [CNT] */ /* cache_req_fsm.c [CNT] */
int CNT_Request(struct worker *, struct req *); int CNT_Request(struct worker *, struct req *);
...@@ -1074,6 +1078,7 @@ void STV_close(void); ...@@ -1074,6 +1078,7 @@ void STV_close(void);
void STV_Freestore(struct object *o); void STV_Freestore(struct object *o);
int STV_BanInfo(enum baninfo event, const uint8_t *ban, unsigned len); int STV_BanInfo(enum baninfo event, const uint8_t *ban, unsigned len);
void STV_BanExport(const uint8_t *bans, unsigned len); void STV_BanExport(const uint8_t *bans, unsigned len);
struct storage *STV_alloc_transient(size_t size);
/* storage_synth.c */ /* storage_synth.c */
struct vsb *SMS_Makesynth(struct object *obj); struct vsb *SMS_Makesynth(struct object *obj);
......
...@@ -44,8 +44,6 @@ ...@@ -44,8 +44,6 @@
static unsigned fetchfrag; static unsigned fetchfrag;
static int fetchReqBody(struct req *req, int sendbody);
/*-------------------------------------------------------------------- /*--------------------------------------------------------------------
* We want to issue the first error we encounter on fetching and * We want to issue the first error we encounter on fetching and
* supress the rest. This function does that. * supress the rest. This function does that.
...@@ -373,39 +371,20 @@ fetch_eof(struct busyobj *bo, struct http_conn *htc) ...@@ -373,39 +371,20 @@ fetch_eof(struct busyobj *bo, struct http_conn *htc)
} }
/*-------------------------------------------------------------------- /*--------------------------------------------------------------------
* Fetch any body attached to the incoming request, and either write it * Pass the request body to the backend
* to the backend (if we pass) or discard it (anything else).
* This is mainly a separate function to isolate the stack buffer and
* to contain the complexity when we start handling chunked encoding.
*/ */
static int static int __match_proto__(req_body_iter_f)
fetchReqBody(struct req *req, int sendbody) fetch_iter_req_body(struct req *req, void *priv, void *ptr, size_t l)
{ {
char buf[8192];
ssize_t l = 1234;
if (req->req_body_status == REQ_BODY_DONE) { CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
AZ(sendbody); (void)priv;
return (0);
} if (l > 0) {
while (req->req_body_status != REQ_BODY_DONE && (void)WRW_Write(req->wrk, ptr, l);
req->req_body_status != REQ_BODY_NONE) { if (WRW_Flush(req->wrk))
l = HTTP1_GetReqBody(req, buf, sizeof buf); return (-1);
if (l < 0) {
return (1);
} else if (l == 0) {
assert(req->req_body_status == REQ_BODY_DONE ||
req->req_body_status == REQ_BODY_NONE);
} else if (sendbody) {
/* XXX: stats ? */
(void)WRW_Write(req->wrk, buf, l);
if (WRW_Flush(req->wrk)) {
/* XXX: Try to salvage client-conn ? */
req->req_body_status = REQ_BODY_DONE;
return (2);
}
}
} }
return (0); return (0);
} }
...@@ -468,11 +447,19 @@ FetchHdr(struct req *req, int need_host_hdr, int sendbody) ...@@ -468,11 +447,19 @@ FetchHdr(struct req *req, int need_host_hdr, int sendbody)
WRW_Reserve(wrk, &vc->fd, bo->vsl, req->t_req); /* XXX t_resp ? */ WRW_Reserve(wrk, &vc->fd, bo->vsl, req->t_req); /* XXX t_resp ? */
(void)http_Write(wrk, hp, 0); /* XXX: stats ? */ (void)http_Write(wrk, hp, 0); /* XXX: stats ? */
/* Deal with any message-body the request might have */ /* Deal with any message-body the request might (still) have */
i = fetchReqBody(req, sendbody); i = 0;
if (sendbody && req->req_body_status == REQ_BODY_DONE)
retry = -1; if (sendbody) {
if (WRW_FlushRelease(wrk) || i > 0) { i = HTTP1_IterateReqBody(req,
fetch_iter_req_body, NULL);
if (req->req_body_status == REQ_BODY_DONE)
retry = -1;
} else {
i = HTTP1_DiscardReqBody(req);
}
if (WRW_FlushRelease(wrk) || i != 0) {
VSLb(req->vsl, SLT_FetchError, VSLb(req->vsl, SLT_FetchError,
"backend write error: %d (%s)", "backend write error: %d (%s)",
errno, strerror(errno)); errno, strerror(errno));
......
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
#include "vtcp.h" #include "vtcp.h"
#include "vtim.h" #include "vtim.h"
/*-------------------------------------------------------------------- /*----------------------------------------------------------------------
* Collect a request from the client. * Collect a request from the client.
*/ */
...@@ -161,7 +161,7 @@ http1_wait(struct sess *sp, struct worker *wrk, struct req *req) ...@@ -161,7 +161,7 @@ http1_wait(struct sess *sp, struct worker *wrk, struct req *req)
return (1); return (1);
} }
/*-------------------------------------------------------------------- /*----------------------------------------------------------------------
* This is the final state, figure out if we should close or recycle * This is the final state, figure out if we should close or recycle
* the client connection * the client connection
*/ */
...@@ -235,7 +235,7 @@ http1_cleanup(struct sess *sp, struct worker *wrk, struct req *req) ...@@ -235,7 +235,7 @@ http1_cleanup(struct sess *sp, struct worker *wrk, struct req *req)
} }
} }
/*-------------------------------------------------------------------- /*----------------------------------------------------------------------
*/ */
static int static int
...@@ -296,7 +296,7 @@ http1_dissect(struct worker *wrk, struct req *req) ...@@ -296,7 +296,7 @@ http1_dissect(struct worker *wrk, struct req *req)
return (0); return (0);
} }
/*-------------------------------------------------------------------- /*----------------------------------------------------------------------
*/ */
void void
...@@ -375,75 +375,225 @@ HTTP1_Session(struct worker *wrk, struct req *req) ...@@ -375,75 +375,225 @@ HTTP1_Session(struct worker *wrk, struct req *req)
} }
} }
/* /*----------------------------------------------------------------------
* XXX: DiscardReqBody() is a dedicated function, because we might */
* XXX: be able to disuade or terminate its transmission in some protocols.
*/
int
HTTP1_DiscardReqBody(struct req *req)
{
char buf[8192];
ssize_t l;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC); struct http1_r_b_s {
while (req->req_body_status != REQ_BODY_DONE && ssize_t bytes_done;
req->req_body_status != REQ_BODY_NONE) { ssize_t yet;
l = HTTP1_GetReqBody(req, buf, sizeof buf); enum {CL, CHUNKED} mode;
if (l < 0) };
return (-1);
}
return (0);
}
ssize_t static int
HTTP1_GetReqBody(struct req *req, void *buf, ssize_t len) http1_setup_req_body(struct req *req, struct http1_r_b_s *rbs)
{ {
char *ptr, *endp; char *ptr, *endp;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC); CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
memset(rbs, 0, sizeof *rbs);
if (req->req_body_status == REQ_BODY_INIT) { assert(req->req_body_status == REQ_BODY_INIT);
if (http_GetHdr(req->http, H_Content_Length, &ptr)) { if (http_GetHdr(req->http, H_Content_Length, &ptr)) {
AN(ptr); AN(ptr);
if (*ptr == '\0') { if (*ptr == '\0') {
req->req_body_status = REQ_BODY_DONE; req->req_body_status = REQ_BODY_FAIL;
return (-1);
}
req->req_bodybytes = strtoul(ptr, &endp, 10);
if (*endp != '\0' && !vct_islws(*endp)) {
req->req_body_status = REQ_BODY_DONE;
return (-1);
}
if (req->req_bodybytes == 0) {
req->req_body_status = REQ_BODY_DONE;
return (0);
}
req->req_body_status = REQ_BODY_CL;
} else if (http_GetHdr(req->http, H_Transfer_Encoding, NULL)) {
VSLb(req->vsl, SLT_Debug,
"Transfer-Encoding in request");
req->req_body_status = REQ_BODY_DONE;
return (-1); return (-1);
} else { }
req->req_bodybytes = strtoul(ptr, &endp, 10);
if (*endp != '\0' && !vct_islws(*endp)) {
req->req_body_status = REQ_BODY_FAIL;
return (-1);
}
if (req->req_bodybytes == 0) {
req->req_body_status = REQ_BODY_NONE; req->req_body_status = REQ_BODY_NONE;
return (0); return (0);
} }
rbs->mode = CL;
rbs->yet = req->req_bodybytes - rbs->bytes_done;
return (0);
} }
if (req->req_body_status == REQ_BODY_CL) {
if (req->req_bodybytes == 0) { if (http_GetHdr(req->http, H_Transfer_Encoding, NULL)) {
rbs->mode = CHUNKED;
VSLb(req->vsl, SLT_Debug,
"Transfer-Encoding in request");
req->req_body_status = REQ_BODY_DONE;
return (-1);
}
req->req_body_status = REQ_BODY_NONE;
req->req_bodybytes = 0;
return (0);
}
static ssize_t
http1_iter_req_body(struct req *req, struct http1_r_b_s *rbs, void *buf,
ssize_t len)
{
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
if (rbs->mode == CL) {
AN(req->req_bodybytes);
AN(len);
AN(buf);
if (len > req->req_bodybytes - rbs->bytes_done)
len = req->req_bodybytes - rbs->bytes_done;
if (len == 0) {
req->req_body_status = REQ_BODY_DONE; req->req_body_status = REQ_BODY_DONE;
return (0); return (0);
} }
if (len > req->req_bodybytes)
len = req->req_bodybytes;
len = HTC_Read(req->htc, buf, len); len = HTC_Read(req->htc, buf, len);
if (len <= 0) { if (len <= 0) {
req->req_body_status = REQ_BODY_DONE; req->req_body_status = REQ_BODY_FAIL;
return (-1); return (-1);
} }
req->req_bodybytes -= len; rbs->bytes_done += len;
rbs->yet = req->req_bodybytes - rbs->bytes_done;
return (len); return (len);
} }
INCOMPL();
}
/*----------------------------------------------------------------------
* Iterate over the req.body.
*
* This can be done exactly once if uncached, and multiple times if the
* req.body is cached.
*/
int
HTTP1_IterateReqBody(struct req *req, req_body_iter_f *func, void *priv)
{
char buf[8192];
struct storage *st;
ssize_t l;
int i;
struct http1_r_b_s rbs;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
AN(func);
if (req->req_body_status == REQ_BODY_CACHED) {
VTAILQ_FOREACH(st, &req->body, list) {
i = func(req, priv, st->ptr, st->len);
if (i)
return (i);
}
return (0);
}
if (req->req_body_status == REQ_BODY_NONE)
return (0);
if (req->req_body_status != REQ_BODY_INIT)
return (-1);
i = http1_setup_req_body(req, &rbs);
if (i < 0)
return (i);
if (req->req_body_status == REQ_BODY_NONE)
return (0);
do {
l = http1_iter_req_body(req, &rbs, buf, sizeof buf);
if (l < 0)
return (l);
if (l > 0) {
i = func(req, priv, buf, l);
if (i)
return (i);
}
} while (l > 0);
return(0);
}
/*----------------------------------------------------------------------
* DiscardReqBody() is a dedicated function, because we might
* be able to disuade or terminate its transmission in some protocols.
* For HTTP1 we have no such luck, and we just iterate it into oblivion.
*/
static int __match_proto__(req_body_iter_f)
httpq_req_body_discard(struct req *req, void *priv, void *ptr, size_t len)
{
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
(void)priv;
(void)ptr;
(void)len;
return (0); return (0);
} }
int
HTTP1_DiscardReqBody(struct req *req)
{
return(HTTP1_IterateReqBody(req, httpq_req_body_discard, NULL));
}
/*----------------------------------------------------------------------
* Cache the req.body if it is smaller than the given size
*/
int
HTTP1_CacheReqBody(struct req *req, ssize_t maxsize)
{
struct storage *st;
ssize_t l;
int i;
struct http1_r_b_s rbs;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
if (req->req_body_status == REQ_BODY_CACHED)
return (0);
if (req->req_body_status == REQ_BODY_NONE)
return (0);
if (req->req_body_status != REQ_BODY_INIT)
return (-1);
i = http1_setup_req_body(req, &rbs);
if (i < 0)
return (i);
if (req->req_bodybytes > maxsize) {
req->req_body_status = REQ_BODY_FAIL;
return (-1);
}
if (req->req_body_status == REQ_BODY_NONE)
return (0);
st = NULL;
do {
if (st == NULL) {
st = STV_alloc_transient(
rbs.yet ? rbs.yet : cache_param->fetch_chunksize);
if (st == NULL) {
req->req_body_status = REQ_BODY_FAIL;
return (-1);
} else {
VTAILQ_INSERT_TAIL(&req->body, st, list);
}
}
l = st->space - st->len;
l = http1_iter_req_body(req, &rbs, st->ptr + st->len, l);
if (l < 0)
return (l);
if (req->req_bodybytes > maxsize) {
req->req_body_status = REQ_BODY_FAIL;
return (-1);
}
if (l > 0) {
st->len += l;
if (st->space == st->len)
st = NULL;
}
} while (l > 0);
req->req_body_status = REQ_BODY_CACHED;
return(0);
}
...@@ -232,7 +232,7 @@ pan_busyobj(const struct busyobj *bo) ...@@ -232,7 +232,7 @@ pan_busyobj(const struct busyobj *bo)
static void static void
pan_req(const struct req *req) pan_req(const struct req *req)
{ {
const char *hand, *stp; const char *hand, *stp, *body;
VSB_printf(pan_vsp, "req = %p {\n", req); VSB_printf(pan_vsp, "req = %p {\n", req);
...@@ -249,6 +249,18 @@ pan_req(const struct req *req) ...@@ -249,6 +249,18 @@ pan_req(const struct req *req)
else else
VSB_printf(pan_vsp, " step = 0x%x,\n", req->req_step); VSB_printf(pan_vsp, " step = 0x%x,\n", req->req_step);
switch (req->req_body_status) {
#define REQ_BODY(U) case REQ_BODY_##U: body = "R_BODY_" #U; break;
#include "tbl/req_body.h"
#undef REQ_BODY
default: body = NULL;
}
if (body != NULL)
VSB_printf(pan_vsp, " req_body = %s,\n", body);
else
VSB_printf(pan_vsp, " req_body = 0x%x,\n",
req->req_body_status);
hand = VCL_Return_Name(req->handling); hand = VCL_Return_Name(req->handling);
if (hand != NULL) if (hand != NULL)
VSB_printf(pan_vsp, " handling = %s,\n", hand); VSB_printf(pan_vsp, " handling = %s,\n", hand);
......
...@@ -1089,6 +1089,7 @@ cnt_recv(const struct worker *wrk, struct req *req) ...@@ -1089,6 +1089,7 @@ cnt_recv(const struct worker *wrk, struct req *req)
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC); CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
CHECK_OBJ_NOTNULL(req, REQ_MAGIC); CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
CHECK_OBJ_NOTNULL(req->vcl, VCL_CONF_MAGIC); CHECK_OBJ_NOTNULL(req->vcl, VCL_CONF_MAGIC);
AZ(req->objcore);
AZ(req->obj); AZ(req->obj);
AZ(req->objcore); AZ(req->objcore);
AZ(req->busyobj); AZ(req->busyobj);
......
...@@ -394,6 +394,8 @@ SES_GetReq(struct worker *wrk, struct sess *sp) ...@@ -394,6 +394,8 @@ SES_GetReq(struct worker *wrk, struct sess *sp)
req->t_req = NAN; req->t_req = NAN;
req->t_resp = NAN; req->t_resp = NAN;
VTAILQ_INIT(&req->body);
return (req); return (req);
} }
......
...@@ -511,6 +511,16 @@ VRT_ban_string(const struct req *req, const char *str) ...@@ -511,6 +511,16 @@ VRT_ban_string(const struct req *req, const char *str)
VAV_Free(av); VAV_Free(av);
} }
/*--------------------------------------------------------------------
*
*/
int
VRT_CacheReqBody(struct req *req, long long maxsize)
{
return (HTTP1_CacheReqBody(req, maxsize));
}
/*-------------------------------------------------------------------- /*--------------------------------------------------------------------
* "real" purges * "real" purges
*/ */
......
...@@ -177,7 +177,7 @@ stv_alloc(struct stevedore *stv, size_t size) ...@@ -177,7 +177,7 @@ stv_alloc(struct stevedore *stv, size_t size)
if (st != NULL) if (st != NULL)
break; break;
if (size <= cache_param->fetch_chunksize) if (size <= cache_param->fetch_chunksize)
break; break;
size <<= 1; size <<= 1;
...@@ -416,6 +416,13 @@ STV_alloc(struct busyobj *bo, size_t size) ...@@ -416,6 +416,13 @@ STV_alloc(struct busyobj *bo, size_t size)
return (stv_alloc_obj(bo, size)); return (stv_alloc_obj(bo, size));
} }
struct storage *
STV_alloc_transient(size_t size)
{
return (stv_alloc(stv_transient, size));
}
void void
STV_trim(struct storage *st, size_t size, int move_ok) STV_trim(struct storage *st, size_t size, int move_ok)
{ {
......
varnishtest "test caching of req.body"
server s1 {
rxreq
expect req.bodylen == 3
txresp -status 200 -hdr "Foo: BAR" -body "1234"
accept
rxreq
expect req.bodylen == 3
txresp -status 200 -hdr "Foo: Foo" -body "56"
} -start
varnish v1 -vcl+backend {
sub vcl_recv {
C{ VRT_CacheReqBody(req, 1000); }C
return (pass);
}
sub vcl_fetch {
if (beresp.http.foo == "BAR") {
return (restart);
}
}
} -start
client c1 {
txreq -body "FOO"
rxresp
expect resp.http.Foo == "Foo"
expect resp.bodylen == 2
} -run
/*-
* Copyright (c) 2013 Varnish Software AS
* All rights reserved.
*
* Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
/*lint -save -e525 -e539 */
REQ_BODY(INIT)
REQ_BODY(CACHED)
REQ_BODY(DONE)
REQ_BODY(FAIL)
REQ_BODY(NONE)
...@@ -170,6 +170,10 @@ struct vrt_ref { ...@@ -170,6 +170,10 @@ struct vrt_ref {
void VRT_acl_log(struct req *, const char *msg); void VRT_acl_log(struct req *, const char *msg);
/* req related */
int VRT_CacheReqBody(struct req *, long long maxsize);
/* Regexp related */ /* Regexp related */
void VRT_re_init(void **, const char *); void VRT_re_init(void **, const char *);
void VRT_re_fini(void *); void VRT_re_fini(void *);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment