blob: 54f550565eed883fd72f0771325ff71e661713ea [file] [log] [blame] [raw]
/*
* Copyright (C) Xiaozhe Wang (chaoslawful)
* Copyright (C) Yichun Zhang (agentzh)
*/
#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_lua_subrequest.h"
#include "ngx_http_lua_util.h"
#include "ngx_http_lua_ctx.h"
#include "ngx_http_lua_contentby.h"
#include "ngx_http_lua_headers_in.h"
#if defined(NGX_DTRACE) && NGX_DTRACE
#include "ngx_http_probe.h"
#endif
#define NGX_HTTP_LUA_SHARE_ALL_VARS 0x01
#define NGX_HTTP_LUA_COPY_ALL_VARS 0x02
#define ngx_http_lua_method_name(m) { sizeof(m) - 1, (u_char *) m " " }
ngx_str_t ngx_http_lua_get_method = ngx_http_lua_method_name("GET");
ngx_str_t ngx_http_lua_put_method = ngx_http_lua_method_name("PUT");
ngx_str_t ngx_http_lua_post_method = ngx_http_lua_method_name("POST");
ngx_str_t ngx_http_lua_head_method = ngx_http_lua_method_name("HEAD");
ngx_str_t ngx_http_lua_delete_method =
ngx_http_lua_method_name("DELETE");
ngx_str_t ngx_http_lua_options_method =
ngx_http_lua_method_name("OPTIONS");
ngx_str_t ngx_http_lua_copy_method = ngx_http_lua_method_name("COPY");
ngx_str_t ngx_http_lua_move_method = ngx_http_lua_method_name("MOVE");
ngx_str_t ngx_http_lua_lock_method = ngx_http_lua_method_name("LOCK");
ngx_str_t ngx_http_lua_mkcol_method =
ngx_http_lua_method_name("MKCOL");
ngx_str_t ngx_http_lua_propfind_method =
ngx_http_lua_method_name("PROPFIND");
ngx_str_t ngx_http_lua_proppatch_method =
ngx_http_lua_method_name("PROPPATCH");
ngx_str_t ngx_http_lua_unlock_method =
ngx_http_lua_method_name("UNLOCK");
ngx_str_t ngx_http_lua_patch_method =
ngx_http_lua_method_name("PATCH");
ngx_str_t ngx_http_lua_trace_method =
ngx_http_lua_method_name("TRACE");
static ngx_str_t ngx_http_lua_content_length_header_key =
ngx_string("Content-Length");
static ngx_int_t ngx_http_lua_set_content_length_header(ngx_http_request_t *r,
off_t len);
static ngx_int_t ngx_http_lua_adjust_subrequest(ngx_http_request_t *sr,
ngx_uint_t method, int forward_body,
ngx_http_request_body_t *body, unsigned vars_action,
ngx_array_t *extra_vars);
static int ngx_http_lua_ngx_location_capture(lua_State *L);
static int ngx_http_lua_ngx_location_capture_multi(lua_State *L);
static void ngx_http_lua_process_vars_option(ngx_http_request_t *r,
lua_State *L, int table, ngx_array_t **varsp);
static ngx_int_t ngx_http_lua_subrequest_add_extra_vars(ngx_http_request_t *r,
ngx_array_t *extra_vars);
static ngx_int_t ngx_http_lua_subrequest(ngx_http_request_t *r,
ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
ngx_http_post_subrequest_t *ps, ngx_uint_t flags);
static ngx_int_t ngx_http_lua_subrequest_resume(ngx_http_request_t *r);
static void ngx_http_lua_handle_subreq_responses(ngx_http_request_t *r,
ngx_http_lua_ctx_t *ctx);
static void ngx_http_lua_cancel_subreq(ngx_http_request_t *r);
static ngx_int_t ngx_http_post_request_to_head(ngx_http_request_t *r);
static ngx_int_t ngx_http_lua_copy_in_file_request_body(ngx_http_request_t *r);
static ngx_int_t ngx_http_lua_copy_request_headers(ngx_http_request_t *sr,
ngx_http_request_t *r);
/* ngx.location.capture is just a thin wrapper around
* ngx.location.capture_multi */
static int
ngx_http_lua_ngx_location_capture(lua_State *L)
{
int n;
n = lua_gettop(L);
if (n != 1 && n != 2) {
return luaL_error(L, "expecting one or two arguments");
}
lua_createtable(L, n, 0); /* uri opts? table */
lua_insert(L, 1); /* table uri opts? */
if (n == 1) { /* table uri */
lua_rawseti(L, 1, 1); /* table */
} else { /* table uri opts */
lua_rawseti(L, 1, 2); /* table uri */
lua_rawseti(L, 1, 1); /* table */
}
lua_createtable(L, 1, 0); /* table table' */
lua_insert(L, 1); /* table' table */
lua_rawseti(L, 1, 1); /* table' */
return ngx_http_lua_ngx_location_capture_multi(L);
}
static int
ngx_http_lua_ngx_location_capture_multi(lua_State *L)
{
ngx_http_request_t *r;
ngx_http_request_t *sr; /* subrequest object */
ngx_http_post_subrequest_t *psr;
ngx_http_lua_ctx_t *sr_ctx;
ngx_http_lua_ctx_t *ctx;
ngx_array_t *extra_vars;
ngx_str_t uri;
ngx_str_t args;
ngx_str_t extra_args;
ngx_uint_t flags;
u_char *p;
u_char *q;
size_t len;
size_t nargs;
int rc;
int n;
int always_forward_body = 0;
ngx_uint_t method;
ngx_http_request_body_t *body;
int type;
ngx_buf_t *b;
unsigned vars_action;
ngx_uint_t nsubreqs;
ngx_uint_t index;
size_t sr_statuses_len;
size_t sr_headers_len;
size_t sr_bodies_len;
size_t sr_flags_len;
unsigned custom_ctx;
ngx_http_lua_co_ctx_t *coctx;
ngx_http_lua_post_subrequest_data_t *psr_data;
n = lua_gettop(L);
if (n != 1) {
return luaL_error(L, "only one argument is expected, but got %d", n);
}
luaL_checktype(L, 1, LUA_TTABLE);
nsubreqs = lua_objlen(L, 1);
if (nsubreqs == 0) {
return luaL_error(L, "at least one subrequest should be specified");
}
r = ngx_http_lua_get_req(L);
if (r == NULL) {
return luaL_error(L, "no request object found");
}
#if (NGX_HTTP_SPDY)
if (r->spdy_stream) {
return luaL_error(L, "spdy not supported yet");
}
#endif
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
if (ctx == NULL) {
return luaL_error(L, "no ctx found");
}
ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE
| NGX_HTTP_LUA_CONTEXT_ACCESS
| NGX_HTTP_LUA_CONTEXT_CONTENT);
coctx = ctx->cur_co_ctx;
if (coctx == NULL) {
return luaL_error(L, "no co ctx found");
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua location capture, uri:\"%V\" c:%ud", &r->uri,
r->main->count);
sr_statuses_len = nsubreqs * sizeof(ngx_int_t);
sr_headers_len = nsubreqs * sizeof(ngx_http_headers_out_t *);
sr_bodies_len = nsubreqs * sizeof(ngx_str_t);
sr_flags_len = nsubreqs * sizeof(uint8_t);
p = ngx_pcalloc(r->pool, sr_statuses_len + sr_headers_len +
sr_bodies_len + sr_flags_len);
if (p == NULL) {
return luaL_error(L, "out of memory");
}
coctx->sr_statuses = (void *) p;
p += sr_statuses_len;
coctx->sr_headers = (void *) p;
p += sr_headers_len;
coctx->sr_bodies = (void *) p;
p += sr_bodies_len;
coctx->sr_flags = (void *) p;
coctx->nsubreqs = nsubreqs;
coctx->pending_subreqs = 0;
extra_vars = NULL;
for (index = 0; index < nsubreqs; index++) {
coctx->pending_subreqs++;
lua_rawgeti(L, 1, index + 1);
if (lua_isnil(L, -1)) {
return luaL_error(L, "only array-like tables are allowed");
}
dd("queries query: top %d", lua_gettop(L));
if (lua_type(L, -1) != LUA_TTABLE) {
return luaL_error(L, "the query argument %d is not a table, "
"but a %s",
index, lua_typename(L, lua_type(L, -1)));
}
nargs = lua_objlen(L, -1);
if (nargs != 1 && nargs != 2) {
return luaL_error(L, "query argument %d expecting one or "
"two arguments", index);
}
lua_rawgeti(L, 2, 1); /* queries query uri */
dd("queries query uri: %d", lua_gettop(L));
dd("first arg in first query: %s", lua_typename(L, lua_type(L, -1)));
body = NULL;
extra_args.data = NULL;
extra_args.len = 0;
if (extra_vars != NULL) {
/* flush out existing elements in the array */
extra_vars->nelts = 0;
}
vars_action = 0;
custom_ctx = 0;
if (nargs == 2) {
/* check out the options table */
lua_rawgeti(L, 2, 2); /* queries query uri opts */
dd("queries query uri opts: %d", lua_gettop(L));
if (lua_type(L, 4) != LUA_TTABLE) {
return luaL_error(L, "expecting table as the 2nd argument for "
"subrequest %d, but got %s", index,
luaL_typename(L, 4));
}
dd("queries query uri opts: %d", lua_gettop(L));
/* check the args option */
lua_getfield(L, 4, "args");
type = lua_type(L, -1);
switch (type) {
case LUA_TTABLE:
ngx_http_lua_process_args_option(r, L, -1, &extra_args);
break;
case LUA_TNIL:
/* do nothing */
break;
case LUA_TNUMBER:
case LUA_TSTRING:
extra_args.data = (u_char *) lua_tolstring(L, -1, &len);
extra_args.len = len;
break;
default:
return luaL_error(L, "Bad args option value");
}
lua_pop(L, 1);
dd("queries query uri opts: %d", lua_gettop(L));
/* check the vars option */
lua_getfield(L, 4, "vars");
switch (lua_type(L, -1)) {
case LUA_TTABLE:
ngx_http_lua_process_vars_option(r, L, -1, &extra_vars);
dd("post process vars top: %d", lua_gettop(L));
break;
case LUA_TNIL:
/* do nothing */
break;
default:
return luaL_error(L, "Bad vars option value");
}
lua_pop(L, 1);
dd("queries query uri opts: %d", lua_gettop(L));
/* check the share_all_vars option */
lua_getfield(L, 4, "share_all_vars");
switch (lua_type(L, -1)) {
case LUA_TNIL:
/* do nothing */
break;
case LUA_TBOOLEAN:
if (lua_toboolean(L, -1)) {
vars_action |= NGX_HTTP_LUA_SHARE_ALL_VARS;
}
break;
default:
return luaL_error(L, "Bad share_all_vars option value");
}
lua_pop(L, 1);
dd("queries query uri opts: %d", lua_gettop(L));
/* check the copy_all_vars option */
lua_getfield(L, 4, "copy_all_vars");
switch (lua_type(L, -1)) {
case LUA_TNIL:
/* do nothing */
break;
case LUA_TBOOLEAN:
if (lua_toboolean(L, -1)) {
vars_action |= NGX_HTTP_LUA_COPY_ALL_VARS;
}
break;
default:
return luaL_error(L, "Bad copy_all_vars option value");
}
lua_pop(L, 1);
dd("queries query uri opts: %d", lua_gettop(L));
/* check the "forward_body" option */
lua_getfield(L, 4, "always_forward_body");
always_forward_body = lua_toboolean(L, -1);
lua_pop(L, 1);
dd("always foward body: %d", always_forward_body);
/* check the "method" option */
lua_getfield(L, 4, "method");
type = lua_type(L, -1);
if (type == LUA_TNIL) {
method = NGX_HTTP_GET;
} else {
if (type != LUA_TNUMBER) {
return luaL_error(L, "Bad http request method");
}
method = (ngx_uint_t) lua_tonumber(L, -1);
}
lua_pop(L, 1);
dd("queries query uri opts: %d", lua_gettop(L));
/* check the "ctx" option */
lua_getfield(L, 4, "ctx");
type = lua_type(L, -1);
if (type != LUA_TNIL) {
if (type != LUA_TTABLE) {
return luaL_error(L, "Bad ctx option value type %s, "
"expected a Lua table",
lua_typename(L, type));
}
custom_ctx = 1;
} else {
lua_pop(L, 1);
}
dd("queries query uri opts ctx?: %d", lua_gettop(L));
/* check the "body" option */
lua_getfield(L, 4, "body");
type = lua_type(L, -1);
if (type != LUA_TNIL) {
if (type != LUA_TSTRING && type != LUA_TNUMBER) {
return luaL_error(L, "Bad http request body");
}
body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
if (body == NULL) {
return luaL_error(L, "out of memory");
}
q = (u_char *) lua_tolstring(L, -1, &len);
dd("request body: [%.*s]", (int) len, q);
if (len) {
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return luaL_error(L, "out of memory");
}
b->last = ngx_copy(b->last, q, len);
body->bufs = ngx_alloc_chain_link(r->pool);
if (body->bufs == NULL) {
return luaL_error(L, "out of memory");
}
body->bufs->buf = b;
body->bufs->next = NULL;
body->buf = b;
}
}
lua_pop(L, 1); /* pop the body */
/* stack: queries query uri opts ctx? */
lua_remove(L, 4);
/* stack: queries query uri ctx? */
dd("queries query uri ctx?: %d", lua_gettop(L));
} else {
method = NGX_HTTP_GET;
}
/* stack: queries query uri ctx? */
p = (u_char *) luaL_checklstring(L, 3, &len);
uri.data = ngx_palloc(r->pool, len);
if (uri.data == NULL) {
return luaL_error(L, "memory allocation error");
}
ngx_memcpy(uri.data, p, len);
uri.len = len;
args.data = NULL;
args.len = 0;
flags = 0;
rc = ngx_http_parse_unsafe_uri(r, &uri, &args, &flags);
if (rc != NGX_OK) {
dd("rc = %d", (int) rc);
return luaL_error(L, "unsafe uri in argument #1: %s", p);
}
if (args.len == 0) {
if (extra_args.len) {
p = ngx_palloc(r->pool, extra_args.len);
if (p == NULL) {
return luaL_error(L, "out of memory");
}
ngx_memcpy(p, extra_args.data, extra_args.len);
args.data = p;
args.len = extra_args.len;
}
} else if (extra_args.len) {
/* concatenate the two parts of args together */
len = args.len + (sizeof("&") - 1) + extra_args.len;
p = ngx_palloc(r->pool, len);
if (p == NULL) {
return luaL_error(L, "out of memory");
}
q = ngx_copy(p, args.data, args.len);
*q++ = '&';
ngx_memcpy(q, extra_args.data, extra_args.len);
args.data = p;
args.len = len;
}
p = ngx_pnalloc(r->pool, sizeof(ngx_http_post_subrequest_t)
+ sizeof(ngx_http_lua_ctx_t)
+ sizeof(ngx_http_lua_post_subrequest_data_t));
if (p == NULL) {
return luaL_error(L, "out of memory");
}
psr = (ngx_http_post_subrequest_t *) p;
p += sizeof(ngx_http_post_subrequest_t);
sr_ctx = (ngx_http_lua_ctx_t *) p;
p += sizeof(ngx_http_lua_ctx_t);
psr_data = (ngx_http_lua_post_subrequest_data_t *) p;
ngx_memzero(sr_ctx, sizeof(ngx_http_lua_ctx_t));
/* set by ngx_memzero:
* sr_ctx->run_post_subrequest = 0
* sr_ctx->free = NULL
* sr_ctx->body = NULL
*/
psr_data->ctx = sr_ctx;
psr_data->pr_co_ctx = coctx;
psr->handler = ngx_http_lua_post_subrequest;
psr->data = psr_data;
rc = ngx_http_lua_subrequest(r, &uri, &args, &sr, psr, 0);
if (rc != NGX_OK) {
return luaL_error(L, "failed to issue subrequest: %d", (int) rc);
}
ngx_http_lua_init_ctx(sr, sr_ctx);
sr_ctx->capture = 1;
sr_ctx->index = index;
sr_ctx->last_body = &sr_ctx->body;
sr_ctx->vm_state = ctx->vm_state;
ngx_http_set_ctx(sr, sr_ctx, ngx_http_lua_module);
rc = ngx_http_lua_adjust_subrequest(sr, method, always_forward_body,
body, vars_action, extra_vars);
if (rc != NGX_OK) {
ngx_http_lua_cancel_subreq(sr);
return luaL_error(L, "failed to adjust the subrequest: %d",
(int) rc);
}
dd("queries query uri opts ctx? %d", lua_gettop(L));
/* stack: queries query uri ctx? */
if (custom_ctx) {
ngx_http_lua_ngx_set_ctx_helper(L, sr, sr_ctx, -1);
lua_pop(L, 3);
} else {
lua_pop(L, 2);
}
/* stack: queries */
}
if (extra_vars) {
ngx_array_destroy(extra_vars);
}
ctx->no_abort = 1;
return lua_yield(L, 0);
}
static ngx_int_t
ngx_http_lua_adjust_subrequest(ngx_http_request_t *sr, ngx_uint_t method,
int always_forward_body, ngx_http_request_body_t *body,
unsigned vars_action, ngx_array_t *extra_vars)
{
ngx_http_request_t *r;
ngx_int_t rc;
ngx_http_core_main_conf_t *cmcf;
size_t size;
r = sr->parent;
sr->header_in = r->header_in;
if (body) {
sr->request_body = body;
rc = ngx_http_lua_set_content_length_header(sr,
body->buf
? ngx_buf_size(body->buf)
: 0);
if (rc != NGX_OK) {
return NGX_ERROR;
}
} else if (!always_forward_body
&& method != NGX_HTTP_PUT
&& method != NGX_HTTP_POST
&& r->headers_in.content_length_n > 0)
{
rc = ngx_http_lua_set_content_length_header(sr, 0);
if (rc != NGX_OK) {
return NGX_ERROR;
}
#if 1
sr->request_body = NULL;
#endif
} else {
if (ngx_http_lua_copy_request_headers(sr, r) != NGX_OK) {
return NGX_ERROR;
}
if (sr->request_body) {
/* deep-copy the request body */
if (sr->request_body->temp_file) {
if (ngx_http_lua_copy_in_file_request_body(sr) != NGX_OK) {
return NGX_ERROR;
}
}
}
}
sr->method = method;
switch (method) {
case NGX_HTTP_GET:
sr->method_name = ngx_http_lua_get_method;
break;
case NGX_HTTP_POST:
sr->method_name = ngx_http_lua_post_method;
break;
case NGX_HTTP_PUT:
sr->method_name = ngx_http_lua_put_method;
break;
case NGX_HTTP_HEAD:
sr->method_name = ngx_http_lua_head_method;
break;
case NGX_HTTP_DELETE:
sr->method_name = ngx_http_lua_delete_method;
break;
case NGX_HTTP_OPTIONS:
sr->method_name = ngx_http_lua_options_method;
break;
case NGX_HTTP_MKCOL:
sr->method_name = ngx_http_lua_mkcol_method;
break;
case NGX_HTTP_COPY:
sr->method_name = ngx_http_lua_copy_method;
break;
case NGX_HTTP_MOVE:
sr->method_name = ngx_http_lua_move_method;
break;
case NGX_HTTP_PROPFIND:
sr->method_name = ngx_http_lua_propfind_method;
break;
case NGX_HTTP_PROPPATCH:
sr->method_name = ngx_http_lua_proppatch_method;
break;
case NGX_HTTP_LOCK:
sr->method_name = ngx_http_lua_lock_method;
break;
case NGX_HTTP_UNLOCK:
sr->method_name = ngx_http_lua_unlock_method;
break;
case NGX_HTTP_PATCH:
sr->method_name = ngx_http_lua_patch_method;
break;
case NGX_HTTP_TRACE:
sr->method_name = ngx_http_lua_trace_method;
break;
default:
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"unsupported HTTP method: %u", (unsigned) method);
return NGX_ERROR;
}
if (!(vars_action & NGX_HTTP_LUA_SHARE_ALL_VARS)) {
/* we do not inherit the parent request's variables */
cmcf = ngx_http_get_module_main_conf(sr, ngx_http_core_module);
size = cmcf->variables.nelts * sizeof(ngx_http_variable_value_t);
if (vars_action & NGX_HTTP_LUA_COPY_ALL_VARS) {
sr->variables = ngx_palloc(sr->pool, size);
if (sr->variables == NULL) {
return NGX_ERROR;
}
ngx_memcpy(sr->variables, r->variables, size);
} else {
/* we do not inherit the parent request's variables */
sr->variables = ngx_pcalloc(sr->pool, size);
if (sr->variables == NULL) {
return NGX_ERROR;
}
}
}
return ngx_http_lua_subrequest_add_extra_vars(sr, extra_vars);
}
static ngx_int_t
ngx_http_lua_subrequest_add_extra_vars(ngx_http_request_t *sr,
ngx_array_t *extra_vars)
{
ngx_http_core_main_conf_t *cmcf;
ngx_http_variable_t *v;
ngx_http_variable_value_t *vv;
u_char *val;
u_char *p;
ngx_uint_t i, hash;
ngx_str_t name;
size_t len;
ngx_hash_t *variables_hash;
ngx_keyval_t *var;
/* set any extra variables that were passed to the subrequest */
if (extra_vars == NULL || extra_vars->nelts == 0) {
return NGX_OK;
}
cmcf = ngx_http_get_module_main_conf(sr, ngx_http_core_module);
variables_hash = &cmcf->variables_hash;
var = extra_vars->elts;
for (i = 0; i < extra_vars->nelts; i++, var++) {
/* copy the variable's name and value because they are allocated
* by the lua VM */
len = var->key.len + var->value.len;
p = ngx_pnalloc(sr->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
name.data = p;
name.len = var->key.len;
p = ngx_copy(p, var->key.data, var->key.len);
hash = ngx_hash_strlow(name.data, name.data, name.len);
val = p;
len = var->value.len;
ngx_memcpy(p, var->value.data, len);
v = ngx_hash_find(variables_hash, hash, name.data, name.len);
if (v) {
if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
ngx_log_error(NGX_LOG_ERR, sr->connection->log, 0,
"variable \"%V\" not changeable", &name);
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (v->set_handler) {
vv = ngx_palloc(sr->pool, sizeof(ngx_http_variable_value_t));
if (vv == NULL) {
return NGX_ERROR;
}
vv->valid = 1;
vv->not_found = 0;
vv->no_cacheable = 0;
vv->data = val;
vv->len = len;
v->set_handler(sr, vv, v->data);
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sr->connection->log, 0,
"variable \"%V\" set to value \"%v\"", &name,
vv);
continue;
}
if (v->flags & NGX_HTTP_VAR_INDEXED) {
vv = &sr->variables[v->index];
vv->valid = 1;
vv->not_found = 0;
vv->no_cacheable = 0;
vv->data = val;
vv->len = len;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sr->connection->log, 0,
"variable \"%V\" set to value \"%v\"",
&name, vv);
continue;
}
}
ngx_log_error(NGX_LOG_ERR, sr->connection->log, 0,
"variable \"%V\" cannot be assigned a value (maybe you "
"forgot to define it first?) ", &name);
return NGX_ERROR;
}
return NGX_OK;
}
static void
ngx_http_lua_process_vars_option(ngx_http_request_t *r, lua_State *L,
int table, ngx_array_t **varsp)
{
ngx_array_t *vars;
ngx_keyval_t *var;
if (table < 0) {
table = lua_gettop(L) + table + 1;
}
vars = *varsp;
if (vars == NULL) {
vars = ngx_array_create(r->pool, 4, sizeof(ngx_keyval_t));
if (vars == NULL) {
dd("here");
luaL_error(L, "out of memory");
return;
}
*varsp = vars;
}
lua_pushnil(L);
while (lua_next(L, table) != 0) {
if (lua_type(L, -2) != LUA_TSTRING) {
luaL_error(L, "attempt to use a non-string key in the "
"\"vars\" option table");
return;
}
if (!lua_isstring(L, -1)) {
luaL_error(L, "attempt to use bad variable value type %s",
luaL_typename(L, -1));
}
var = ngx_array_push(vars);
if (var == NULL) {
dd("here");
luaL_error(L, "out of memory");
return;
}
var->key.data = (u_char *) lua_tolstring(L, -2, &var->key.len);
var->value.data = (u_char *) lua_tolstring(L, -1, &var->value.len);
lua_pop(L, 1);
}
}
ngx_int_t
ngx_http_lua_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
ngx_http_request_t *pr;
ngx_http_lua_ctx_t *pr_ctx;
ngx_http_lua_ctx_t *ctx; /* subrequest ctx */
ngx_http_lua_co_ctx_t *pr_coctx;
size_t len;
ngx_str_t *body_str;
u_char *p;
ngx_chain_t *cl;
ngx_http_lua_post_subrequest_data_t *psr_data = data;
ctx = psr_data->ctx;
if (ctx->run_post_subrequest) {
if (r != r->connection->data) {
r->connection->data = r;
}
return NGX_OK;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua run post subrequest handler, rc:%i c:%ud", rc,
r->main->count);
ctx->run_post_subrequest = 1;
pr = r->parent;
pr_ctx = ngx_http_get_module_ctx(pr, ngx_http_lua_module);
if (pr_ctx == NULL) {
return NGX_ERROR;
}
pr_coctx = psr_data->pr_co_ctx;
pr_coctx->pending_subreqs--;
if (pr_coctx->pending_subreqs == 0) {
dd("all subrequests are done");
pr_ctx->no_abort = 0;
pr_ctx->resume_handler = ngx_http_lua_subrequest_resume;
pr_ctx->cur_co_ctx = pr_coctx;
}
if (pr_ctx->entered_content_phase) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua restoring write event handler");
pr->write_event_handler = ngx_http_lua_content_wev_handler;
} else {
pr->write_event_handler = ngx_http_core_run_phases;
}
dd("status rc = %d", (int) rc);
dd("status headers_out.status = %d", (int) r->headers_out.status);
dd("uri: %.*s", (int) r->uri.len, r->uri.data);
/* capture subrequest response status */
pr_coctx->sr_statuses[ctx->index] = r->headers_out.status;
if (pr_coctx->sr_statuses[ctx->index] == 0) {
if (rc == NGX_OK) {
rc = NGX_HTTP_OK;
}
if (rc == NGX_ERROR) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
pr_coctx->sr_statuses[ctx->index] = rc;
}
}
if (!ctx->seen_last_for_subreq) {
pr_coctx->sr_flags[ctx->index] |= NGX_HTTP_LUA_SUBREQ_TRUNCATED;
}
dd("pr_coctx status: %d", (int) pr_coctx->sr_statuses[ctx->index]);
/* copy subrequest response headers */
pr_coctx->sr_headers[ctx->index] = &r->headers_out;
/* copy subrequest response body */
body_str = &pr_coctx->sr_bodies[ctx->index];
len = 0;
for (cl = ctx->body; cl; cl = cl->next) {
/* ignore all non-memory buffers */
len += cl->buf->last - cl->buf->pos;
}
body_str->len = len;
if (len == 0) {
body_str->data = NULL;
} else {
p = ngx_palloc(r->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
body_str->data = p;
/* copy from and then free the data buffers */
for (cl = ctx->body; cl; cl = cl->next) {
p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos);
cl->buf->last = cl->buf->pos;
#if 0
dd("free body chain link buf ASAP");
ngx_pfree(r->pool, cl->buf->start);
#endif
}
}
if (ctx->body) {
#if defined(nginx_version) && nginx_version >= 1001004
ngx_chain_update_chains(r->pool,
#else
ngx_chain_update_chains(
#endif
&pr_ctx->free_bufs, &pr_ctx->busy_bufs,
&ctx->body,
(ngx_buf_tag_t) &ngx_http_lua_module);
dd("free bufs: %p", pr_ctx->free_bufs);
}
ngx_http_post_request_to_head(pr);
if (r != r->connection->data) {
r->connection->data = r;
}
if (rc == NGX_ERROR
|| rc == NGX_HTTP_CREATED
|| rc == NGX_HTTP_NO_CONTENT
|| (rc >= NGX_HTTP_SPECIAL_RESPONSE
&& rc != NGX_HTTP_CLOSE
&& rc != NGX_HTTP_REQUEST_TIME_OUT
&& rc != NGX_HTTP_CLIENT_CLOSED_REQUEST))
{
/* emulate ngx_http_special_response_handler */
if (rc > NGX_OK) {
r->err_status = rc;
r->expect_tested = 1;
r->headers_out.content_type.len = 0;
r->headers_out.content_length_n = 0;
ngx_http_clear_accept_ranges(r);
ngx_http_clear_last_modified(r);
rc = ngx_http_lua_send_header_if_needed(r, ctx);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
}
return NGX_OK;
}
return rc;
}
static ngx_int_t
ngx_http_lua_set_content_length_header(ngx_http_request_t *r, off_t len)
{
ngx_table_elt_t *h, *header;
u_char *p;
ngx_list_part_t *part;
ngx_http_request_t *pr;
ngx_uint_t i;
r->headers_in.content_length_n = len;
if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t)) != NGX_OK)
{
return NGX_ERROR;
}
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
return NGX_ERROR;
}
h->key = ngx_http_lua_content_length_header_key;
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
return NGX_ERROR;
}
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
r->headers_in.content_length = h;
p = ngx_palloc(r->pool, NGX_OFF_T_LEN);
if (p == NULL) {
return NGX_ERROR;
}
h->value.data = p;
h->value.len = ngx_sprintf(h->value.data, "%O", len) - h->value.data;
h->hash = ngx_http_lua_content_length_hash;
#if 0
dd("content length hash: %lu == %lu", (unsigned long) h->hash,
ngx_hash_key_lc((u_char *) "Content-Length",
sizeof("Content-Length") - 1));
#endif
dd("r content length: %.*s",
(int)r->headers_in.content_length->value.len,
r->headers_in.content_length->value.data);
pr = r->parent;
if (pr == NULL) {
return NGX_OK;
}
/* forward the parent request's all other request headers */
part = &pr->headers_in.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (header[i].key.len == sizeof("Content-Length") - 1
&& ngx_strncasecmp(header[i].key.data, (u_char *) "Content-Length",
sizeof("Content-Length") - 1) == 0)
{
continue;
}
if (ngx_http_lua_set_input_header(r, header[i].key,
header[i].value, 0) == NGX_ERROR)
{
return NGX_ERROR;
}
}
return NGX_OK;
}
static void
ngx_http_lua_handle_subreq_responses(ngx_http_request_t *r,
ngx_http_lua_ctx_t *ctx)
{
ngx_uint_t i, count;
ngx_uint_t index;
lua_State *co;
ngx_str_t *body_str;
ngx_table_elt_t *header;
ngx_list_part_t *part;
ngx_http_headers_out_t *sr_headers;
ngx_http_lua_co_ctx_t *coctx;
u_char buf[sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1];
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua handle subrequest responses");
coctx = ctx->cur_co_ctx;
co = coctx->co;
for (index = 0; index < coctx->nsubreqs; index++) {
dd("summary: reqs %d, subquery %d, pending %d, req %.*s",
(int) coctx->nsubreqs,
(int) index,
(int) coctx->pending_subreqs,
(int) r->uri.len, r->uri.data);
/* {{{ construct ret value */
lua_createtable(co, 0 /* narr */, 4 /* nrec */);
/* copy captured status */
lua_pushinteger(co, coctx->sr_statuses[index]);
lua_setfield(co, -2, "status");
dd("captured subrequest flags: %d", (int) coctx->sr_flags[index]);
/* set truncated flag if truncation happens */
if (coctx->sr_flags[index] & NGX_HTTP_LUA_SUBREQ_TRUNCATED) {
lua_pushboolean(co, 1);
lua_setfield(co, -2, "truncated");
} else {
lua_pushboolean(co, 0);
lua_setfield(co, -2, "truncated");
}
/* copy captured body */
body_str = &coctx->sr_bodies[index];
lua_pushlstring(co, (char *) body_str->data, body_str->len);
lua_setfield(co, -2, "body");
if (body_str->data) {
dd("free body buffer ASAP");
ngx_pfree(r->pool, body_str->data);
}
/* copy captured headers */
sr_headers = coctx->sr_headers[index];
part = &sr_headers->headers.part;
count = part->nelts;
while (part->next) {
part = part->next;
count += part->nelts;
}
lua_createtable(co, 0, count + 5); /* res.header */
dd("saving subrequest response headers");
part = &sr_headers->headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
dd("checking sr header %.*s", (int) header[i].key.len,
header[i].key.data);
#if 1
if (header[i].hash == 0) {
continue;
}
#endif
header[i].hash = 0;
dd("pushing sr header %.*s", (int) header[i].key.len,
header[i].key.data);
lua_pushlstring(co, (char *) header[i].key.data,
header[i].key.len); /* header key */
lua_pushvalue(co, -1); /* stack: table key key */
/* check if header already exists */
lua_rawget(co, -3); /* stack: table key value */
if (lua_isnil(co, -1)) {
lua_pop(co, 1); /* stack: table key */
lua_pushlstring(co, (char *) header[i].value.data,
header[i].value.len);
/* stack: table key value */
lua_rawset(co, -3); /* stack: table */
} else {
if (!lua_istable(co, -1)) { /* already inserted one value */
lua_createtable(co, 4, 0);
/* stack: table key value table */
lua_insert(co, -2); /* stack: table key table value */
lua_rawseti(co, -2, 1); /* stack: table key table */
lua_pushlstring(co, (char *) header[i].value.data,
header[i].value.len);
/* stack: table key table value */
lua_rawseti(co, -2, lua_objlen(co, -2) + 1);
/* stack: table key table */
lua_rawset(co, -3); /* stack: table */
} else {
lua_pushlstring(co, (char *) header[i].value.data,
header[i].value.len);
/* stack: table key table value */
lua_rawseti(co, -2, lua_objlen(co, -2) + 1);
/* stack: table key table */
lua_pop(co, 2); /* stack: table */
}
}
}
if (sr_headers->content_type.len) {
lua_pushliteral(co, "Content-Type"); /* header key */
lua_pushlstring(co, (char *) sr_headers->content_type.data,
sr_headers->content_type.len); /* head key value */
lua_rawset(co, -3); /* head */
}
if (sr_headers->content_length == NULL
&& sr_headers->content_length_n >= 0)
{
lua_pushliteral(co, "Content-Length"); /* header key */
lua_pushnumber(co, (lua_Number) sr_headers->content_length_n);
/* head key value */
lua_rawset(co, -3); /* head */
}
/* to work-around an issue in ngx_http_static_module
* (github issue #41) */
if (sr_headers->location && sr_headers->location->value.len) {
lua_pushliteral(co, "Location"); /* header key */
lua_pushlstring(co, (char *) sr_headers->location->value.data,
sr_headers->location->value.len);
/* head key value */
lua_rawset(co, -3); /* head */
}
if (sr_headers->last_modified_time != -1) {
if (sr_headers->status != NGX_HTTP_OK
&& sr_headers->status != NGX_HTTP_PARTIAL_CONTENT
&& sr_headers->status != NGX_HTTP_NOT_MODIFIED
&& sr_headers->status != NGX_HTTP_NO_CONTENT)
{
sr_headers->last_modified_time = -1;
sr_headers->last_modified = NULL;
}
}
if (sr_headers->last_modified == NULL
&& sr_headers->last_modified_time != -1)
{
(void) ngx_http_time(buf, sr_headers->last_modified_time);
lua_pushliteral(co, "Last-Modified"); /* header key */
lua_pushlstring(co, (char *) buf, sizeof(buf)); /* head key value */
lua_rawset(co, -3); /* head */
}
lua_setfield(co, -2, "header");
/* }}} */
}
}
void
ngx_http_lua_inject_subrequest_api(lua_State *L)
{
lua_createtable(L, 0 /* narr */, 2 /* nrec */); /* .location */
lua_pushcfunction(L, ngx_http_lua_ngx_location_capture);
lua_setfield(L, -2, "capture");
lua_pushcfunction(L, ngx_http_lua_ngx_location_capture_multi);
lua_setfield(L, -2, "capture_multi");
lua_setfield(L, -2, "location");
}
static ngx_int_t
ngx_http_lua_subrequest(ngx_http_request_t *r,
ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
{
ngx_time_t *tp;
ngx_connection_t *c;
ngx_http_request_t *sr;
ngx_http_core_srv_conf_t *cscf;
r->main->subrequests--;
if (r->main->subrequests == 0) {
#if defined(NGX_DTRACE) && NGX_DTRACE
ngx_http_probe_subrequest_cycle(r, uri, args);
#endif
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"subrequests cycle while processing \"%V\"", uri);
r->main->subrequests = 1;
return NGX_ERROR;
}
sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
if (sr == NULL) {
return NGX_ERROR;
}
sr->signature = NGX_HTTP_MODULE;
c = r->connection;
sr->connection = c;
sr->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
if (sr->ctx == NULL) {
return NGX_ERROR;
}
if (ngx_list_init(&sr->headers_out.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
return NGX_ERROR;
}
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
sr->main_conf = cscf->ctx->main_conf;
sr->srv_conf = cscf->ctx->srv_conf;
sr->loc_conf = cscf->ctx->loc_conf;
sr->pool = r->pool;
sr->headers_in.content_length_n = -1;
sr->headers_in.keep_alive_n = -1;
ngx_http_clear_content_length(sr);
ngx_http_clear_accept_ranges(sr);
ngx_http_clear_last_modified(sr);
sr->request_body = r->request_body;
#ifdef HAVE_ALLOW_REQUEST_BODY_UPDATING_PATCH
sr->content_length_n = -1;
#endif
sr->method = NGX_HTTP_GET;
sr->http_version = r->http_version;
sr->request_line = r->request_line;
sr->uri = *uri;
if (args) {
sr->args = *args;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http subrequest \"%V?%V\"", uri, &sr->args);
sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;
sr->unparsed_uri = r->unparsed_uri;
sr->method_name = ngx_http_core_get_method;
sr->http_protocol = r->http_protocol;
ngx_http_set_exten(sr);
sr->main = r->main;
sr->parent = r;
sr->post_subrequest = ps;
sr->read_event_handler = ngx_http_request_empty_handler;
sr->write_event_handler = ngx_http_handler;
sr->variables = r->variables;
sr->log_handler = r->log_handler;
sr->internal = 1;
sr->discard_body = r->discard_body;
sr->expect_tested = 1;
sr->main_filter_need_in_memory = r->main_filter_need_in_memory;
sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
tp = ngx_timeofday();
sr->start_sec = tp->sec;
sr->start_msec = tp->msec;
r->main->count++;
*psr = sr;
#if defined(NGX_DTRACE) && NGX_DTRACE
ngx_http_probe_subrequest_start(sr);
#endif
return ngx_http_post_request(sr, NULL);
}
static ngx_int_t
ngx_http_lua_subrequest_resume(ngx_http_request_t *r)
{
lua_State *vm;
ngx_int_t rc;
ngx_connection_t *c;
ngx_http_lua_ctx_t *ctx;
ngx_http_lua_co_ctx_t *coctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
if (ctx == NULL) {
return NGX_ERROR;
}
ctx->resume_handler = ngx_http_lua_wev_handler;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua run subrequests done, resuming lua thread");
coctx = ctx->cur_co_ctx;
dd("nsubreqs: %d", (int) coctx->nsubreqs);
ngx_http_lua_handle_subreq_responses(r, ctx);
dd("free sr_statues/headers/bodies memory ASAP");
#if 1
ngx_pfree(r->pool, coctx->sr_statuses);
coctx->sr_statuses = NULL;
coctx->sr_headers = NULL;
coctx->sr_bodies = NULL;
coctx->sr_flags = NULL;
#endif
c = r->connection;
vm = ngx_http_lua_get_lua_vm(r, ctx);
rc = ngx_http_lua_run_thread(vm, r, ctx, coctx->nsubreqs);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua run thread returned %d", rc);
if (rc == NGX_AGAIN) {
return ngx_http_lua_run_posted_threads(c, vm, r, ctx);
}
if (rc == NGX_DONE) {
ngx_http_lua_finalize_request(r, NGX_DONE);
return ngx_http_lua_run_posted_threads(c, vm, r, ctx);
}
/* rc == NGX_ERROR || rc >= NGX_OK */
if (ctx->entered_content_phase) {
ngx_http_lua_finalize_request(r, rc);
return NGX_DONE;
}
return rc;
}
static void
ngx_http_lua_cancel_subreq(ngx_http_request_t *r)
{
ngx_http_posted_request_t *pr;
ngx_http_posted_request_t **p;
#if 1
r->main->count--;
r->main->subrequests++;
#endif
p = &r->main->posted_requests;
for (pr = r->main->posted_requests; pr->next; pr = pr->next) {
p = &pr->next;
}
*p = NULL;
r->connection->data = r->parent;
}
static ngx_int_t
ngx_http_post_request_to_head(ngx_http_request_t *r)
{
ngx_http_posted_request_t *pr;
pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t));
if (pr == NULL) {
return NGX_ERROR;
}
pr->request = r;
pr->next = r->main->posted_requests;
r->main->posted_requests = pr;
return NGX_OK;
}
static ngx_int_t
ngx_http_lua_copy_in_file_request_body(ngx_http_request_t *r)
{
ngx_temp_file_t *tf;
ngx_http_request_body_t *body;
tf = r->request_body->temp_file;
if (!tf->persistent || !tf->clean) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"the request body was not read by ngx_lua");
return NGX_ERROR;
}
body = ngx_palloc(r->pool, sizeof(ngx_http_request_body_t));
if (body == NULL) {
return NGX_ERROR;
}
ngx_memcpy(body, r->request_body, sizeof(ngx_http_request_body_t));
body->temp_file = ngx_palloc(r->pool, sizeof(ngx_temp_file_t));
if (body->temp_file == NULL) {
return NGX_ERROR;
}
ngx_memcpy(body->temp_file, tf, sizeof(ngx_temp_file_t));
dd("file fd: %d", body->temp_file->file.fd);
r->request_body = body;
return NGX_OK;
}
static ngx_int_t
ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, ngx_http_request_t *r)
{
ngx_table_elt_t *header;
ngx_list_part_t *part;
ngx_uint_t i;
if (ngx_list_init(&sr->headers_in.headers, sr->pool, 20,
sizeof(ngx_table_elt_t)) != NGX_OK)
{
return NGX_ERROR;
}
dd("before: parent req headers count: %d",
(int) r->headers_in.headers.part.nelts);
part = &r->headers_in.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
dd("setting request header %.*s: %.*s", (int) header[i].key.len,
header[i].key.data, (int) header[i].value.len,
header[i].value.data);
if (ngx_http_lua_set_input_header(sr, header[i].key,
header[i].value, 0) == NGX_ERROR)
{
return NGX_ERROR;
}
}
dd("after: parent req headers count: %d",
(int) r->headers_in.headers.part.nelts);
return NGX_OK;
}
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */