Make subreq() work

parent 99a7d77f
......@@ -25,6 +25,16 @@ AC_ARG_WITH([rst2man],
VARNISH_PREREQ([6.0.0])
VARNISH_VMODS([zipflow])
AC_ARG_VAR([VARNISHSRC], [path to Varnish source])
if test "x$VARNISHSRC" = x; then
AC_MSG_FAILURE([Need VARNISHSRC])
fi
VARNISHSRC_CFLAGS="$VARNISHAPI_CFLAGS \
-I$VARNISHSRC/include \
-I$VARNISHSRC/bin/varnishd \
-I$VARNISHSRC/lib/libvsc"
AC_SUBST([VARNISHSRC_CFLAGS])
AC_CONFIG_FILES([
Makefile
src/Makefile
......
AM_CFLAGS = $(VARNISHAPI_CFLAGS) $(ZLIB_CFLAGS) -I../foreign/zipflow
AM_CFLAGS = $(VARNISHSRC_CFLAGS) $(ZLIB_CFLAGS) -I../foreign/zipflow
# Modules
......@@ -29,7 +29,9 @@ AM_VTC_LOG_FLAGS = \
TESTS = \
vtc/vmod_zipflow.vtc \
vtc/empty.vtc \
vtc/coverage.vtc
vtc/coverage.vtc \
vtc/sub.vtc \
vtc/sub-coalesce.vtc
# Documentation
......
......@@ -3,6 +3,7 @@
-efile(766, vmod_compat.h)
-e717 // do ... while(0)
-e663 // array to pointer
-ecall(835, dlopen)
-e801 // goto
......@@ -19,4 +20,6 @@
// assert constructors not referenced
-esym(528, assert_*)
-emacro(747, WS_TASK_ALLOC_OBJ)
\ No newline at end of file
-emacro(747, WS_TASK_ALLOC_OBJ)
-emacro(527, WRONG)
-emacro(506, VSTAILQ_FOREACH_SAFE)
\ No newline at end of file
/*-
* Copyright 2022 UPLEX Nils Goroll Systemoptimierung
* Copyright 2022,2023 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Nils Goroll <nils.goroll@uplex.de>
......@@ -31,10 +31,12 @@
#include <string.h> // memset() for INIT_OBJ()
#include <stdlib.h> // strtoul()
#include <cache/cache.h>
#include <cache/cache_varnishd.h>
#include <cache/cache_transport.h>
#include <cache/cache_filter.h>
#include <vcl.h>
#include <vtim.h>
#include <VSC_main.h>
#include "vcc_zipflow_if.h"
......@@ -47,16 +49,17 @@ assert_zlib(void)
assert(Z_DEFAULT_COMPRESSION == -1); //lint !e506 const bool
}
// https://github.com/varnishcache/varnish-cache/pull/3815
#ifndef WS_TASK_ALLOC_OBJ
#define WS_TASK_ALLOC_OBJ(ctx, ptr, magic) do { \
ptr = WS_Alloc((ctx)->ws, sizeof *(ptr)); \
#define WS_TOP_ALLOC_OBJ(ctx, ptr, magic) do { \
AN((ctx)->req); \
AN((ctx)->req->top); \
AN((ctx)->req->top->topreq); \
ptr = WS_Alloc((ctx)->req->top->topreq->ws, \
(int)sizeof *(ptr)); \
if ((ptr) == NULL) \
VRT_fail(ctx, "Out of workspace for " #magic); \
else \
INIT_OBJ(ptr, magic); \
} while(0)
#endif
static char default_level = Z_DEFAULT_COMPRESSION;
......@@ -68,13 +71,16 @@ struct zipflow_request {
unsigned magic;
#define ZIPFLOW_REQUEST_MAGIC 0xaa175160
unsigned bundle:1;
unsigned woken:1; // reembark
char level;
VCL_STRING url;
VCL_STRING host;
VCL_STRING uri;
VCL_STRING name;
unsigned mode;
VCL_TIME atime, mtime;
VSTAILQ_ENTRY(zipflow_request) list;
struct zipflow_top *top;
pthread_cond_t *cond;
};
VSTAILQ_HEAD(zipflow_head, zipflow_request);
......@@ -84,6 +90,7 @@ struct zipflow_top {
#define ZIPFLOW_TOP_MAGIC 0x5743145e
struct zipflow_head head;
ZIP *zip;
struct req *req;
};
static struct zipflow_top *
......@@ -103,7 +110,7 @@ get_zipflow_top(VRT_CTX)
return (zft);
}
WS_TASK_ALLOC_OBJ(ctx, zft, ZIPFLOW_TOP_MAGIC);
WS_TOP_ALLOC_OBJ(ctx, zft, ZIPFLOW_TOP_MAGIC);
if (zft == NULL)
return (NULL);
......@@ -125,12 +132,26 @@ static const struct vdp vdp_zipflow[1] = {{
.priv1 = NULL
}};
static int vdp_zipsub_init(VRT_CTX, struct vdp_ctx *vdc, void **priv,
struct objcore *oc);
static int vdp_zipsub_fini(struct vdp_ctx *vdc, void **priv);
static int vdp_zipsub_bytes(struct vdp_ctx *vdc, enum vdp_action act,
void **priv, const void *ptr, ssize_t len);
static const struct vdp vdp_zipsub[1] = {{
.name = "zipsub",
.init = vdp_zipsub_init,
.bytes = vdp_zipsub_bytes,
.fini = vdp_zipsub_fini,
.priv1 = NULL
}};
static struct zipflow_request *
new_zipflow_request(VRT_CTX, struct zipflow_top *zft)
{
struct zipflow_request *zfr;
WS_TASK_ALLOC_OBJ(ctx, zfr, ZIPFLOW_REQUEST_MAGIC);
WS_TOP_ALLOC_OBJ(ctx, zfr, ZIPFLOW_REQUEST_MAGIC);
if (zfr == NULL)
return (NULL);
......@@ -182,27 +203,39 @@ get_zipflow_request(VRT_CTX)
*/
VCL_VOID
vmod_subreq(VRT_CTX, VCL_STRING url)
vmod_subreq(VRT_CTX, VCL_STRING uri, VCL_STRING host)
{
struct zipflow_request *zfr;
struct zipflow_top *zft;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(ctx->method & (VCL_MET_TASK_C));
AN(ctx->method & VCL_MET_TASK_C);
zft = get_zipflow_top(ctx);
zfr = new_zipflow_request(ctx, zft);
if (uri == NULL || *uri != '/') {
VRT_fail(ctx, "subreq uri argument needs to start with /");
return;
}
zfr = get_zipflow_request(ctx);
if (zfr == NULL)
return;
zfr->url = url;
zfr = new_zipflow_request(ctx, zfr->top);
if (zfr == NULL)
return;
zfr->host = host;
zfr->uri = uri;
}
VCL_BOOL
vmod_is_subreq(VRT_CTX)
{
struct zipflow_request *zfr;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
// XXX TODO
return (1);
zfr = get_zipflow_request(ctx);
return (zfr != NULL && zfr->uri != NULL);
}
VCL_VOID
......@@ -410,23 +443,64 @@ vdp_zipflow_log(void *vsl, char *msg)
}
static int
vdp_zipflow_init(VRT_CTX, struct vdp_ctx *vdc, void **priv, struct objcore *oc)
vdp_zipsub_init(VRT_CTX, struct vdp_ctx *vdc, void **priv, struct objcore *oc)
{
struct zipflow_request *zfr = get_zipflow_request(ctx);
struct zipflow_request *zfr;
struct zipflow_top *zft;
struct req *req;
(void)oc;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
(void) vdc;
AN(vdc);
AN(priv);
(void) priv;
CAST_OBJ_NOTNULL(zfr, *priv, ZIPFLOW_REQUEST_MAGIC);
(void) oc;
zft = zfr->top;
CHECK_OBJ_NOTNULL(zft, ZIPFLOW_TOP_MAGIC);
assert(zfr == VSTAILQ_FIRST(&zft->head));
VSTAILQ_REMOVE_HEAD(&zft->head, list);
AN(zft->zip);
if (zfr->bundle) {
fill_meta(ctx, zfr);
AZ(zip_meta(zft->zip, zfr->name, 3, zfr->mode,
(uint32_t)zfr->atime, (uint32_t)zfr->mtime));
}
req = vdc->req;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
RFC2616_Weaken_Etag(req->resp);
if (req->resp_len != 0)
req->resp_len = -1;
return (0);
}
static int
vdp_zipflow_init(VRT_CTX, struct vdp_ctx *vdc, void **priv, struct objcore *oc)
{
struct zipflow_request *zfr = get_zipflow_request(ctx);
struct zipflow_top *zft;
if (zfr == NULL)
return (1);
zft = zfr->top;
CHECK_OBJ_NOTNULL(zft, ZIPFLOW_TOP_MAGIC);
CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC);
CHECK_OBJ_NOTNULL(vdc->req, REQ_MAGIC);
assert(zfr == VSTAILQ_FIRST(&zft->head));
VSTAILQ_REMOVE_HEAD(&zft->head, list);
if (zft->req) {
VSLb(vdc->vsl, SLT_Error, "zipflow: can't be nested");
return (1);
}
AZ(zft->req);
zft->req = vdc->req;
AZ(zft->zip);
zft->zip = zip_pipe(vdc, vdp_zipflow_put, zfr->level);
if (zft->zip == NULL) {
......@@ -434,22 +508,28 @@ vdp_zipflow_init(VRT_CTX, struct vdp_ctx *vdc, void **priv, struct objcore *oc)
return (1);
}
AZ(zip_log(zft->zip, vdc->vsl, vdp_zipflow_log));
if (zfr->bundle) {
fill_meta(ctx, zfr);
AZ(zip_meta(zft->zip, zfr->name, 3, zfr->mode,
(uint32_t)zfr->atime, (uint32_t)zfr->mtime));
}
AN(priv);
AZ(*priv);
*priv = zfr;
req = vdc->req;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
RFC2616_Weaken_Etag(req->resp);
if (req->resp_len != 0)
req->resp_len = -1;
return (vdp_zipsub_init(ctx, vdc, priv, oc));
}
static int
vdp_zipsub_fini(struct vdp_ctx *vdc, void **priv)
{
struct zipflow_request *zfr;
(void) vdc;
AN(priv);
zfr = *priv;
*priv = NULL;
if (zfr != NULL) {
CHECK_OBJ(zfr, ZIPFLOW_REQUEST_MAGIC);
memset(zfr, 0, sizeof *zfr);
}
return (0);
}
......@@ -460,7 +540,7 @@ vdp_zipflow_fini(struct vdp_ctx *vdc, void **priv)
struct zipflow_top *zft;
int r;
(void)vdc;
(void) vdc;
AN(priv);
zfr = *priv;
*priv = NULL;
......@@ -477,22 +557,22 @@ vdp_zipflow_fini(struct vdp_ctx *vdc, void **priv)
}
static int
vdp_zipflow_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv,
vdp_zipsub_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv,
const void *ptr, ssize_t len)
{
struct zipflow_request *zfr;
struct zipflow_top *zft;
int r;
(void)vdc;
(void) vdc;
(void) priv;
AN(priv);
CAST_OBJ_NOTNULL(zfr, *priv, ZIPFLOW_REQUEST_MAGIC);
zft = zfr->top;
CHECK_OBJ_NOTNULL(zft, ZIPFLOW_TOP_MAGIC);
if (zfr->bundle == 0) {
(void) zip_close(zft->zip);
return (0);
}
if (zfr->bundle == 0)
return (1);
assert(len >= 0);
r = zip_data(zft->zip, ptr, (size_t)len, act == VDP_END ? 1 : 0);
if (r) {
......@@ -500,13 +580,254 @@ vdp_zipflow_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv,
return (-1);
}
if (act != VDP_END)
return (0);
return (0);
}
static void
zfr_include(struct req *preq, struct zipflow_request *zfr);
*priv = NULL;
r = zip_close(zft->zip);
if (r)
VSLb(vdc->vsl, SLT_Error, "zip_close returned %d", r);
static int
vdp_zipflow_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv,
const void *ptr, ssize_t len)
{
struct zipflow_request *zfr;
struct zipflow_top *zft;
int r, c;
r = vdp_zipsub_bytes(vdc, act, priv, ptr, len);
if (r == 0 && act != VDP_END)
return (r);
TAKE_OBJ_NOTNULL(zfr, priv, ZIPFLOW_REQUEST_MAGIC);
zft = zfr->top;
memset(zfr, 0, sizeof *zfr);
CHECK_OBJ_NOTNULL(zft, ZIPFLOW_TOP_MAGIC);
if (r == 0) {
while ((zfr = VSTAILQ_FIRST(&zft->head)) != NULL)
zfr_include(zft->req, zfr);
}
c = zip_close(zft->zip);
if (c)
VSLb(vdc->vsl, SLT_Error, "zip_close returned %d", r);
if (r == 0)
r = c;
return (r);
}
static void v_matchproto_(vtr_reembark_f)
zfr_reembark(struct worker *wrk, struct req *req)
{
struct zipflow_request *zfr;
(void)wrk;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
CAST_OBJ_NOTNULL(zfr, req->transport_priv, ZIPFLOW_REQUEST_MAGIC);
AN(zfr->cond);
AN(req->sp);
Lck_Lock(&req->sp->mtx);
zfr->woken = 1;
PTOK(pthread_cond_signal(zfr->cond));
Lck_Unlock(&req->sp->mtx);
}
static int v_matchproto_(vtr_minimal_response_f)
zfr_minimal_response(struct req *req, uint16_t status)
{
(void)req;
(void)status;
WRONG("zfr_minimal_response should not be called");
return (0);
}
static void v_matchproto_(vtr_deliver_f)
zfr_deliver(struct req *req, struct boc *boc, int wantbody);
//lint -e{785} too few initializers
static const struct transport ZIPFLOW_transport = {
.magic = TRANSPORT_MAGIC,
.name = "ZIPFLOW",
.deliver = zfr_deliver,
.reembark = zfr_reembark,
.minimal_response = zfr_minimal_response
};
/*--------------------------------------------------------------------
* basically varnish-cache ESI
*/
static void
zfr_include(struct req *preq, struct zipflow_request *zfr)
{
struct worker *wrk;
struct sess *sp;
struct req *req;
struct vmod_priv *task_priv;
struct vrt_ctx ctx[1];
enum req_fsm_nxt s;
CHECK_OBJ_NOTNULL(preq, REQ_MAGIC);
CHECK_OBJ_NOTNULL(preq->top, REQTOP_MAGIC);
sp = preq->sp;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(zfr, ZIPFLOW_REQUEST_MAGIC);
wrk = preq->wrk;
if (preq->esi_level >= cache_param->max_esi_depth) {
VSLb(preq->vsl, SLT_VCL_Error,
"ESI depth limit reached (param max_esi_depth = %u)",
cache_param->max_esi_depth);
return;
}
req = Req_New(sp);
AN(req);
THR_SetRequest(req);
assert(IS_NO_VXID(req->vsl->wid));
req->vsl->wid = VXID_Get(wrk, VSL_CLIENTMARKER);
wrk->stats->esi_req++;
req->esi_level = preq->esi_level + 1;
VSLb(req->vsl, SLT_Begin, "req %ju esi %u",
(uintmax_t)VXID(preq->vsl->wid), req->esi_level);
VSLb(preq->vsl, SLT_Link, "req %ju esi %u",
(uintmax_t)VXID(req->vsl->wid), req->esi_level);
VSLb_ts_req(req, "Start", W_TIM_real(wrk));
memset(req->top, 0, sizeof *req->top);
req->top = preq->top;
HTTP_Setup(req->http, req->ws, req->vsl, SLT_ReqMethod);
HTTP_Dup(req->http, preq->http0);
http_SetH(req->http, HTTP_HDR_URL, zfr->uri);
if (zfr->host != NULL && *zfr->host != '\0') {
http_Unset(req->http, H_Host);
http_SetHeader(req->http, zfr->host);
}
http_ForceField(req->http, HTTP_HDR_METHOD, "GET");
http_ForceField(req->http, HTTP_HDR_PROTO, "HTTP/1.1");
/* Don't allow conditionals, we can't use a 304 */
http_Unset(req->http, H_If_Modified_Since);
http_Unset(req->http, H_If_None_Match);
/* Don't allow Range */
http_Unset(req->http, H_Range);
// XXX handle gzip when zipflow supports it
http_Unset(req->http, H_Accept_Encoding);
/* Client content already taken care of */
http_Unset(req->http, H_Content_Length);
http_Unset(req->http, H_Transfer_Encoding);
req->req_body_status = BS_NONE;
AZ(req->vcl);
AN(req->top);
if (req->top->vcl0)
req->vcl = req->top->vcl0;
else
req->vcl = preq->vcl;
VCL_Ref(req->vcl);
assert(req->req_step == R_STP_TRANSPORT);
req->t_req = preq->t_req;
req->transport = &ZIPFLOW_transport;
req->transport_priv = zfr;
CNT_Embark(wrk, req);
VCL_TaskEnter(req->privs);
INIT_OBJ(ctx, VRT_CTX_MAGIC);
VCL_Req2Ctx(ctx, req);
task_priv = VRT_priv_task(ctx, zipflow_request_priv);
AN(task_priv);
AZ(task_priv->priv);
task_priv->priv = zfr;
AZ(zfr->cond);
zfr->cond = &wrk->cond;
//lint -e{716} while(1)
while (1) {
CNT_Embark(wrk, req);
zfr->woken = 0;
s = CNT_Request(req);
if (s == REQ_FSM_DONE)
break;
DSL(DBG_WAITINGLIST, req->vsl->wid,
"waiting for zipflow (%d)", (int)s);
assert(s == REQ_FSM_DISEMBARK);
AN(zfr->cond);
Lck_Lock(&sp->mtx);
if (!zfr->woken)
(void)Lck_CondWait(zfr->cond, &sp->mtx);
Lck_Unlock(&sp->mtx);
AZ(req->wrk);
}
VCL_Rel(&req->vcl);
req->wrk = NULL;
THR_SetRequest(preq);
Req_Cleanup(sp, wrk, req);
Req_Release(req);
}
static void v_matchproto_(vtr_deliver_f)
zfr_deliver(struct req *req, struct boc *boc, int wantbody)
{
int i;
const char *p;
uint16_t status;
struct vrt_ctx ctx[1];
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
CHECK_OBJ_ORNULL(boc, BOC_MAGIC);
CHECK_OBJ_NOTNULL(req->objcore, OBJCORE_MAGIC);
if (wantbody == 0)
return;
status = req->resp->status % 1000;
// XXX should we trigger an error like ESI does?
if (status != 200)
return;
if (boc == NULL && ObjGetLen(req->wrk, req->objcore) == 0)
return;
AZ(http_GetHdr(req->resp, H_Content_Encoding, &p));
INIT_OBJ(ctx, VRT_CTX_MAGIC);
VCL_Req2Ctx(ctx, req);
i = VDP_Push(ctx, req->vdc, req->ws, vdp_zipsub, req->transport_priv);
if (i == 0) {
i = VDP_DeliverObj(req->vdc, req->objcore);
} else {
VSLb(req->vsl, SLT_Error, "Failure to push ZIP processors");
req->doclose = SC_OVERLOAD;
}
if (i && req->doclose == SC_NULL)
req->doclose = SC_REM_CLOSE;
req->acct.resp_bodybytes += VDP_Close(req->vdc, req->objcore, boc);
#if 0
if (i && !ecx->incl_cont) {
req->top->topreq->vdc->retval = -1;
req->top->topreq->doclose = req->doclose;
}
#endif
}
......@@ -37,17 +37,25 @@ Example
set resp.filters += " zipflow";
}
$Function VOID subreq(STRING url)
$Function VOID subreq(STRING url, STRING host=0)
$Restrict client
Issue a sub requets to *url* when the VDP runs, similar to ESI
Issue a sub requets to *host*/*uri* when the VDP runs, similar to ESI
processing.
This function can be called any number of times. The sub request can
be identified using `zipflow.is_subreq()`_. In the sub request,
`zipflow.set_level()`_ and `zipflow.meta()`_ should be used to control
how zipflow handles the body.
If *host* is omitted (default), it is taken from the parent request.
This function can be called any number of times to add multiple
files, it can eben be called from a sub request, which is to say that
more files can be added while requests for files are processed.
The sub request can be identified using `zipflow.is_subreq()`_. In the
sub request, `zipflow.set_level()`_ and `zipflow.meta()`_ should be
used to control how zipflow handles the body.
Only sub requests with reponse status 200 will be included in the
resulting zip file.
$Function BOOL is_subreq()
......
varnishtest "vmod-zipflow sub request / race & waitinglist"
feature cmd "type curl && type unzip && echo 'foo' grep -P '^foo'"
server s1 {
rxreq
expect req.url == "/file1"
txresp -gziplevel 1 -gziplen 10240
rxreq
expect req.url == "/file2"
txresp -bodylen 20480
} -start
varnish v1 -vcl+backend {
import zipflow;
sub vcl_recv {
zipflow.set_level(0);
if (zipflow.is_subreq()) {
return (hash);
}
return (synth(200));
}
sub vcl_synth {
synthetic("top zip");
zipflow.meta(name="top");
zipflow.subreq("/file1");
zipflow.subreq("/file2");
set resp.filters += " zipflow";
return (deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.bodylen == 31102
} -start
client c2 {
txreq
rxresp
expect resp.status == 200
expect resp.bodylen == 31102
} -start
client c3 {
txreq
rxresp
expect resp.status == 200
expect resp.bodylen == 31102
} -start
client c4 {
txreq
rxresp
expect resp.status == 200
expect resp.bodylen == 31102
} -start
client c1 -wait
client c2 -wait
client c3 -wait
client c4 -wait
\ No newline at end of file
varnishtest "vmod-zipflow sub request"
feature cmd "type curl && type unzip && echo 'foo' grep -P '^foo'"
varnish v1 -vcl {
import zipflow;
import std;
backend proforma none;
sub vcl_recv {
return (synth(200));
}
sub synth_top {
synthetic("top zip");
zipflow.meta(name="top");
zipflow.subreq("/file1");
zipflow.subreq("/file2");
set resp.filters += " zipflow";
}
sub synth_sub {
synthetic("sub " + req.url);
}
sub vcl_synth {
if (zipflow.is_subreq()) {
call synth_sub;
if (req.url == "/file1") {
zipflow.meta(name="file1.changed");
zipflow.subreq("/file3");
} else
if (req.url == "/file3") {
zipflow.subreq("/file4");
}
} else {
call synth_top;
}
return (deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
} -run
# all default
shell "curl -so t.zip -H 'Host: ${v1_addr}' http://${v1_addr}:${v1_port}/ && unzip -Z t.zip"
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