| |
| /* |
| * Copyright (C) Xiaozhe Wang (chaoslawful) |
| * Copyright (C) Yichun Zhang (agentzh) |
| */ |
| |
| |
| #ifndef DDEBUG |
| #define DDEBUG 0 |
| #endif |
| #include "ddebug.h" |
| |
| |
| #include "nginx.h" |
| #include "ngx_http_lua_directive.h" |
| #include "ngx_http_lua_util.h" |
| #include "ngx_http_lua_exception.h" |
| #include "ngx_http_lua_pcrefix.h" |
| #include "ngx_http_lua_regex.h" |
| #include "ngx_http_lua_args.h" |
| #include "ngx_http_lua_uri.h" |
| #include "ngx_http_lua_req_body.h" |
| #include "ngx_http_lua_headers.h" |
| #include "ngx_http_lua_output.h" |
| #include "ngx_http_lua_time.h" |
| #include "ngx_http_lua_control.h" |
| #include "ngx_http_lua_ndk.h" |
| #include "ngx_http_lua_subrequest.h" |
| #include "ngx_http_lua_log.h" |
| #include "ngx_http_lua_variable.h" |
| #include "ngx_http_lua_string.h" |
| #include "ngx_http_lua_misc.h" |
| #include "ngx_http_lua_consts.h" |
| #include "ngx_http_lua_req_method.h" |
| #include "ngx_http_lua_shdict.h" |
| #include "ngx_http_lua_coroutine.h" |
| #include "ngx_http_lua_socket_tcp.h" |
| #include "ngx_http_lua_socket_udp.h" |
| #include "ngx_http_lua_sleep.h" |
| #include "ngx_http_lua_setby.h" |
| #include "ngx_http_lua_headerfilterby.h" |
| #include "ngx_http_lua_bodyfilterby.h" |
| #include "ngx_http_lua_logby.h" |
| #include "ngx_http_lua_phase.h" |
| #include "ngx_http_lua_probe.h" |
| #include "ngx_http_lua_uthread.h" |
| #include "ngx_http_lua_contentby.h" |
| #include "ngx_http_lua_timer.h" |
| #include "ngx_http_lua_config.h" |
| #include "ngx_http_lua_worker.h" |
| |
| |
| #if 1 |
| #undef ngx_http_lua_probe_info |
| #define ngx_http_lua_probe_info(msg) |
| #endif |
| |
| |
| #ifndef NGX_HTTP_LUA_BT_DEPTH |
| #define NGX_HTTP_LUA_BT_DEPTH 22 |
| #endif |
| |
| |
| #ifndef NGX_HTTP_LUA_BT_MAX_COROS |
| #define NGX_HTTP_LUA_BT_MAX_COROS 5 |
| #endif |
| |
| |
| char ngx_http_lua_code_cache_key; |
| char ngx_http_lua_regex_cache_key; |
| char ngx_http_lua_socket_pool_key; |
| char ngx_http_lua_coroutines_key; |
| char ngx_http_lua_req_get_headers_metatable_key; |
| |
| |
| ngx_uint_t ngx_http_lua_location_hash = 0; |
| ngx_uint_t ngx_http_lua_content_length_hash = 0; |
| |
| |
| static ngx_int_t ngx_http_lua_send_http10_headers(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx); |
| static void ngx_http_lua_init_registry(lua_State *L, ngx_log_t *log); |
| static void ngx_http_lua_init_globals(lua_State *L, |
| ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log); |
| static void ngx_http_lua_set_path(ngx_cycle_t *cycle, lua_State *L, int tab_idx, |
| const char *fieldname, const char *path, const char *default_path, |
| ngx_log_t *log); |
| static ngx_int_t ngx_http_lua_handle_exec(lua_State *L, ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx); |
| static ngx_int_t ngx_http_lua_handle_exit(lua_State *L, ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx); |
| static ngx_int_t ngx_http_lua_handle_rewrite_jump(lua_State *L, |
| ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx); |
| static int ngx_http_lua_thread_traceback(lua_State *L, lua_State *co, |
| ngx_http_lua_co_ctx_t *coctx); |
| static void ngx_http_lua_inject_ngx_api(lua_State *L, |
| ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log); |
| static void ngx_http_lua_inject_arg_api(lua_State *L); |
| static int ngx_http_lua_param_get(lua_State *L); |
| static int ngx_http_lua_param_set(lua_State *L); |
| static void ngx_http_lua_del_all_threads(ngx_http_request_t *r, lua_State *L, |
| ngx_http_lua_ctx_t *ctx); |
| static ngx_int_t ngx_http_lua_output_filter(ngx_http_request_t *r, |
| ngx_chain_t *in); |
| static ngx_int_t ngx_http_lua_send_special(ngx_http_request_t *r, |
| ngx_uint_t flags); |
| static void ngx_http_lua_finalize_coroutines(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx); |
| static ngx_int_t ngx_http_lua_post_zombie_thread(ngx_http_request_t *r, |
| ngx_http_lua_co_ctx_t *parent, ngx_http_lua_co_ctx_t *thread); |
| static void ngx_http_lua_cleanup_zombie_child_uthreads(ngx_http_request_t *r, |
| lua_State *L, ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t *coctx); |
| static ngx_int_t ngx_http_lua_on_abort_resume(ngx_http_request_t *r); |
| static void ngx_http_lua_close_fake_request(ngx_http_request_t *r); |
| static void ngx_http_lua_free_fake_request(ngx_http_request_t *r); |
| static ngx_int_t ngx_http_lua_flush_pending_output(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx); |
| static ngx_int_t |
| ngx_http_lua_process_flushing_coroutines(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx); |
| static lua_State * ngx_http_lua_new_state(lua_State *parent_vm, |
| ngx_cycle_t *cycle, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log); |
| static void ngx_http_lua_cleanup_conn_pools(lua_State *L); |
| |
| |
| #ifndef LUA_PATH_SEP |
| #define LUA_PATH_SEP ";" |
| #endif |
| |
| #define AUX_MARK "\1" |
| |
| |
| static void |
| ngx_http_lua_set_path(ngx_cycle_t *cycle, lua_State *L, int tab_idx, |
| const char *fieldname, const char *path, const char *default_path, |
| ngx_log_t *log) |
| { |
| const char *tmp_path; |
| const char *prefix; |
| |
| /* XXX here we use some hack to simplify string manipulation */ |
| tmp_path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP, |
| LUA_PATH_SEP AUX_MARK LUA_PATH_SEP); |
| |
| lua_pushlstring(L, (char *) cycle->prefix.data, cycle->prefix.len); |
| prefix = lua_tostring(L, -1); |
| tmp_path = luaL_gsub(L, tmp_path, "$prefix", prefix); |
| tmp_path = luaL_gsub(L, tmp_path, "${prefix}", prefix); |
| lua_pop(L, 3); |
| |
| dd("tmp_path path: %s", tmp_path); |
| |
| #if (NGX_DEBUG) |
| tmp_path = |
| #else |
| (void) |
| #endif |
| luaL_gsub(L, tmp_path, AUX_MARK, default_path); |
| |
| #if (NGX_DEBUG) |
| ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua setting lua package.%s to \"%s\"", fieldname, tmp_path); |
| #endif |
| |
| lua_remove(L, -2); |
| |
| /* fix negative index as there's new data on stack */ |
| tab_idx = (tab_idx < 0) ? (tab_idx - 1) : tab_idx; |
| lua_setfield(L, tab_idx, fieldname); |
| } |
| |
| |
| /** |
| * Create new table and set _G field to itself. |
| * |
| * After: |
| * | new table | <- top |
| * | ... | |
| * */ |
| void |
| ngx_http_lua_create_new_global_table(lua_State *L, int narr, int nrec) |
| { |
| lua_createtable(L, narr, nrec + 1); |
| lua_pushvalue(L, -1); |
| lua_setfield(L, -2, "_G"); |
| } |
| |
| |
| static lua_State * |
| ngx_http_lua_new_state(lua_State *parent_vm, ngx_cycle_t *cycle, |
| ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log) |
| { |
| lua_State *L; |
| const char *old_path; |
| const char *new_path; |
| size_t old_path_len; |
| const char *old_cpath; |
| const char *new_cpath; |
| size_t old_cpath_len; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "lua creating new vm state"); |
| |
| L = luaL_newstate(); |
| if (L == NULL) { |
| return NULL; |
| } |
| |
| luaL_openlibs(L); |
| |
| lua_getglobal(L, "package"); |
| |
| if (!lua_istable(L, -1)) { |
| ngx_log_error(NGX_LOG_EMERG, log, 0, |
| "the \"package\" table does not exist"); |
| return NULL; |
| } |
| |
| if (parent_vm) { |
| lua_getglobal(parent_vm, "package"); |
| lua_getfield(parent_vm, -1, "path"); |
| old_path = lua_tolstring(parent_vm, -1, &old_path_len); |
| lua_pop(parent_vm, 1); |
| |
| lua_pushlstring(L, old_path, old_path_len); |
| lua_setfield(L, -2, "path"); |
| |
| lua_getfield(parent_vm, -1, "cpath"); |
| old_path = lua_tolstring(parent_vm, -1, &old_path_len); |
| lua_pop(parent_vm, 2); |
| |
| lua_pushlstring(L, old_path, old_path_len); |
| lua_setfield(L, -2, "cpath"); |
| |
| } else { |
| #ifdef LUA_DEFAULT_PATH |
| # define LUA_DEFAULT_PATH_LEN (sizeof(LUA_DEFAULT_PATH) - 1) |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua prepending default package.path with %s", |
| LUA_DEFAULT_PATH); |
| |
| lua_pushliteral(L, LUA_DEFAULT_PATH ";"); /* package default */ |
| lua_getfield(L, -2, "path"); /* package default old */ |
| old_path = lua_tolstring(L, -1, &old_path_len); |
| lua_concat(L, 2); /* package new */ |
| lua_setfield(L, -2, "path"); /* package */ |
| #endif |
| |
| #ifdef LUA_DEFAULT_CPATH |
| # define LUA_DEFAULT_CPATH_LEN (sizeof(LUA_DEFAULT_CPATH) - 1) |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua prepending default package.cpath with %s", |
| LUA_DEFAULT_CPATH); |
| |
| lua_pushliteral(L, LUA_DEFAULT_CPATH ";"); /* package default */ |
| lua_getfield(L, -2, "cpath"); /* package default old */ |
| old_cpath = lua_tolstring(L, -1, &old_cpath_len); |
| lua_concat(L, 2); /* package new */ |
| lua_setfield(L, -2, "cpath"); /* package */ |
| #endif |
| |
| if (lmcf->lua_path.len != 0) { |
| lua_getfield(L, -1, "path"); /* get original package.path */ |
| old_path = lua_tolstring(L, -1, &old_path_len); |
| |
| dd("old path: %s", old_path); |
| |
| lua_pushlstring(L, (char *) lmcf->lua_path.data, |
| lmcf->lua_path.len); |
| new_path = lua_tostring(L, -1); |
| |
| ngx_http_lua_set_path(cycle, L, -3, "path", new_path, old_path, |
| log); |
| |
| lua_pop(L, 2); |
| } |
| |
| if (lmcf->lua_cpath.len != 0) { |
| lua_getfield(L, -1, "cpath"); /* get original package.cpath */ |
| old_cpath = lua_tolstring(L, -1, &old_cpath_len); |
| |
| dd("old cpath: %s", old_cpath); |
| |
| lua_pushlstring(L, (char *) lmcf->lua_cpath.data, |
| lmcf->lua_cpath.len); |
| new_cpath = lua_tostring(L, -1); |
| |
| ngx_http_lua_set_path(cycle, L, -3, "cpath", new_cpath, old_cpath, |
| log); |
| |
| |
| lua_pop(L, 2); |
| } |
| } |
| |
| lua_pop(L, 1); /* remove the "package" table */ |
| |
| ngx_http_lua_init_registry(L, log); |
| ngx_http_lua_init_globals(L, lmcf, log); |
| |
| return L; |
| } |
| |
| |
| lua_State * |
| ngx_http_lua_new_thread(ngx_http_request_t *r, lua_State *L, int *ref) |
| { |
| int base; |
| lua_State *co; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua creating new thread"); |
| |
| base = lua_gettop(L); |
| |
| lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); |
| |
| co = lua_newthread(L); |
| |
| /* {{{ inherit coroutine's globals to main thread's globals table |
| * for print() function will try to find tostring() in current |
| * globals table. |
| */ |
| /* new globals table for coroutine */ |
| ngx_http_lua_create_new_global_table(co, 0, 0); |
| |
| lua_createtable(co, 0, 1); |
| lua_pushvalue(co, LUA_GLOBALSINDEX); |
| lua_setfield(co, -2, "__index"); |
| lua_setmetatable(co, -2); |
| |
| lua_replace(co, LUA_GLOBALSINDEX); |
| /* }}} */ |
| |
| *ref = luaL_ref(L, -2); |
| |
| if (*ref == LUA_NOREF) { |
| lua_settop(L, base); /* restore main thread stack */ |
| return NULL; |
| } |
| |
| lua_settop(L, base); |
| return co; |
| } |
| |
| |
| void |
| ngx_http_lua_del_thread(ngx_http_request_t *r, lua_State *L, |
| ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t *coctx) |
| { |
| if (coctx->co_ref == LUA_NOREF) { |
| return; |
| } |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua deleting light thread"); |
| |
| lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); |
| |
| ngx_http_lua_probe_thread_delete(r, coctx->co, ctx); |
| |
| luaL_unref(L, -1, coctx->co_ref); |
| coctx->co_ref = LUA_NOREF; |
| coctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| lua_pop(L, 1); |
| } |
| |
| |
| static void |
| ngx_http_lua_del_all_threads(ngx_http_request_t *r, lua_State *L, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| int inited = 0; |
| int ref; |
| ngx_uint_t i; |
| ngx_list_part_t *part; |
| ngx_http_lua_co_ctx_t *entry_coctx; |
| ngx_http_lua_co_ctx_t *cc; |
| |
| cc = ctx->on_abort_co_ctx; |
| if (cc) { |
| lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); |
| inited = 1; |
| |
| if (cc->co_ref != LUA_NOREF) { |
| ngx_http_lua_probe_thread_delete(r, cc->co, ctx); |
| |
| luaL_unref(L, -1, cc->co_ref); |
| cc->co_ref = LUA_NOREF; |
| |
| if (cc->co_status != NGX_HTTP_LUA_CO_SUSPENDED) { |
| ctx->uthreads--; |
| } |
| |
| cc->co_status = NGX_HTTP_LUA_CO_DEAD; |
| } |
| |
| ctx->on_abort_co_ctx = NULL; |
| } |
| |
| if (ctx->uthreads && ctx->user_co_ctx) { |
| /* release all pending user threads */ |
| |
| if (!inited) { |
| lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); |
| inited = 1; |
| } |
| |
| part = &ctx->user_co_ctx->part; |
| cc = part->elts; |
| |
| for (i = 0; /* void */; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| cc = part->elts; |
| i = 0; |
| } |
| |
| ref = cc[i].co_ref; |
| |
| if (ref != LUA_NOREF) { |
| ngx_http_lua_probe_thread_delete(r, cc[i].co, ctx); |
| |
| luaL_unref(L, -1, ref); |
| cc[i].co_ref = LUA_NOREF; |
| cc[i].co_status = NGX_HTTP_LUA_CO_DEAD; |
| ctx->uthreads--; |
| |
| if (ctx->uthreads == 0) { |
| break; |
| } |
| } |
| } |
| } |
| |
| /* release the reference to the entry thread */ |
| |
| entry_coctx = &ctx->entry_co_ctx; |
| |
| if (entry_coctx->co_ref != LUA_NOREF) { |
| if (!inited) { |
| lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); |
| inited = 1; |
| } |
| |
| ngx_http_lua_probe_thread_delete(r, entry_coctx->co, ctx); |
| |
| luaL_unref(L, -1, entry_coctx->co_ref); |
| entry_coctx->co_ref = LUA_NOREF; |
| entry_coctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| } |
| |
| if (inited) { |
| lua_pop(L, 1); |
| } |
| } |
| |
| |
| u_char * |
| ngx_http_lua_rebase_path(ngx_pool_t *pool, u_char *src, size_t len) |
| { |
| u_char *p, *dst; |
| |
| if (len == 0) { |
| return NULL; |
| } |
| |
| if (src[0] == '/') { |
| /* being an absolute path already */ |
| dst = ngx_palloc(pool, len + 1); |
| if (dst == NULL) { |
| return NULL; |
| } |
| |
| p = ngx_copy(dst, src, len); |
| |
| *p = '\0'; |
| |
| return dst; |
| } |
| |
| dst = ngx_palloc(pool, ngx_cycle->prefix.len + len + 1); |
| if (dst == NULL) { |
| return NULL; |
| } |
| |
| p = ngx_copy(dst, ngx_cycle->prefix.data, ngx_cycle->prefix.len); |
| p = ngx_copy(p, src, len); |
| |
| *p = '\0'; |
| |
| return dst; |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_send_header_if_needed(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_int_t rc; |
| |
| if (!r->header_sent) { |
| if (r->headers_out.status == 0) { |
| r->headers_out.status = NGX_HTTP_OK; |
| } |
| |
| if (!ctx->headers_set && ngx_http_lua_set_content_type(r) != NGX_OK) { |
| return NGX_ERROR; |
| } |
| |
| if (!ctx->headers_set) { |
| ngx_http_clear_content_length(r); |
| ngx_http_clear_accept_ranges(r); |
| } |
| |
| if (!ctx->buffering) { |
| dd("sending headers"); |
| rc = ngx_http_send_header(r); |
| return rc; |
| } |
| } |
| |
| return NGX_OK; |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_send_chain_link(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, |
| ngx_chain_t *in) |
| { |
| ngx_int_t rc; |
| ngx_chain_t *cl; |
| ngx_chain_t **ll; |
| ngx_http_lua_loc_conf_t *llcf; |
| |
| #if 1 |
| if (ctx->acquired_raw_req_socket || ctx->eof) { |
| dd("ctx->eof already set or raw req socket already acquired"); |
| return NGX_OK; |
| } |
| #endif |
| |
| if ((r->method & NGX_HTTP_HEAD) && !r->header_only) { |
| r->header_only = 1; |
| } |
| |
| llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); |
| |
| if (llcf->http10_buffering |
| && !ctx->buffering |
| && !r->header_sent |
| && r->http_version < NGX_HTTP_VERSION_11 |
| && r->headers_out.content_length_n < 0) |
| { |
| ctx->buffering = 1; |
| } |
| |
| rc = ngx_http_lua_send_header_if_needed(r, ctx); |
| |
| if (rc == NGX_ERROR || rc > NGX_OK) { |
| return rc; |
| } |
| |
| if (r->header_only) { |
| ctx->eof = 1; |
| |
| if (ctx->buffering) { |
| return ngx_http_lua_send_http10_headers(r, ctx); |
| } |
| |
| return rc; |
| } |
| |
| if (in == NULL) { |
| if (ctx->buffering) { |
| rc = ngx_http_lua_send_http10_headers(r, ctx); |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| |
| if (ctx->out) { |
| |
| rc = ngx_http_lua_output_filter(r, ctx->out); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| |
| ctx->out = NULL; |
| } |
| } |
| |
| #if defined(nginx_version) && nginx_version <= 8004 |
| |
| /* earlier versions of nginx does not allow subrequests |
| to send last_buf themselves */ |
| if (r != r->main) { |
| return NGX_OK; |
| } |
| |
| #endif |
| |
| ctx->eof = 1; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua sending last buf of the response body"); |
| |
| rc = ngx_http_lua_send_special(r, NGX_HTTP_LAST); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| |
| return NGX_OK; |
| } |
| |
| /* in != NULL */ |
| |
| if (ctx->buffering) { |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua buffering output bufs for the HTTP 1.0 request"); |
| |
| for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { |
| ll = &cl->next; |
| } |
| |
| *ll = in; |
| |
| return NGX_OK; |
| } |
| |
| return ngx_http_lua_output_filter(r, in); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_send_special(ngx_http_request_t *r, ngx_uint_t flags) |
| { |
| ngx_int_t rc; |
| ngx_http_request_t *ar; /* active request */ |
| |
| ar = r->connection->data; |
| |
| if (ar != r) { |
| |
| /* bypass ngx_http_postpone_filter_module */ |
| |
| r->connection->data = r; |
| rc = ngx_http_send_special(r, flags); |
| r->connection->data = ar; |
| return rc; |
| } |
| |
| return ngx_http_send_special(r, flags); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_output_filter(ngx_http_request_t *r, ngx_chain_t *in) |
| { |
| ngx_int_t rc; |
| ngx_http_request_t *ar; /* active request */ |
| |
| ar = r->connection->data; |
| |
| if (ar != r) { |
| |
| /* bypass ngx_http_postpone_filter_module */ |
| |
| r->connection->data = r; |
| rc = ngx_http_output_filter(r, in); |
| r->connection->data = ar; |
| return rc; |
| } |
| |
| return ngx_http_output_filter(r, in); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_send_http10_headers(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| off_t size; |
| ngx_chain_t *cl; |
| ngx_int_t rc; |
| |
| if (r->header_sent) { |
| return NGX_OK; |
| } |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua sending HTTP 1.0 response headers"); |
| |
| if (r->header_only) { |
| goto send; |
| } |
| |
| if (r->headers_out.content_length == NULL) { |
| for (size = 0, cl = ctx->out; cl; cl = cl->next) { |
| size += ngx_buf_size(cl->buf); |
| } |
| |
| r->headers_out.content_length_n = size; |
| |
| if (r->headers_out.content_length) { |
| r->headers_out.content_length->hash = 0; |
| } |
| } |
| |
| send: |
| rc = ngx_http_send_header(r); |
| return rc; |
| } |
| |
| |
| static void |
| ngx_http_lua_init_registry(lua_State *L, ngx_log_t *log) |
| { |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua initializing lua registry"); |
| |
| /* {{{ register a table to anchor lua coroutines reliably: |
| * {([int]ref) = [cort]} */ |
| lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); |
| lua_createtable(L, 0, 32 /* nrec */); |
| lua_rawset(L, LUA_REGISTRYINDEX); |
| /* }}} */ |
| |
| /* create the registry entry for the Lua request ctx data table */ |
| lua_pushliteral(L, ngx_http_lua_ctx_tables_key); |
| lua_createtable(L, 0, 32 /* nrec */); |
| lua_rawset(L, LUA_REGISTRYINDEX); |
| |
| /* create the registry entry for the Lua socket connection pool table */ |
| lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); |
| lua_createtable(L, 0, 8 /* nrec */); |
| lua_rawset(L, LUA_REGISTRYINDEX); |
| |
| #if (NGX_PCRE) |
| /* create the registry entry for the Lua precompiled regex object cache */ |
| lua_pushlightuserdata(L, &ngx_http_lua_regex_cache_key); |
| lua_createtable(L, 0, 16 /* nrec */); |
| lua_rawset(L, LUA_REGISTRYINDEX); |
| #endif |
| |
| /* {{{ register table to cache user code: |
| * { [(string)cache_key] = <code closure> } */ |
| lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); |
| lua_createtable(L, 0, 8 /* nrec */); |
| lua_rawset(L, LUA_REGISTRYINDEX); |
| /* }}} */ |
| } |
| |
| |
| static void |
| ngx_http_lua_init_globals(lua_State *L, ngx_http_lua_main_conf_t *lmcf, |
| ngx_log_t *log) |
| { |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua initializing lua globals"); |
| |
| #if defined(NDK) && NDK |
| ngx_http_lua_inject_ndk_api(L); |
| #endif /* defined(NDK) && NDK */ |
| |
| ngx_http_lua_inject_ngx_api(L, lmcf, log); |
| } |
| |
| |
| static void |
| ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, |
| ngx_log_t *log) |
| { |
| lua_createtable(L, 0 /* narr */, 97 /* nrec */); /* ngx.* */ |
| |
| ngx_http_lua_inject_arg_api(L); |
| |
| ngx_http_lua_inject_http_consts(L); |
| ngx_http_lua_inject_core_consts(L); |
| |
| ngx_http_lua_inject_log_api(L); |
| ngx_http_lua_inject_output_api(L); |
| ngx_http_lua_inject_time_api(L); |
| ngx_http_lua_inject_string_api(L); |
| ngx_http_lua_inject_control_api(log, L); |
| ngx_http_lua_inject_subrequest_api(L); |
| ngx_http_lua_inject_sleep_api(L); |
| ngx_http_lua_inject_phase_api(L); |
| |
| #if (NGX_PCRE) |
| ngx_http_lua_inject_regex_api(L); |
| #endif |
| |
| ngx_http_lua_inject_req_api(log, L); |
| ngx_http_lua_inject_resp_header_api(L); |
| ngx_http_lua_inject_variable_api(L); |
| ngx_http_lua_inject_shdict_api(lmcf, L); |
| ngx_http_lua_inject_socket_tcp_api(log, L); |
| ngx_http_lua_inject_socket_udp_api(log, L); |
| ngx_http_lua_inject_uthread_api(log, L); |
| ngx_http_lua_inject_timer_api(L); |
| ngx_http_lua_inject_config_api(L); |
| ngx_http_lua_inject_worker_api(L); |
| |
| ngx_http_lua_inject_misc_api(L); |
| |
| lua_getglobal(L, "package"); /* ngx package */ |
| lua_getfield(L, -1, "loaded"); /* ngx package loaded */ |
| lua_pushvalue(L, -3); /* ngx package loaded ngx */ |
| lua_setfield(L, -2, "ngx"); /* ngx package loaded */ |
| lua_pop(L, 2); |
| |
| lua_setglobal(L, "ngx"); |
| |
| ngx_http_lua_inject_coroutine_api(log, L); |
| } |
| |
| |
| void |
| ngx_http_lua_discard_bufs(ngx_pool_t *pool, ngx_chain_t *in) |
| { |
| ngx_chain_t *cl; |
| |
| for (cl = in; cl; cl = cl->next) { |
| cl->buf->pos = cl->buf->last; |
| cl->buf->file_pos = cl->buf->file_last; |
| } |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_add_copy_chain(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, |
| ngx_chain_t ***plast, ngx_chain_t *in, ngx_int_t *eof) |
| { |
| ngx_chain_t *cl; |
| size_t len; |
| ngx_buf_t *b; |
| |
| len = 0; |
| *eof = 0; |
| |
| for (cl = in; cl; cl = cl->next) { |
| if (ngx_buf_in_memory(cl->buf)) { |
| len += cl->buf->last - cl->buf->pos; |
| } |
| |
| if (cl->buf->last_in_chain || cl->buf->last_buf) { |
| *eof = 1; |
| } |
| } |
| |
| if (len == 0) { |
| return NGX_OK; |
| } |
| |
| cl = ngx_http_lua_chains_get_free_buf(r->connection->log, r->pool, |
| &ctx->free_bufs, len, |
| (ngx_buf_tag_t) &ngx_http_lua_module); |
| |
| if (cl == NULL) { |
| return NGX_ERROR; |
| } |
| |
| dd("chains get free buf: %d == %d", (int) (cl->buf->end - cl->buf->start), |
| (int) len); |
| |
| b = cl->buf; |
| |
| while (in) { |
| if (ngx_buf_in_memory(in->buf)) { |
| b->last = ngx_copy(b->last, in->buf->pos, |
| in->buf->last - in->buf->pos); |
| } |
| |
| in = in->next; |
| } |
| |
| **plast = cl; |
| *plast = &cl->next; |
| |
| return NGX_OK; |
| } |
| |
| |
| void |
| ngx_http_lua_reset_ctx(ngx_http_request_t *r, lua_State *L, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua reset ctx"); |
| |
| ngx_http_lua_del_all_threads(r, L, ctx); |
| |
| if (ctx->user_co_ctx) { |
| /* no way to destroy a list but clean up the whole pool */ |
| ctx->user_co_ctx = NULL; |
| } |
| |
| ngx_memzero(&ctx->entry_co_ctx, sizeof(ngx_http_lua_co_ctx_t)); |
| |
| ctx->entry_co_ctx.co_ref = LUA_NOREF; |
| |
| ctx->entered_rewrite_phase = 0; |
| ctx->entered_access_phase = 0; |
| ctx->entered_content_phase = 0; |
| |
| ctx->exit_code = 0; |
| ctx->exited = 0; |
| ctx->resume_handler = ngx_http_lua_wev_handler; |
| |
| ngx_str_null(&ctx->exec_uri); |
| ngx_str_null(&ctx->exec_args); |
| |
| ctx->co_op = 0; |
| } |
| |
| |
| /* post read callback for rewrite and access phases */ |
| void |
| ngx_http_lua_generic_phase_post_read(ngx_http_request_t *r) |
| { |
| ngx_http_lua_ctx_t *ctx; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua post read for rewrite/access phases"); |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| |
| ctx->read_body_done = 1; |
| |
| #if defined(nginx_version) && nginx_version >= 8011 |
| r->main->count--; |
| #endif |
| |
| if (ctx->waiting_more_body) { |
| ctx->waiting_more_body = 0; |
| ngx_http_core_run_phases(r); |
| } |
| } |
| |
| |
| void |
| ngx_http_lua_request_cleanup_handler(void *data) |
| { |
| ngx_http_lua_ctx_t *ctx = data; |
| |
| ngx_http_lua_request_cleanup(ctx, 0 /* forcible */); |
| } |
| |
| |
| void |
| ngx_http_lua_request_cleanup(ngx_http_lua_ctx_t *ctx, int forcible) |
| { |
| lua_State *L; |
| ngx_http_request_t *r; |
| ngx_http_lua_main_conf_t *lmcf; |
| ngx_http_lua_loc_conf_t *llcf; |
| ngx_http_lua_ctx_t *cur_ctx; |
| |
| r = ctx->request; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua request cleanup: forcible=%d", forcible); |
| |
| /* force coroutine handling the request quit */ |
| if (ctx == NULL) { |
| dd("ctx is NULL"); |
| return; |
| } |
| |
| if (ctx->cleanup) { |
| *ctx->cleanup = NULL; |
| ctx->cleanup = NULL; |
| } |
| |
| lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); |
| |
| #if 1 |
| if (r->connection->fd == -1) { |
| /* being a fake request */ |
| lmcf->running_timers--; |
| } |
| #endif |
| |
| L = ngx_http_lua_get_lua_vm(r, ctx); |
| |
| /* we cannot release the ngx.ctx table if we have log_by_lua* hooks |
| * because request cleanup runs before log phase handlers */ |
| |
| if (ctx->ctx_ref != LUA_NOREF) { |
| |
| if (forcible || r->connection->fd == -1 /* being a fake request */) { |
| ngx_http_lua_release_ngx_ctx_table(r->connection->log, L, ctx); |
| |
| } else { |
| |
| cur_ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (cur_ctx != ctx) { |
| /* internal redirects happened */ |
| ngx_http_lua_release_ngx_ctx_table(r->connection->log, L, ctx); |
| |
| } else { |
| |
| llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); |
| if (llcf->log_handler == NULL) { |
| /* no log_by_lua* configured */ |
| ngx_http_lua_release_ngx_ctx_table(r->connection->log, L, |
| ctx); |
| } |
| } |
| } |
| } |
| |
| ngx_http_lua_finalize_coroutines(r, ctx); |
| ngx_http_lua_del_all_threads(r, L, ctx); |
| } |
| |
| |
| void |
| ngx_http_lua_release_ngx_ctx_table(ngx_log_t *log, lua_State *L, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua release ngx.ctx at ref %d", ctx->ctx_ref); |
| |
| lua_pushliteral(L, ngx_http_lua_ctx_tables_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); |
| luaL_unref(L, -1, ctx->ctx_ref); |
| ctx->ctx_ref = LUA_NOREF; |
| lua_pop(L, 1); |
| } |
| |
| |
| /* |
| * description: |
| * run a Lua coroutine specified by ctx->cur_co_ctx->co |
| * return value: |
| * NGX_AGAIN: I/O interruption: r->main->count intact |
| * NGX_DONE: I/O interruption: r->main->count already incremented by 1 |
| * NGX_ERROR: error |
| * >= 200 HTTP status code |
| */ |
| ngx_int_t |
| ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx, volatile int nrets) |
| { |
| ngx_http_lua_co_ctx_t *next_coctx, *parent_coctx, *orig_coctx; |
| int rv, success = 1; |
| lua_State *next_co; |
| lua_State *old_co; |
| const char *err, *msg, *trace; |
| ngx_int_t rc; |
| #if (NGX_PCRE) |
| ngx_pool_t *old_pool = NULL; |
| #endif |
| |
| ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua run thread, top:%d c:%ud", lua_gettop(L), |
| r->main->count); |
| |
| /* set Lua VM panic handler */ |
| lua_atpanic(L, ngx_http_lua_atpanic); |
| |
| dd("ctx = %p", ctx); |
| |
| NGX_LUA_EXCEPTION_TRY { |
| |
| if (ctx->cur_co_ctx->thread_spawn_yielded) { |
| ngx_http_lua_probe_info("thread spawn yielded"); |
| |
| ctx->cur_co_ctx->thread_spawn_yielded = 0; |
| nrets = 1; |
| } |
| |
| for ( ;; ) { |
| |
| dd("calling lua_resume: vm %p, nret %d", ctx->cur_co_ctx->co, |
| (int) nrets); |
| |
| #if (NGX_PCRE) |
| /* XXX: work-around to nginx regex subsystem */ |
| old_pool = ngx_http_lua_pcre_malloc_init(r->pool); |
| #endif |
| |
| /* run code */ |
| dd("ctx: %p", ctx); |
| dd("cur co: %p", ctx->cur_co_ctx->co); |
| dd("cur co status: %d", ctx->cur_co_ctx->co_status); |
| |
| orig_coctx = ctx->cur_co_ctx; |
| rv = lua_resume(orig_coctx->co, nrets); |
| |
| #if (NGX_PCRE) |
| /* XXX: work-around to nginx regex subsystem */ |
| ngx_http_lua_pcre_malloc_done(old_pool); |
| #endif |
| |
| #if 0 |
| /* test the longjmp thing */ |
| if (rand() % 2 == 0) { |
| NGX_LUA_EXCEPTION_THROW(1); |
| } |
| #endif |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua resume returned %d", rv); |
| |
| switch (rv) { |
| case LUA_YIELD: |
| /* yielded, let event handler do the rest job */ |
| /* FIXME: add io cmd dispatcher here */ |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua thread yielded"); |
| |
| if (r->uri_changed) { |
| return ngx_http_lua_handle_rewrite_jump(L, r, ctx); |
| } |
| |
| if (ctx->exited) { |
| return ngx_http_lua_handle_exit(L, r, ctx); |
| } |
| |
| if (ctx->exec_uri.len) { |
| return ngx_http_lua_handle_exec(L, r, ctx); |
| } |
| |
| /* |
| * check if coroutine.resume or coroutine.yield called |
| * lua_yield() |
| */ |
| switch(ctx->co_op) { |
| case NGX_HTTP_LUA_USER_CORO_NOP: |
| dd("hit! it is the API yield"); |
| |
| lua_settop(ctx->cur_co_ctx->co, 0); |
| ctx->cur_co_ctx = NULL; |
| |
| return NGX_AGAIN; |
| |
| case NGX_HTTP_LUA_USER_THREAD_RESUME: |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua user thread resume"); |
| |
| ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP; |
| nrets = lua_gettop(ctx->cur_co_ctx->co) - 1; |
| dd("nrets = %d", nrets); |
| |
| break; |
| |
| case NGX_HTTP_LUA_USER_CORO_RESUME: |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua coroutine: resume"); |
| |
| /* |
| * the target coroutine lies at the base of the |
| * parent's stack |
| */ |
| ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP; |
| |
| old_co = ctx->cur_co_ctx->parent_co_ctx->co; |
| |
| nrets = lua_gettop(old_co); |
| if (nrets) { |
| lua_xmove(old_co, ctx->cur_co_ctx->co, nrets); |
| } |
| |
| break; |
| |
| default: |
| /* ctx->co_op == NGX_HTTP_LUA_USER_CORO_YIELD */ |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua coroutine: yield"); |
| |
| ctx->co_op = NGX_HTTP_LUA_USER_CORO_NOP; |
| |
| if (ngx_http_lua_is_thread(ctx)) { |
| ngx_http_lua_probe_thread_yield(r, ctx->cur_co_ctx->co); |
| |
| lua_settop(ctx->cur_co_ctx->co, 0); |
| |
| ngx_http_lua_probe_info("set co running"); |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_RUNNING; |
| |
| if (ctx->posted_threads) { |
| ngx_http_lua_post_thread(r, ctx, ctx->cur_co_ctx); |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| /* no pending threads, so resume the thread |
| * immediately */ |
| |
| nrets = 0; |
| continue; |
| } |
| |
| /* being a user coroutine that has a parent */ |
| |
| nrets = lua_gettop(ctx->cur_co_ctx->co); |
| |
| next_coctx = ctx->cur_co_ctx->parent_co_ctx; |
| next_co = next_coctx->co; |
| |
| /* |
| * prepare return values for coroutine.resume |
| * (true plus any retvals) |
| */ |
| lua_pushboolean(next_co, 1); |
| |
| if (nrets) { |
| lua_xmove(ctx->cur_co_ctx->co, next_co, nrets); |
| } |
| |
| nrets++; |
| |
| ctx->cur_co_ctx = next_coctx; |
| |
| break; |
| } |
| |
| /* try resuming on the new coroutine again */ |
| continue; |
| |
| case 0: |
| |
| ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 1); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| if (ctx->cur_co_ctx->zombie_child_threads) { |
| ngx_http_lua_cleanup_zombie_child_uthreads(r, L, ctx, |
| ctx->cur_co_ctx); |
| } |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua light thread ended normally"); |
| |
| if (ngx_http_lua_is_entry_thread(ctx)) { |
| |
| lua_settop(L, 0); |
| |
| ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx); |
| |
| dd("uthreads: %d", (int) ctx->uthreads); |
| |
| if (ctx->uthreads) { |
| |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| /* all user threads terminated already */ |
| goto done; |
| } |
| |
| if (ctx->cur_co_ctx->is_uthread) { |
| /* being a user thread */ |
| |
| lua_settop(L, 0); |
| |
| parent_coctx = ctx->cur_co_ctx->parent_co_ctx; |
| |
| if (ngx_http_lua_coroutine_alive(parent_coctx)) { |
| if (ctx->cur_co_ctx->waited_by_parent) { |
| ngx_http_lua_probe_info("parent already waiting"); |
| ctx->cur_co_ctx->waited_by_parent = 0; |
| success = 1; |
| goto user_co_done; |
| } |
| |
| ngx_http_lua_probe_info("parent still alive"); |
| |
| if (ngx_http_lua_post_zombie_thread(r, parent_coctx, |
| ctx->cur_co_ctx) |
| != NGX_OK) |
| { |
| return NGX_ERROR; |
| } |
| |
| lua_pushboolean(ctx->cur_co_ctx->co, 1); |
| lua_insert(ctx->cur_co_ctx->co, 1); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_ZOMBIE; |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx); |
| ctx->uthreads--; |
| |
| if (ctx->uthreads == 0) { |
| if (ngx_http_lua_entry_thread_alive(ctx)) { |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| /* all threads terminated already */ |
| goto done; |
| } |
| |
| /* some other user threads still running */ |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| /* being a user coroutine that has a parent */ |
| |
| success = 1; |
| |
| user_co_done: |
| nrets = lua_gettop(ctx->cur_co_ctx->co); |
| |
| next_coctx = ctx->cur_co_ctx->parent_co_ctx; |
| |
| if (next_coctx == NULL) { |
| /* being a light thread */ |
| goto no_parent; |
| } |
| |
| next_co = next_coctx->co; |
| |
| /* |
| * ended successful, coroutine.resume returns true plus |
| * any return values |
| */ |
| lua_pushboolean(next_co, success); |
| |
| if (nrets) { |
| lua_xmove(ctx->cur_co_ctx->co, next_co, nrets); |
| } |
| |
| if (ctx->cur_co_ctx->is_uthread) { |
| ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx); |
| ctx->uthreads--; |
| } |
| |
| nrets++; |
| ctx->cur_co_ctx = next_coctx; |
| |
| ngx_http_lua_probe_info("set parent running"); |
| |
| next_coctx->co_status = NGX_HTTP_LUA_CO_RUNNING; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua coroutine: lua user thread ended normally"); |
| |
| continue; |
| |
| case LUA_ERRRUN: |
| err = "runtime error"; |
| break; |
| |
| case LUA_ERRSYNTAX: |
| err = "syntax error"; |
| break; |
| |
| case LUA_ERRMEM: |
| err = "memory allocation error"; |
| ngx_quit = 1; |
| break; |
| |
| case LUA_ERRERR: |
| err = "error handler error"; |
| break; |
| |
| default: |
| err = "unknown error"; |
| break; |
| } |
| |
| if (ctx->cur_co_ctx != orig_coctx) { |
| ctx->cur_co_ctx = orig_coctx; |
| } |
| |
| if (lua_isstring(ctx->cur_co_ctx->co, -1)) { |
| dd("user custom error msg"); |
| msg = lua_tostring(ctx->cur_co_ctx->co, -1); |
| |
| } else { |
| msg = "unknown reason"; |
| } |
| |
| ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 0); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| ngx_http_lua_thread_traceback(L, ctx->cur_co_ctx->co, |
| ctx->cur_co_ctx); |
| trace = lua_tostring(L, -1); |
| |
| if (ctx->cur_co_ctx->is_uthread) { |
| |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, |
| "lua user thread aborted: %s: %s\n%s", |
| err, msg, trace); |
| |
| lua_settop(L, 0); |
| |
| parent_coctx = ctx->cur_co_ctx->parent_co_ctx; |
| |
| if (ngx_http_lua_coroutine_alive(parent_coctx)) { |
| if (ctx->cur_co_ctx->waited_by_parent) { |
| ctx->cur_co_ctx->waited_by_parent = 0; |
| success = 0; |
| goto user_co_done; |
| } |
| |
| if (ngx_http_lua_post_zombie_thread(r, parent_coctx, |
| ctx->cur_co_ctx) |
| != NGX_OK) |
| { |
| return NGX_ERROR; |
| } |
| |
| lua_pushboolean(ctx->cur_co_ctx->co, 0); |
| lua_insert(ctx->cur_co_ctx->co, 1); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_ZOMBIE; |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| ngx_http_lua_del_thread(r, L, ctx, ctx->cur_co_ctx); |
| ctx->uthreads--; |
| |
| if (ctx->uthreads == 0) { |
| if (ngx_http_lua_entry_thread_alive(ctx)) { |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| /* all threads terminated already */ |
| goto done; |
| } |
| |
| /* some other user threads still running */ |
| ctx->cur_co_ctx = NULL; |
| return NGX_AGAIN; |
| } |
| |
| if (ngx_http_lua_is_entry_thread(ctx)) { |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, |
| "lua entry thread aborted: %s: %s\n%s", |
| err, msg, trace); |
| |
| lua_settop(L, 0); |
| |
| /* being the entry thread aborted */ |
| |
| if (r->filter_finalize) { |
| ngx_http_set_ctx(r, ctx, ngx_http_lua_module); |
| } |
| |
| ngx_http_lua_request_cleanup(ctx, 0); |
| |
| dd("headers sent? %d", r->header_sent ? 1 : 0); |
| |
| if (ctx->no_abort) { |
| ctx->no_abort = 0; |
| return NGX_ERROR; |
| } |
| |
| return r->header_sent ? NGX_ERROR : |
| NGX_HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| /* being a user coroutine that has a parent */ |
| |
| next_coctx = ctx->cur_co_ctx->parent_co_ctx; |
| if (next_coctx == NULL) { |
| goto no_parent; |
| } |
| |
| next_co = next_coctx->co; |
| |
| ngx_http_lua_probe_info("set parent running"); |
| |
| next_coctx->co_status = NGX_HTTP_LUA_CO_RUNNING; |
| |
| /* |
| * ended with error, coroutine.resume returns false plus |
| * err msg |
| */ |
| lua_pushboolean(next_co, 0); |
| lua_xmove(ctx->cur_co_ctx->co, next_co, 1); |
| nrets = 2; |
| |
| ctx->cur_co_ctx = next_coctx; |
| |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, |
| "lua coroutine: %s: %s\n%s", err, msg, trace); |
| |
| /* try resuming on the new coroutine again */ |
| continue; |
| } |
| |
| } NGX_LUA_EXCEPTION_CATCH { |
| dd("nginx execution restored"); |
| } |
| |
| return NGX_ERROR; |
| |
| no_parent: |
| lua_settop(L, 0); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| if (r->filter_finalize) { |
| ngx_http_set_ctx(r, ctx, ngx_http_lua_module); |
| } |
| |
| ngx_http_lua_request_cleanup(ctx, 0); |
| |
| ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "lua handler aborted: " |
| "user coroutine has no parent"); |
| |
| return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR; |
| |
| done: |
| if (ctx->entered_content_phase && r->connection->fd != -1) { |
| rc = ngx_http_lua_send_chain_link(r, ctx, |
| NULL /* last_buf */); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| } |
| |
| return NGX_OK; |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_wev_handler(ngx_http_request_t *r) |
| { |
| ngx_int_t rc; |
| ngx_event_t *wev; |
| ngx_connection_t *c; |
| ngx_http_lua_ctx_t *ctx; |
| ngx_http_lua_co_ctx_t *coctx; |
| ngx_http_core_loc_conf_t *clcf; |
| |
| ngx_http_lua_socket_tcp_upstream_t *u; |
| |
| c = r->connection; |
| wev = c->write; |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return NGX_ERROR; |
| } |
| |
| ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "lua run write event handler: timedout:%ud, ready:%ud, " |
| "writing_raw_req_socket:%ud", |
| wev->timedout, wev->ready, ctx->writing_raw_req_socket); |
| |
| clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module); |
| |
| if (wev->timedout && !ctx->writing_raw_req_socket) { |
| if (!wev->delayed) { |
| ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, |
| "client timed out"); |
| c->timedout = 1; |
| |
| if (ctx->entered_content_phase) { |
| ngx_http_lua_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); |
| } |
| |
| return NGX_HTTP_REQUEST_TIME_OUT; |
| } |
| |
| wev->timedout = 0; |
| wev->delayed = 0; |
| |
| if (!wev->ready) { |
| ngx_add_timer(wev, clcf->send_timeout); |
| |
| if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { |
| if (ctx->entered_content_phase) { |
| ngx_http_lua_finalize_request(r, NGX_ERROR); |
| } |
| return NGX_ERROR; |
| } |
| } |
| } |
| |
| if (!wev->ready && !wev->timedout) { |
| goto useless; |
| } |
| |
| if (ctx->writing_raw_req_socket) { |
| ctx->writing_raw_req_socket = 0; |
| |
| coctx = ctx->downstream_co_ctx; |
| if (coctx == NULL) { |
| return NGX_ERROR; |
| } |
| |
| u = coctx->data; |
| if (u == NULL) { |
| return NGX_ERROR; |
| } |
| |
| u->write_event_handler(r, u); |
| return NGX_DONE; |
| } |
| |
| if (c->buffered) { |
| rc = ngx_http_lua_flush_pending_output(r, ctx); |
| if (rc != NGX_OK) { |
| return rc; |
| } |
| } |
| |
| if (ctx->flushing_coros) { |
| return ngx_http_lua_process_flushing_coroutines(r, ctx); |
| } |
| |
| /* ctx->flushing_coros == 0 */ |
| |
| useless: |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "useless lua write event handler"); |
| |
| if (ctx->entered_content_phase) { |
| return NGX_OK; |
| } |
| |
| return NGX_DONE; |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_process_flushing_coroutines(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_int_t rc, n; |
| ngx_uint_t i; |
| ngx_list_part_t *part; |
| ngx_http_lua_co_ctx_t *coctx; |
| |
| dd("processing flushing coroutines"); |
| |
| coctx = &ctx->entry_co_ctx; |
| n = ctx->flushing_coros; |
| |
| if (coctx->flushing) { |
| coctx->flushing = 0; |
| |
| ctx->flushing_coros--; |
| n--; |
| ctx->cur_co_ctx = coctx; |
| |
| rc = ngx_http_lua_flush_resume_helper(r, ctx); |
| if (rc == NGX_ERROR || rc >= NGX_OK) { |
| return rc; |
| } |
| |
| /* rc == NGX_DONE */ |
| } |
| |
| if (n) { |
| |
| if (ctx->user_co_ctx == NULL) { |
| return NGX_ERROR; |
| } |
| |
| part = &ctx->user_co_ctx->part; |
| coctx = part->elts; |
| |
| for (i = 0; /* void */; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| coctx = part->elts; |
| i = 0; |
| } |
| |
| if (coctx[i].flushing) { |
| coctx[i].flushing = 0; |
| ctx->cur_co_ctx = &coctx[i]; |
| |
| rc = ngx_http_lua_flush_resume_helper(r, ctx); |
| if (rc == NGX_ERROR || rc >= NGX_OK) { |
| return rc; |
| } |
| |
| /* rc == NGX_DONE */ |
| |
| ctx->flushing_coros--; |
| n--; |
| if (n == 0) { |
| return NGX_DONE; |
| } |
| } |
| } |
| } |
| |
| if (n) { |
| return NGX_ERROR; |
| } |
| |
| return NGX_DONE; |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_flush_pending_output(ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_int_t rc; |
| ngx_chain_t *cl; |
| ngx_event_t *wev; |
| ngx_connection_t *c; |
| |
| ngx_http_core_loc_conf_t *clcf; |
| |
| c = r->connection; |
| wev = c->write; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "lua flushing output: buffered 0x%uxd", |
| c->buffered); |
| |
| rc = ngx_http_lua_output_filter(r, NULL); |
| |
| if (rc == NGX_ERROR || rc > NGX_OK) { |
| if (ctx->entered_content_phase) { |
| ngx_http_lua_finalize_request(r, rc); |
| } |
| |
| return rc; |
| } |
| |
| if (ctx->busy_bufs) { |
| cl = NULL; |
| |
| dd("updating chains..."); |
| |
| #if nginx_version >= 1001004 |
| ngx_chain_update_chains(r->pool, |
| #else |
| ngx_chain_update_chains( |
| #endif |
| &ctx->free_bufs, &ctx->busy_bufs, &cl, |
| (ngx_buf_tag_t) &ngx_http_lua_module); |
| |
| dd("update lua buf tag: %p, buffered: %x, busy bufs: %p", |
| &ngx_http_lua_module, (int) c->buffered, ctx->busy_bufs); |
| } |
| |
| if (c->buffered) { |
| |
| clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module); |
| |
| if (!wev->delayed) { |
| ngx_add_timer(wev, clcf->send_timeout); |
| } |
| |
| if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { |
| if (ctx->entered_content_phase) { |
| ngx_http_lua_finalize_request(r, NGX_ERROR); |
| } |
| |
| return NGX_ERROR; |
| } |
| |
| if (ctx->flushing_coros) { |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "lua flush still waiting: buffered 0x%uxd", |
| c->buffered); |
| |
| return NGX_DONE; |
| } |
| |
| } else { |
| #if 1 |
| if (wev->timer_set) { |
| ngx_del_timer(wev); |
| } |
| #endif |
| } |
| |
| return NGX_OK; |
| } |
| |
| |
| u_char * |
| ngx_http_lua_digest_hex(u_char *dest, const u_char *buf, int buf_len) |
| { |
| ngx_md5_t md5; |
| u_char md5_buf[MD5_DIGEST_LENGTH]; |
| |
| ngx_md5_init(&md5); |
| ngx_md5_update(&md5, buf, buf_len); |
| ngx_md5_final(md5_buf, &md5); |
| |
| return ngx_hex_dump(dest, md5_buf, sizeof(md5_buf)); |
| } |
| |
| |
| void |
| ngx_http_lua_set_multi_value_table(lua_State *L, int index) |
| { |
| if (index < 0) { |
| index = lua_gettop(L) + index + 1; |
| } |
| |
| lua_pushvalue(L, -2); /* stack: table key value key */ |
| lua_rawget(L, index); |
| if (lua_isnil(L, -1)) { |
| lua_pop(L, 1); /* stack: table key value */ |
| lua_rawset(L, index); /* stack: table */ |
| |
| } else { |
| if (!lua_istable(L, -1)) { |
| /* just inserted one value */ |
| lua_createtable(L, 4, 0); |
| /* stack: table key value value table */ |
| lua_insert(L, -2); |
| /* stack: table key value table value */ |
| lua_rawseti(L, -2, 1); |
| /* stack: table key value table */ |
| lua_insert(L, -2); |
| /* stack: table key table value */ |
| |
| lua_rawseti(L, -2, 2); /* stack: table key table */ |
| |
| lua_rawset(L, index); /* stack: table */ |
| |
| } else { |
| /* stack: table key value table */ |
| lua_insert(L, -2); /* stack: table key table value */ |
| |
| lua_rawseti(L, -2, lua_objlen(L, -2) + 1); |
| /* stack: table key table */ |
| lua_pop(L, 2); /* stack: table */ |
| } |
| } |
| } |
| |
| |
| uintptr_t |
| ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) |
| { |
| ngx_uint_t n; |
| uint32_t *escape; |
| static u_char hex[] = "0123456789abcdef"; |
| |
| /* " ", "#", "%", "?", %00-%1F, %7F-%FF */ |
| |
| static uint32_t uri[] = { |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| |
| /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ |
| 0xfc00886d, /* 1111 1100 0000 0000 1000 1000 0110 1101 */ |
| |
| /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ |
| 0x78000000, /* 0111 1000 0000 0000 0000 0000 0000 0000 */ |
| |
| /* ~}| {zyx wvut srqp onml kjih gfed cba` */ |
| 0xa8000000, /* 1010 1000 0000 0000 0000 0000 0000 0000 */ |
| |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| }; |
| |
| /* " ", "#", "%", "+", "?", %00-%1F, %7F-%FF */ |
| |
| static uint32_t args[] = { |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| |
| /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ |
| 0x80000829, /* 1000 0000 0000 0000 0000 1000 0010 1001 */ |
| |
| /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| /* ~}| {zyx wvut srqp onml kjih gfed cba` */ |
| 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| }; |
| |
| /* " ", "#", """, "%", "'", %00-%1F, %7F-%FF */ |
| |
| static uint32_t html[] = { |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| |
| /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ |
| 0x000000ad, /* 0000 0000 0000 0000 0000 0000 1010 1101 */ |
| |
| /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| /* ~}| {zyx wvut srqp onml kjih gfed cba` */ |
| 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| }; |
| |
| /* " ", """, "%", "'", %00-%1F, %7F-%FF */ |
| |
| static uint32_t refresh[] = { |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| |
| /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ |
| 0x00000085, /* 0000 0000 0000 0000 0000 0000 1000 0101 */ |
| |
| /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| /* ~}| {zyx wvut srqp onml kjih gfed cba` */ |
| 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| }; |
| |
| /* " ", "%", %00-%1F */ |
| |
| static uint32_t memcached[] = { |
| 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ |
| |
| /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ |
| 0x00000021, /* 0000 0000 0000 0000 0000 0000 0010 0001 */ |
| |
| /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| /* ~}| {zyx wvut srqp onml kjih gfed cba` */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ |
| }; |
| |
| /* mail_auth is the same as memcached */ |
| |
| static uint32_t *map[] = |
| { uri, args, html, refresh, memcached, memcached }; |
| |
| |
| escape = map[type]; |
| |
| if (dst == NULL) { |
| |
| /* find the number of the characters to be escaped */ |
| |
| n = 0; |
| |
| while (size) { |
| if (escape[*src >> 5] & (1 << (*src & 0x1f))) { |
| n++; |
| } |
| src++; |
| size--; |
| } |
| |
| return (uintptr_t) n; |
| } |
| |
| while (size) { |
| if (escape[*src >> 5] & (1 << (*src & 0x1f))) { |
| *dst++ = '%'; |
| *dst++ = hex[*src >> 4]; |
| *dst++ = hex[*src & 0xf]; |
| src++; |
| |
| } else { |
| *dst++ = *src++; |
| } |
| size--; |
| } |
| |
| return (uintptr_t) dst; |
| } |
| |
| |
| /* XXX we also decode '+' to ' ' */ |
| void |
| ngx_http_lua_unescape_uri(u_char **dst, u_char **src, size_t size, |
| ngx_uint_t type) |
| { |
| u_char *d, *s, ch, c, decoded; |
| enum { |
| sw_usual = 0, |
| sw_quoted, |
| sw_quoted_second |
| } state; |
| |
| d = *dst; |
| s = *src; |
| |
| state = 0; |
| decoded = 0; |
| |
| while (size--) { |
| |
| ch = *s++; |
| |
| switch (state) { |
| case sw_usual: |
| if (ch == '?' |
| && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT))) |
| { |
| *d++ = ch; |
| goto done; |
| } |
| |
| if (ch == '%') { |
| state = sw_quoted; |
| break; |
| } |
| |
| if (ch == '+') { |
| *d++ = ' '; |
| break; |
| } |
| |
| *d++ = ch; |
| break; |
| |
| case sw_quoted: |
| |
| if (ch >= '0' && ch <= '9') { |
| decoded = (u_char) (ch - '0'); |
| state = sw_quoted_second; |
| break; |
| } |
| |
| c = (u_char) (ch | 0x20); |
| if (c >= 'a' && c <= 'f') { |
| decoded = (u_char) (c - 'a' + 10); |
| state = sw_quoted_second; |
| break; |
| } |
| |
| /* the invalid quoted character */ |
| |
| state = sw_usual; |
| |
| *d++ = ch; |
| |
| break; |
| |
| case sw_quoted_second: |
| |
| state = sw_usual; |
| |
| if (ch >= '0' && ch <= '9') { |
| ch = (u_char) ((decoded << 4) + ch - '0'); |
| |
| if (type & NGX_UNESCAPE_REDIRECT) { |
| if (ch > '%' && ch < 0x7f) { |
| *d++ = ch; |
| break; |
| } |
| |
| *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1); |
| break; |
| } |
| |
| *d++ = ch; |
| |
| break; |
| } |
| |
| c = (u_char) (ch | 0x20); |
| if (c >= 'a' && c <= 'f') { |
| ch = (u_char) ((decoded << 4) + c - 'a' + 10); |
| |
| if (type & NGX_UNESCAPE_URI) { |
| if (ch == '?') { |
| *d++ = ch; |
| goto done; |
| } |
| |
| *d++ = ch; |
| break; |
| } |
| |
| if (type & NGX_UNESCAPE_REDIRECT) { |
| if (ch == '?') { |
| *d++ = ch; |
| goto done; |
| } |
| |
| if (ch > '%' && ch < 0x7f) { |
| *d++ = ch; |
| break; |
| } |
| |
| *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1); |
| break; |
| } |
| |
| *d++ = ch; |
| |
| break; |
| } |
| |
| /* the invalid quoted character */ |
| |
| break; |
| } |
| } |
| |
| done: |
| |
| *dst = d; |
| *src = s; |
| } |
| |
| |
| void |
| ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L) |
| { |
| /* ngx.req table */ |
| |
| lua_createtable(L, 0 /* narr */, 23 /* nrec */); /* .req */ |
| |
| ngx_http_lua_inject_req_header_api(log, L); |
| ngx_http_lua_inject_req_uri_api(log, L); |
| ngx_http_lua_inject_req_args_api(L); |
| ngx_http_lua_inject_req_body_api(L); |
| ngx_http_lua_inject_req_socket_api(L); |
| ngx_http_lua_inject_req_method_api(L); |
| ngx_http_lua_inject_req_time_api(L); |
| |
| lua_setfield(L, -2, "req"); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_handle_exec(lua_State *L, ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_int_t rc; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua thread initiated internal redirect to %V", |
| &ctx->exec_uri); |
| |
| ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 1); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| if (r->filter_finalize) { |
| ngx_http_set_ctx(r, ctx, ngx_http_lua_module); |
| } |
| |
| ngx_http_lua_request_cleanup(ctx, 1 /* forcible */); |
| |
| if (ctx->exec_uri.data[0] == '@') { |
| if (ctx->exec_args.len > 0) { |
| ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, |
| "query strings %V ignored when exec'ing " |
| "named location %V", |
| &ctx->exec_args, &ctx->exec_uri); |
| } |
| |
| r->write_event_handler = ngx_http_request_empty_handler; |
| |
| #if 1 |
| if (r->read_event_handler == ngx_http_lua_rd_check_broken_connection) { |
| /* resume the read event handler */ |
| |
| r->read_event_handler = ngx_http_block_reading; |
| } |
| #endif |
| |
| #if 1 |
| /* clear the modules contexts */ |
| ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module); |
| #endif |
| |
| rc = ngx_http_named_location(r, &ctx->exec_uri); |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) |
| { |
| return rc; |
| } |
| |
| #if 0 |
| if (!ctx->entered_content_phase) { |
| /* XXX ensure the main request ref count |
| * is decreased because the current |
| * request will be quit */ |
| r->main->count--; |
| dd("XXX decrement main count: c:%d", (int) r->main->count); |
| } |
| #endif |
| |
| return NGX_DONE; |
| } |
| |
| dd("internal redirect to %.*s", (int) ctx->exec_uri.len, |
| ctx->exec_uri.data); |
| |
| r->write_event_handler = ngx_http_request_empty_handler; |
| |
| if (r->read_event_handler == ngx_http_lua_rd_check_broken_connection) { |
| /* resume the read event handler */ |
| |
| r->read_event_handler = ngx_http_block_reading; |
| } |
| |
| rc = ngx_http_internal_redirect(r, &ctx->exec_uri, &ctx->exec_args); |
| |
| dd("internal redirect returned %d when in content phase? " |
| "%d", (int) rc, ctx->entered_content_phase); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| |
| #if 0 |
| if (!ctx->entered_content_phase) { |
| /* XXX ensure the main request ref count |
| * is decreased because the current |
| * request will be quit */ |
| dd("XXX decrement main count"); |
| r->main->count--; |
| } |
| #endif |
| |
| return NGX_DONE; |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_handle_exit(lua_State *L, ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_int_t rc; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua thread aborting request with status %d", |
| ctx->exit_code); |
| |
| #if 1 |
| if (!r->header_sent |
| && r->headers_out.status == 0 |
| && ctx->exit_code >= NGX_HTTP_OK) |
| { |
| r->headers_out.status = ctx->exit_code; |
| } |
| #endif |
| |
| ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 1); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| if (r->filter_finalize) { |
| ngx_http_set_ctx(r, ctx, ngx_http_lua_module); |
| } |
| |
| ngx_http_lua_request_cleanup(ctx, 0); |
| |
| if (ctx->buffering |
| && r->headers_out.status |
| && ctx->exit_code != NGX_ERROR |
| && ctx->exit_code != NGX_HTTP_REQUEST_TIME_OUT |
| && ctx->exit_code != NGX_HTTP_CLIENT_CLOSED_REQUEST |
| && ctx->exit_code != NGX_HTTP_CLOSE) |
| { |
| rc = ngx_http_lua_send_chain_link(r, ctx, NULL /* indicate last_buf */); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| |
| if (ctx->exit_code >= NGX_HTTP_OK) { |
| return NGX_HTTP_OK; |
| } |
| |
| return ctx->exit_code; |
| } |
| |
| if ((ctx->exit_code == NGX_OK |
| && ctx->entered_content_phase) |
| || (ctx->exit_code >= NGX_HTTP_OK |
| && ctx->exit_code < NGX_HTTP_SPECIAL_RESPONSE |
| && ctx->exit_code != NGX_HTTP_NO_CONTENT)) |
| { |
| rc = ngx_http_lua_send_chain_link(r, ctx, NULL /* indicate last_buf */); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| return rc; |
| } |
| } |
| |
| #if 1 |
| if (r->header_sent |
| && ctx->exit_code > NGX_OK |
| && ctx->exit_code != NGX_HTTP_REQUEST_TIME_OUT |
| && ctx->exit_code != NGX_HTTP_CLIENT_CLOSED_REQUEST |
| && ctx->exit_code != NGX_HTTP_CLOSE) |
| { |
| if (ctx->entered_content_phase) { |
| return NGX_OK; |
| } |
| |
| return NGX_HTTP_OK; |
| } |
| #endif |
| |
| return ctx->exit_code; |
| } |
| |
| |
| void |
| ngx_http_lua_process_args_option(ngx_http_request_t *r, lua_State *L, |
| int table, ngx_str_t *args) |
| { |
| u_char *key; |
| size_t key_len; |
| u_char *value; |
| size_t value_len; |
| size_t len = 0; |
| size_t key_escape = 0; |
| uintptr_t total_escape = 0; |
| int n; |
| int i; |
| u_char *p; |
| |
| if (table < 0) { |
| table = lua_gettop(L) + table + 1; |
| } |
| |
| n = 0; |
| 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 " |
| "\"args\" option table"); |
| return; |
| } |
| |
| key = (u_char *) lua_tolstring(L, -2, &key_len); |
| |
| key_escape = 2 * ngx_http_lua_escape_uri(NULL, key, key_len, |
| NGX_ESCAPE_URI); |
| total_escape += key_escape; |
| |
| switch (lua_type(L, -1)) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| value = (u_char *) lua_tolstring(L, -1, &value_len); |
| |
| total_escape += 2 * ngx_http_lua_escape_uri(NULL, value, value_len, |
| NGX_ESCAPE_URI); |
| |
| len += key_len + value_len + (sizeof("=") - 1); |
| n++; |
| |
| break; |
| |
| case LUA_TBOOLEAN: |
| if (lua_toboolean(L, -1)) { |
| len += key_len; |
| n++; |
| } |
| |
| break; |
| |
| case LUA_TTABLE: |
| |
| i = 0; |
| lua_pushnil(L); |
| while (lua_next(L, -2) != 0) { |
| if (lua_isboolean(L, -1)) { |
| if (lua_toboolean(L, -1)) { |
| len += key_len; |
| |
| } else { |
| lua_pop(L, 1); |
| continue; |
| } |
| |
| } else { |
| value = (u_char *) lua_tolstring(L, -1, &value_len); |
| |
| if (value == NULL) { |
| luaL_error(L, "attempt to use %s as query arg value", |
| luaL_typename(L, -1)); |
| return; |
| } |
| |
| total_escape += |
| 2 * ngx_http_lua_escape_uri(NULL, value, |
| value_len, |
| NGX_ESCAPE_URI); |
| |
| len += key_len + value_len + (sizeof("=") - 1); |
| } |
| |
| if (i++ > 0) { |
| total_escape += key_escape; |
| } |
| |
| n++; |
| lua_pop(L, 1); |
| } |
| |
| break; |
| |
| default: |
| luaL_error(L, "attempt to use %s as query arg value", |
| luaL_typename(L, -1)); |
| return; |
| } |
| |
| lua_pop(L, 1); |
| } |
| |
| len += (size_t) total_escape; |
| |
| if (n > 1) { |
| len += (n - 1) * (sizeof("&") - 1); |
| } |
| |
| dd("len 1: %d", (int) len); |
| |
| if (r) { |
| p = ngx_palloc(r->pool, len); |
| if (p == NULL) { |
| luaL_error(L, "out of memory"); |
| return; |
| } |
| |
| } else { |
| p = lua_newuserdata(L, len); |
| } |
| |
| args->data = p; |
| args->len = len; |
| |
| i = 0; |
| lua_pushnil(L); |
| while (lua_next(L, table) != 0) { |
| key = (u_char *) lua_tolstring(L, -2, &key_len); |
| |
| switch (lua_type(L, -1)) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| |
| if (total_escape) { |
| p = (u_char *) ngx_http_lua_escape_uri(p, key, key_len, |
| NGX_ESCAPE_URI); |
| |
| } else { |
| dd("shortcut: no escape required"); |
| |
| p = ngx_copy(p, key, key_len); |
| } |
| |
| *p++ = '='; |
| |
| value = (u_char *) lua_tolstring(L, -1, &value_len); |
| |
| if (total_escape) { |
| p = (u_char *) ngx_http_lua_escape_uri(p, value, value_len, |
| NGX_ESCAPE_URI); |
| |
| } else { |
| p = ngx_copy(p, value, value_len); |
| } |
| |
| if (i != n - 1) { |
| /* not the last pair */ |
| *p++ = '&'; |
| } |
| |
| i++; |
| |
| break; |
| |
| case LUA_TBOOLEAN: |
| if (lua_toboolean(L, -1)) { |
| if (total_escape) { |
| p = (u_char *) ngx_http_lua_escape_uri(p, key, key_len, |
| NGX_ESCAPE_URI); |
| |
| } else { |
| dd("shortcut: no escape required"); |
| |
| p = ngx_copy(p, key, key_len); |
| } |
| |
| if (i != n - 1) { |
| /* not the last pair */ |
| *p++ = '&'; |
| } |
| |
| i++; |
| } |
| |
| break; |
| |
| case LUA_TTABLE: |
| |
| lua_pushnil(L); |
| while (lua_next(L, -2) != 0) { |
| |
| if (lua_isboolean(L, -1)) { |
| if (lua_toboolean(L, -1)) { |
| if (total_escape) { |
| p = (u_char *) ngx_http_lua_escape_uri(p, key, |
| key_len, |
| NGX_ESCAPE_URI); |
| |
| } else { |
| dd("shortcut: no escape required"); |
| |
| p = ngx_copy(p, key, key_len); |
| } |
| |
| } else { |
| lua_pop(L, 1); |
| continue; |
| } |
| |
| } else { |
| |
| if (total_escape) { |
| p = (u_char *) |
| ngx_http_lua_escape_uri(p, key, |
| key_len, |
| NGX_ESCAPE_URI); |
| |
| } else { |
| dd("shortcut: no escape required"); |
| |
| p = ngx_copy(p, key, key_len); |
| } |
| |
| *p++ = '='; |
| |
| value = (u_char *) lua_tolstring(L, -1, &value_len); |
| |
| if (total_escape) { |
| p = (u_char *) |
| ngx_http_lua_escape_uri(p, value, |
| value_len, |
| NGX_ESCAPE_URI); |
| |
| } else { |
| p = ngx_copy(p, value, value_len); |
| } |
| } |
| |
| if (i != n - 1) { |
| /* not the last pair */ |
| *p++ = '&'; |
| } |
| |
| i++; |
| lua_pop(L, 1); |
| } |
| |
| break; |
| |
| default: |
| luaL_error(L, "should not reach here"); |
| return; |
| } |
| |
| lua_pop(L, 1); |
| } |
| |
| if (p - args->data != (ssize_t) len) { |
| luaL_error(L, "buffer error: %d != %d", |
| (int) (p - args->data), (int) len); |
| return; |
| } |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_handle_rewrite_jump(lua_State *L, ngx_http_request_t *r, |
| ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua thread aborting request with URI rewrite jump: " |
| "\"%V?%V\"", &r->uri, &r->args); |
| |
| ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 1); |
| |
| ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| |
| if (r->filter_finalize) { |
| ngx_http_set_ctx(r, ctx, ngx_http_lua_module); |
| } |
| |
| ngx_http_lua_request_cleanup(ctx, 1 /* forcible */); |
| ngx_http_lua_init_ctx(r, ctx); |
| |
| return NGX_OK; |
| } |
| |
| |
| /* XXX ngx_open_and_stat_file is static in the core. sigh. */ |
| ngx_int_t |
| ngx_http_lua_open_and_stat_file(u_char *name, ngx_open_file_info_t *of, |
| ngx_log_t *log) |
| { |
| ngx_fd_t fd; |
| ngx_file_info_t fi; |
| |
| if (of->fd != NGX_INVALID_FILE) { |
| |
| if (ngx_file_info(name, &fi) == NGX_FILE_ERROR) { |
| of->failed = ngx_file_info_n; |
| goto failed; |
| } |
| |
| if (of->uniq == ngx_file_uniq(&fi)) { |
| goto done; |
| } |
| |
| } else if (of->test_dir) { |
| |
| if (ngx_file_info(name, &fi) == NGX_FILE_ERROR) { |
| of->failed = ngx_file_info_n; |
| goto failed; |
| } |
| |
| if (ngx_is_dir(&fi)) { |
| goto done; |
| } |
| } |
| |
| if (!of->log) { |
| |
| /* |
| * Use non-blocking open() not to hang on FIFO files, etc. |
| * This flag has no effect on a regular files. |
| */ |
| |
| fd = ngx_open_file(name, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, |
| NGX_FILE_OPEN, 0); |
| |
| } else { |
| fd = ngx_open_file(name, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN, |
| NGX_FILE_DEFAULT_ACCESS); |
| } |
| |
| if (fd == NGX_INVALID_FILE) { |
| of->failed = ngx_open_file_n; |
| goto failed; |
| } |
| |
| if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) { |
| ngx_log_error(NGX_LOG_CRIT, log, ngx_errno, |
| ngx_fd_info_n " \"%s\" failed", name); |
| |
| if (ngx_close_file(fd) == NGX_FILE_ERROR) { |
| ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, |
| ngx_close_file_n " \"%s\" failed", name); |
| } |
| |
| of->fd = NGX_INVALID_FILE; |
| |
| return NGX_ERROR; |
| } |
| |
| if (ngx_is_dir(&fi)) { |
| if (ngx_close_file(fd) == NGX_FILE_ERROR) { |
| ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, |
| ngx_close_file_n " \"%s\" failed", name); |
| } |
| |
| of->fd = NGX_INVALID_FILE; |
| |
| } else { |
| of->fd = fd; |
| |
| if (of->directio <= ngx_file_size(&fi)) { |
| if (ngx_directio_on(fd) == NGX_FILE_ERROR) { |
| ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, |
| ngx_directio_on_n " \"%s\" failed", name); |
| |
| } else { |
| of->is_directio = 1; |
| } |
| } |
| } |
| |
| done: |
| |
| of->uniq = ngx_file_uniq(&fi); |
| of->mtime = ngx_file_mtime(&fi); |
| of->size = ngx_file_size(&fi); |
| #if defined(nginx_version) && nginx_version >= 1000001 |
| of->fs_size = ngx_file_fs_size(&fi); |
| #endif |
| of->is_dir = ngx_is_dir(&fi); |
| of->is_file = ngx_is_file(&fi); |
| of->is_link = ngx_is_link(&fi); |
| of->is_exec = ngx_is_exec(&fi); |
| |
| return NGX_OK; |
| |
| failed: |
| |
| of->fd = NGX_INVALID_FILE; |
| of->err = ngx_errno; |
| |
| return NGX_ERROR; |
| } |
| |
| |
| ngx_chain_t * |
| ngx_http_lua_chains_get_free_buf(ngx_log_t *log, ngx_pool_t *p, |
| ngx_chain_t **free, size_t len, ngx_buf_tag_t tag) |
| { |
| ngx_chain_t *cl; |
| ngx_buf_t *b; |
| |
| if (*free) { |
| cl = *free; |
| *free = cl->next; |
| cl->next = NULL; |
| |
| b = cl->buf; |
| if ((size_t) (b->end - b->start) >= len) { |
| ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua reuse free buf memory %O >= %uz, cl:%p, p:%p", |
| (off_t) (b->end - b->start), len, cl, b->start); |
| |
| b->pos = b->start; |
| b->last = b->start; |
| b->tag = tag; |
| return cl; |
| } |
| |
| ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua reuse free buf chain, but reallocate memory " |
| "because %uz >= %O, cl:%p, p:%p", len, |
| (off_t) (b->end - b->start), cl, b->start); |
| |
| if (ngx_buf_in_memory(b) && b->start) { |
| ngx_pfree(p, b->start); |
| } |
| |
| if (len) { |
| b->start = ngx_palloc(p, len); |
| if (b->start == NULL) { |
| return NULL; |
| } |
| |
| b->end = b->start + len; |
| |
| } else { |
| b->last = NULL; |
| b->end = NULL; |
| } |
| |
| dd("buf start: %p", cl->buf->start); |
| |
| b->pos = b->start; |
| b->last = b->start; |
| b->tag = tag; |
| |
| return cl; |
| } |
| |
| cl = ngx_alloc_chain_link(p); |
| if (cl == NULL) { |
| return NULL; |
| } |
| |
| ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, |
| "lua allocate new chainlink and new buf of size %uz, cl:%p", |
| len, cl); |
| |
| cl->buf = ngx_create_temp_buf(p, len); |
| if (cl->buf == NULL) { |
| return NULL; |
| } |
| |
| dd("buf start: %p", cl->buf->start); |
| |
| cl->buf->tag = tag; |
| cl->next = NULL; |
| |
| return cl; |
| } |
| |
| |
| static int |
| ngx_http_lua_thread_traceback(lua_State *L, lua_State *co, |
| ngx_http_lua_co_ctx_t *coctx) |
| { |
| int base; |
| int level, coid; |
| lua_Debug ar; |
| |
| base = lua_gettop(L); |
| lua_pushliteral(L, "stack traceback:"); |
| coid = 0; |
| |
| while (co) { |
| |
| if (coid >= NGX_HTTP_LUA_BT_MAX_COROS) { |
| break; |
| } |
| |
| lua_pushfstring(L, "\ncoroutine %d:", coid++); |
| |
| level = 0; |
| |
| while (lua_getstack(co, level++, &ar)) { |
| |
| if (level > NGX_HTTP_LUA_BT_DEPTH) { |
| lua_pushliteral(L, "\n\t..."); |
| break; |
| } |
| |
| lua_pushliteral(L, "\n\t"); |
| lua_getinfo(co, "Snl", &ar); |
| lua_pushfstring(L, "%s:", ar.short_src); |
| |
| if (ar.currentline > 0) { |
| lua_pushfstring(L, "%d:", ar.currentline); |
| } |
| |
| if (*ar.namewhat != '\0') { /* is there a name? */ |
| lua_pushfstring(L, " in function " LUA_QS, ar.name); |
| |
| } else { |
| if (*ar.what == 'm') { /* main? */ |
| lua_pushfstring(L, " in main chunk"); |
| |
| } else if (*ar.what == 'C' || *ar.what == 't') { |
| lua_pushliteral(L, " ?"); /* C function or tail call */ |
| |
| } else { |
| lua_pushfstring(L, " in function <%s:%d>", |
| ar.short_src, ar.linedefined); |
| } |
| } |
| } |
| |
| if (lua_gettop(L) - base >= 15) { |
| lua_concat(L, lua_gettop(L) - base); |
| } |
| |
| /* check if the coroutine has a parent coroutine*/ |
| coctx = coctx->parent_co_ctx; |
| if (!coctx || coctx->co_status == NGX_HTTP_LUA_CO_DEAD) { |
| break; |
| } |
| |
| co = coctx->co; |
| } |
| |
| lua_concat(L, lua_gettop(L) - base); |
| return 1; |
| } |
| |
| |
| int |
| ngx_http_lua_traceback(lua_State *L) |
| { |
| if (!lua_isstring(L, 1)) { /* 'message' not a string? */ |
| return 1; /* keep it intact */ |
| } |
| |
| lua_getfield(L, LUA_GLOBALSINDEX, "debug"); |
| if (!lua_istable(L, -1)) { |
| lua_pop(L, 1); |
| return 1; |
| } |
| |
| lua_getfield(L, -1, "traceback"); |
| if (!lua_isfunction(L, -1)) { |
| lua_pop(L, 2); |
| return 1; |
| } |
| |
| lua_pushvalue(L, 1); /* pass error message */ |
| lua_pushinteger(L, 2); /* skip this function and traceback */ |
| lua_call(L, 2, 1); /* call debug.traceback */ |
| return 1; |
| } |
| |
| |
| static void |
| ngx_http_lua_inject_arg_api(lua_State *L) |
| { |
| lua_pushliteral(L, "arg"); |
| lua_newtable(L); /* .arg table aka {} */ |
| |
| lua_createtable(L, 0 /* narr */, 2 /* nrec */); /* the metatable */ |
| |
| lua_pushcfunction(L, ngx_http_lua_param_get); |
| lua_setfield(L, -2, "__index"); |
| |
| lua_pushcfunction(L, ngx_http_lua_param_set); |
| lua_setfield(L, -2, "__newindex"); |
| |
| lua_setmetatable(L, -2); /* tie the metatable to param table */ |
| |
| dd("top: %d, type -1: %s", lua_gettop(L), luaL_typename(L, -1)); |
| |
| lua_rawset(L, -3); /* set ngx.arg table */ |
| } |
| |
| |
| static int |
| ngx_http_lua_param_get(lua_State *L) |
| { |
| ngx_http_lua_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return 0; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return luaL_error(L, "ctx not found"); |
| } |
| |
| ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_SET |
| | NGX_HTTP_LUA_CONTEXT_BODY_FILTER); |
| |
| if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SET)) { |
| return ngx_http_lua_setby_param_get(L); |
| } |
| |
| /* ctx->context & (NGX_HTTP_LUA_CONTEXT_BODY_FILTER) */ |
| |
| return ngx_http_lua_body_filter_param_get(L); |
| } |
| |
| |
| static int |
| ngx_http_lua_param_set(lua_State *L) |
| { |
| ngx_http_lua_ctx_t *ctx; |
| ngx_http_request_t *r; |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return 0; |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return luaL_error(L, "ctx not found"); |
| } |
| |
| ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_BODY_FILTER); |
| |
| return ngx_http_lua_body_filter_param_set(L, r, ctx); |
| } |
| |
| |
| ngx_http_lua_co_ctx_t * |
| ngx_http_lua_get_co_ctx(lua_State *L, ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_uint_t i; |
| ngx_list_part_t *part; |
| ngx_http_lua_co_ctx_t *coctx; |
| |
| if (L == ctx->entry_co_ctx.co) { |
| return &ctx->entry_co_ctx; |
| } |
| |
| if (ctx->user_co_ctx == NULL) { |
| return NULL; |
| } |
| |
| part = &ctx->user_co_ctx->part; |
| coctx = part->elts; |
| |
| /* FIXME: we should use rbtree here to prevent O(n) lookup overhead */ |
| |
| for (i = 0; /* void */; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| coctx = part->elts; |
| i = 0; |
| } |
| |
| if (coctx[i].co == L) { |
| return &coctx[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| ngx_http_lua_co_ctx_t * |
| ngx_http_lua_create_co_ctx(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_http_lua_co_ctx_t *coctx; |
| |
| if (ctx->user_co_ctx == NULL) { |
| ctx->user_co_ctx = ngx_list_create(r->pool, 4, |
| sizeof(ngx_http_lua_co_ctx_t)); |
| if (ctx->user_co_ctx == NULL) { |
| return NULL; |
| } |
| } |
| |
| coctx = ngx_list_push(ctx->user_co_ctx); |
| if (coctx == NULL) { |
| return NULL; |
| } |
| |
| ngx_memzero(coctx, sizeof(ngx_http_lua_co_ctx_t)); |
| |
| coctx->co_ref = LUA_NOREF; |
| |
| return coctx; |
| } |
| |
| |
| /* this is for callers other than the content handler */ |
| ngx_int_t |
| ngx_http_lua_run_posted_threads(ngx_connection_t *c, lua_State *L, |
| ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_int_t rc; |
| ngx_http_lua_posted_thread_t *pt; |
| |
| for ( ;; ) { |
| if (c->destroyed) { |
| return NGX_DONE; |
| } |
| |
| pt = ctx->posted_threads; |
| if (pt == NULL) { |
| return NGX_DONE; |
| } |
| |
| ctx->posted_threads = pt->next; |
| |
| ngx_http_lua_probe_run_posted_thread(r, pt->co_ctx->co, |
| (int) pt->co_ctx->co_status); |
| |
| if (pt->co_ctx->co_status != NGX_HTTP_LUA_CO_RUNNING) { |
| continue; |
| } |
| |
| ctx->cur_co_ctx = pt->co_ctx; |
| |
| rc = ngx_http_lua_run_thread(L, r, ctx, 0); |
| |
| if (rc == NGX_AGAIN) { |
| continue; |
| } |
| |
| if (rc == NGX_DONE) { |
| ngx_http_lua_finalize_request(r, NGX_DONE); |
| continue; |
| } |
| |
| /* rc == NGX_ERROR || rc >= NGX_OK */ |
| |
| if (ctx->entered_content_phase) { |
| ngx_http_lua_finalize_request(r, rc); |
| } |
| |
| return rc; |
| } |
| |
| /* impossible to reach here */ |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_post_thread(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, |
| ngx_http_lua_co_ctx_t *coctx) |
| { |
| ngx_http_lua_posted_thread_t **p; |
| ngx_http_lua_posted_thread_t *pt; |
| |
| pt = ngx_palloc(r->pool, sizeof(ngx_http_lua_posted_thread_t)); |
| if (pt == NULL) { |
| return NGX_ERROR; |
| } |
| |
| pt->co_ctx = coctx; |
| pt->next = NULL; |
| |
| for (p = &ctx->posted_threads; *p; p = &(*p)->next) { /* void */ } |
| |
| *p = pt; |
| |
| return NGX_OK; |
| } |
| |
| |
| static void |
| ngx_http_lua_finalize_coroutines(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) |
| { |
| ngx_http_lua_co_ctx_t *cc, *coctx; |
| ngx_list_part_t *part; |
| ngx_uint_t i; |
| |
| if (ctx->uthreads == 0) { |
| if (ngx_http_lua_is_entry_thread(ctx)) { |
| return; |
| } |
| |
| /* the current thread is not the entry thread */ |
| |
| if (ctx->entry_co_ctx.co_status == NGX_HTTP_LUA_CO_DEAD) { |
| return; |
| } |
| } |
| |
| if (ctx->user_co_ctx) { |
| part = &ctx->user_co_ctx->part; |
| cc = part->elts; |
| |
| for (i = 0; /* void */; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| cc = part->elts; |
| i = 0; |
| } |
| |
| coctx = &cc[i]; |
| if (coctx->cleanup) { |
| coctx->cleanup(coctx); |
| coctx->cleanup = NULL; |
| coctx->co_status = NGX_HTTP_LUA_CO_DEAD; |
| /* TODO we could also free the user thread here */ |
| } |
| } |
| } |
| |
| coctx = &ctx->entry_co_ctx; |
| if (coctx->cleanup) { |
| coctx->cleanup(coctx); |
| } |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_post_zombie_thread(ngx_http_request_t *r, |
| ngx_http_lua_co_ctx_t *parent, ngx_http_lua_co_ctx_t *thread) |
| { |
| ngx_http_lua_posted_thread_t **p; |
| ngx_http_lua_posted_thread_t *pt; |
| |
| pt = ngx_palloc(r->pool, sizeof(ngx_http_lua_posted_thread_t)); |
| if (pt == NULL) { |
| return NGX_ERROR; |
| } |
| |
| pt->co_ctx = thread; |
| pt->next = NULL; |
| |
| for (p = &parent->zombie_child_threads; *p; p = &(*p)->next) { /* void */ } |
| |
| *p = pt; |
| |
| return NGX_OK; |
| } |
| |
| |
| static void |
| ngx_http_lua_cleanup_zombie_child_uthreads(ngx_http_request_t *r, |
| lua_State *L, ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t *coctx) |
| { |
| ngx_http_lua_posted_thread_t *pt; |
| |
| for (pt = coctx->zombie_child_threads; pt; pt = pt->next) { |
| if (pt->co_ctx->co_ref != LUA_NOREF) { |
| ngx_http_lua_del_thread(r, L, ctx, pt->co_ctx); |
| ctx->uthreads--; |
| } |
| } |
| |
| coctx->zombie_child_threads = NULL; |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_check_broken_connection(ngx_http_request_t *r, ngx_event_t *ev) |
| { |
| int n; |
| char buf[1]; |
| ngx_err_t err; |
| ngx_int_t event; |
| ngx_connection_t *c; |
| |
| ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0, |
| "http lua check client, write event:%d, \"%V\"", |
| ev->write, &r->uri); |
| |
| c = r->connection; |
| |
| if (c->error) { |
| if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) { |
| |
| event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT; |
| |
| if (ngx_del_event(ev, event, 0) != NGX_OK) { |
| return NGX_HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| |
| return NGX_HTTP_CLIENT_CLOSED_REQUEST; |
| } |
| |
| #if (NGX_HAVE_KQUEUE) |
| |
| if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { |
| |
| if (!ev->pending_eof) { |
| return NGX_OK; |
| } |
| |
| ev->eof = 1; |
| |
| if (ev->kq_errno) { |
| ev->error = 1; |
| } |
| |
| ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno, |
| "kevent() reported that client prematurely closed " |
| "connection"); |
| |
| return NGX_HTTP_CLIENT_CLOSED_REQUEST; |
| } |
| |
| #endif |
| |
| n = recv(c->fd, buf, 1, MSG_PEEK); |
| |
| err = ngx_socket_errno; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, err, |
| "http lua recv(): %d", n); |
| |
| if (ev->write && (n >= 0 || err == NGX_EAGAIN)) { |
| return NGX_OK; |
| } |
| |
| if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) { |
| dd("event is active"); |
| |
| event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT; |
| |
| #if 1 |
| if (ngx_del_event(ev, event, 0) != NGX_OK) { |
| return NGX_HTTP_INTERNAL_SERVER_ERROR; |
| } |
| #endif |
| } |
| |
| dd("HERE %d", (int) n); |
| |
| if (n > 0) { |
| return NGX_OK; |
| } |
| |
| if (n == -1) { |
| if (err == NGX_EAGAIN) { |
| dd("HERE"); |
| return NGX_OK; |
| } |
| |
| ev->error = 1; |
| |
| } else { /* n == 0 */ |
| err = 0; |
| } |
| |
| ev->eof = 1; |
| |
| ngx_log_error(NGX_LOG_INFO, ev->log, err, |
| "client prematurely closed connection"); |
| |
| return NGX_HTTP_CLIENT_CLOSED_REQUEST; |
| } |
| |
| |
| void |
| ngx_http_lua_rd_check_broken_connection(ngx_http_request_t *r) |
| { |
| ngx_int_t rc; |
| ngx_event_t *rev; |
| ngx_http_lua_ctx_t *ctx; |
| |
| if (r->done) { |
| return; |
| } |
| |
| rc = ngx_http_lua_check_broken_connection(r, r->connection->read); |
| |
| if (rc == NGX_OK) { |
| return; |
| } |
| |
| /* rc == NGX_ERROR || rc > NGX_OK */ |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return; |
| } |
| |
| if (ctx->on_abort_co_ctx == NULL) { |
| r->connection->error = 1; |
| ngx_http_lua_request_cleanup(ctx, 0); |
| ngx_http_lua_finalize_request(r, rc); |
| return; |
| } |
| |
| if (ctx->on_abort_co_ctx->co_status != NGX_HTTP_LUA_CO_SUSPENDED) { |
| |
| /* on_abort already run for the current request handler */ |
| |
| rev = r->connection->read; |
| |
| if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && rev->active) { |
| if (ngx_del_event(rev, NGX_READ_EVENT, 0) != NGX_OK) { |
| ngx_http_lua_request_cleanup(ctx, 0); |
| ngx_http_lua_finalize_request(r, |
| NGX_HTTP_INTERNAL_SERVER_ERROR); |
| return; |
| } |
| } |
| |
| return; |
| } |
| |
| ctx->uthreads++; |
| ctx->resume_handler = ngx_http_lua_on_abort_resume; |
| ctx->on_abort_co_ctx->co_status = NGX_HTTP_LUA_CO_RUNNING; |
| ctx->cur_co_ctx = ctx->on_abort_co_ctx; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua waking up the on_abort callback thread"); |
| |
| if (ctx->entered_content_phase) { |
| r->write_event_handler = ngx_http_lua_content_wev_handler; |
| |
| } else { |
| r->write_event_handler = ngx_http_core_run_phases; |
| } |
| |
| r->write_event_handler(r); |
| } |
| |
| |
| static ngx_int_t |
| ngx_http_lua_on_abort_resume(ngx_http_request_t *r) |
| { |
| lua_State *vm; |
| ngx_int_t rc; |
| ngx_connection_t *c; |
| ngx_http_lua_ctx_t *ctx; |
| |
| 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 resuming the on_abort callback thread"); |
| |
| #if 0 |
| ngx_http_lua_probe_info("tcp resume"); |
| #endif |
| |
| c = r->connection; |
| vm = ngx_http_lua_get_lua_vm(r, ctx); |
| |
| rc = ngx_http_lua_run_thread(vm, r, ctx, 0); |
| |
| 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); |
| } |
| |
| if (ctx->entered_content_phase) { |
| ngx_http_lua_finalize_request(r, rc); |
| return NGX_DONE; |
| } |
| |
| return rc; |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_test_expect(ngx_http_request_t *r) |
| { |
| ngx_int_t n; |
| ngx_str_t *expect; |
| |
| if (r->expect_tested |
| || r->headers_in.expect == NULL |
| || r->http_version < NGX_HTTP_VERSION_11) |
| { |
| return NGX_OK; |
| } |
| |
| r->expect_tested = 1; |
| |
| expect = &r->headers_in.expect->value; |
| |
| if (expect->len != sizeof("100-continue") - 1 |
| || ngx_strncasecmp(expect->data, (u_char *) "100-continue", |
| sizeof("100-continue") - 1) |
| != 0) |
| { |
| return NGX_OK; |
| } |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "send 100 Continue"); |
| |
| n = r->connection->send(r->connection, |
| (u_char *) "HTTP/1.1 100 Continue" CRLF CRLF, |
| sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1); |
| |
| if (n == sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1) { |
| return NGX_OK; |
| } |
| |
| /* we assume that such small packet should be send successfully */ |
| |
| return NGX_ERROR; |
| } |
| |
| |
| void |
| ngx_http_lua_finalize_request(ngx_http_request_t *r, ngx_int_t rc) |
| { |
| if (r->connection->fd != -1) { |
| ngx_http_finalize_request(r, rc); |
| return; |
| } |
| |
| ngx_http_lua_finalize_fake_request(r, rc); |
| } |
| |
| |
| void |
| ngx_http_lua_finalize_fake_request(ngx_http_request_t *r, ngx_int_t rc) |
| { |
| ngx_connection_t *c; |
| |
| c = r->connection; |
| |
| ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "http lua finalize fake request: %d, a:%d, c:%d", |
| rc, r == c->data, r->main->count); |
| |
| if (rc == NGX_DONE) { |
| ngx_http_lua_close_fake_request(r); |
| return; |
| } |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| ngx_http_lua_close_fake_request(r); |
| return; |
| } |
| |
| if (c->read->timer_set) { |
| ngx_del_timer(c->read); |
| } |
| |
| if (c->write->timer_set) { |
| c->write->delayed = 0; |
| ngx_del_timer(c->write); |
| } |
| |
| ngx_http_lua_close_fake_request(r); |
| } |
| |
| |
| static void |
| ngx_http_lua_close_fake_request(ngx_http_request_t *r) |
| { |
| ngx_connection_t *c; |
| |
| r = r->main; |
| c = r->connection; |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "http lua fake request count:%d", r->count); |
| |
| if (r->count == 0) { |
| ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http lua fake request " |
| "count is zero"); |
| } |
| |
| r->count--; |
| |
| if (r->count) { |
| return; |
| } |
| |
| ngx_http_lua_free_fake_request(r); |
| ngx_http_lua_close_fake_connection(c); |
| } |
| |
| |
| static void |
| ngx_http_lua_free_fake_request(ngx_http_request_t *r) |
| { |
| ngx_log_t *log; |
| ngx_http_cleanup_t *cln; |
| ngx_http_log_ctx_t *ctx; |
| |
| log = r->connection->log; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http lua close fake " |
| "request"); |
| |
| if (r->pool == NULL) { |
| ngx_log_error(NGX_LOG_ALERT, log, 0, "http lua fake request " |
| "already closed"); |
| return; |
| } |
| |
| for (cln = r->cleanup; cln; cln = cln->next) { |
| if (cln->handler) { |
| cln->handler(cln->data); |
| } |
| } |
| |
| /* the various request strings were allocated from r->pool */ |
| ctx = log->data; |
| ctx->request = NULL; |
| |
| r->request_line.len = 0; |
| |
| r->connection->destroyed = 1; |
| |
| ngx_destroy_pool(r->pool); |
| } |
| |
| |
| void |
| ngx_http_lua_close_fake_connection(ngx_connection_t *c) |
| { |
| ngx_pool_t *pool; |
| ngx_connection_t *saved_c = NULL; |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, |
| "http lua close fake http connection"); |
| |
| c->destroyed = 1; |
| |
| pool = c->pool; |
| |
| if (c->read->timer_set) { |
| ngx_del_timer(c->read); |
| } |
| |
| if (c->write->timer_set) { |
| ngx_del_timer(c->write); |
| } |
| |
| c->read->closed = 1; |
| c->write->closed = 1; |
| |
| /* we temporarily use a valid fd (0) to make ngx_free_connection happy */ |
| |
| c->fd = 0; |
| |
| if (ngx_cycle->files) { |
| saved_c = ngx_cycle->files[0]; |
| } |
| |
| ngx_free_connection(c); |
| |
| c->fd = (ngx_socket_t) -1; |
| |
| if (ngx_cycle->files) { |
| ngx_cycle->files[0] = saved_c; |
| } |
| |
| if (pool) { |
| ngx_destroy_pool(pool); |
| } |
| } |
| |
| |
| lua_State * |
| ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, |
| ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log, |
| ngx_pool_cleanup_t **pcln) |
| { |
| lua_State *L; |
| ngx_uint_t i; |
| ngx_pool_cleanup_t *cln; |
| ngx_http_lua_preload_hook_t *hook; |
| ngx_http_lua_vm_state_t *state; |
| |
| /* add new cleanup handler to config mem pool */ |
| cln = ngx_pool_cleanup_add(pool, 0); |
| if (cln == NULL) { |
| return NULL; |
| } |
| |
| /* create new Lua VM instance */ |
| L = ngx_http_lua_new_state(parent_vm, cycle, lmcf, log); |
| if (L == NULL) { |
| return NULL; |
| } |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "lua initialize the " |
| "global Lua VM %p", L); |
| |
| /* register cleanup handler for Lua VM */ |
| cln->handler = ngx_http_lua_cleanup_vm; |
| |
| state = ngx_alloc(sizeof(ngx_http_lua_vm_state_t), log); |
| if (state == NULL) { |
| return NULL; |
| } |
| state->vm = L; |
| state->count = 1; |
| |
| cln->data = state; |
| |
| if (pcln) { |
| *pcln = cln; |
| } |
| |
| if (lmcf->preload_hooks) { |
| |
| /* register the 3rd-party module's preload hooks */ |
| |
| lua_getglobal(L, "package"); |
| lua_getfield(L, -1, "preload"); |
| |
| hook = lmcf->preload_hooks->elts; |
| |
| for (i = 0; i < lmcf->preload_hooks->nelts; i++) { |
| |
| ngx_http_lua_probe_register_preload_package(L, hook[i].package); |
| |
| lua_pushcfunction(L, hook[i].loader); |
| lua_setfield(L, -2, (char *) hook[i].package); |
| } |
| |
| lua_pop(L, 2); |
| } |
| |
| return L; |
| } |
| |
| |
| void |
| ngx_http_lua_cleanup_vm(void *data) |
| { |
| lua_State *L; |
| ngx_http_lua_vm_state_t *state = data; |
| |
| #if (DDEBUG) |
| if (state) { |
| dd("cleanup VM: c:%d, s:%p", (int) state->count, state->vm); |
| } |
| #endif |
| |
| if (state) { |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "decrementing " |
| "the reference count for Lua VM: %i", state->count); |
| |
| if (--state->count == 0) { |
| L = state->vm; |
| ngx_http_lua_cleanup_conn_pools(L); |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, |
| "lua close the global Lua VM %p", L); |
| lua_close(L); |
| ngx_free(state); |
| } |
| } |
| } |
| |
| |
| static void |
| ngx_http_lua_cleanup_conn_pools(lua_State *L) |
| { |
| ngx_queue_t *q; |
| ngx_connection_t *c; |
| ngx_http_lua_socket_pool_t *spool; |
| ngx_http_lua_socket_pool_item_t *item; |
| |
| lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); |
| lua_rawget(L, LUA_REGISTRYINDEX); /* table */ |
| |
| lua_pushnil(L); /* first key */ |
| while (lua_next(L, -2) != 0) { |
| /* tb key val */ |
| spool = lua_touserdata(L, -1); |
| |
| if (!ngx_queue_empty(&spool->cache)) { |
| q = ngx_queue_head(&spool->cache); |
| item = ngx_queue_data(q, ngx_http_lua_socket_pool_item_t, queue); |
| c = item->connection; |
| ngx_close_connection(c); |
| ngx_queue_remove(q); |
| |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, |
| "lua tcp socket keepalive: free connection pool " |
| "for \"%s\"", spool->key); |
| } |
| |
| lua_pop(L, 1); |
| } |
| |
| lua_pop(L, 1); |
| } |
| |
| /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ |