| #ifndef DDEBUG |
| #define DDEBUG 0 |
| #endif |
| #include "ddebug.h" |
| |
| #include "ngx_http_lua_output.h" |
| #include "ngx_http_lua_util.h" |
| #include "ngx_http_lua_contentby.h" |
| #include <math.h> |
| |
| |
| static int ngx_http_lua_ngx_say(lua_State *L); |
| static int ngx_http_lua_ngx_print(lua_State *L); |
| static int ngx_http_lua_ngx_flush(lua_State *L); |
| static int ngx_http_lua_ngx_eof(lua_State *L); |
| static int ngx_http_lua_ngx_send_headers(lua_State *L); |
| static int ngx_http_lua_ngx_echo(lua_State *L, unsigned newline); |
| static void ngx_http_lua_flush_cleanup(void *data); |
| |
| |
| static int |
| ngx_http_lua_ngx_print(lua_State *L) |
| { |
| dd("calling lua print"); |
| return ngx_http_lua_ngx_echo(L, 0); |
| } |
| |
| |
| static int |
| ngx_http_lua_ngx_say(lua_State *L) |
| { |
| dd("calling"); |
| return ngx_http_lua_ngx_echo(L, 1); |
| } |
| |
| |
| static int |
| ngx_http_lua_ngx_echo(lua_State *L, unsigned newline) |
| { |
| ngx_http_request_t *r; |
| ngx_http_lua_ctx_t *ctx; |
| const char *p; |
| size_t len; |
| size_t size; |
| ngx_buf_t *b; |
| ngx_chain_t *cl; |
| ngx_int_t rc; |
| int i; |
| int nargs; |
| int type; |
| const char *msg; |
| ngx_buf_tag_t tag; |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return luaL_error(L, "no request object found"); |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| |
| if (ctx == NULL) { |
| return luaL_error(L, "no request ctx found"); |
| } |
| |
| ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE |
| | NGX_HTTP_LUA_CONTEXT_ACCESS |
| | NGX_HTTP_LUA_CONTEXT_CONTENT); |
| |
| if (ctx->acquired_raw_req_socket) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "raw request socket acquired"); |
| return 2; |
| } |
| |
| if (r->header_only) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "header only"); |
| return 2; |
| } |
| |
| if (ctx->eof) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "seen eof"); |
| return 2; |
| } |
| |
| nargs = lua_gettop(L); |
| size = 0; |
| |
| for (i = 1; i <= nargs; i++) { |
| |
| type = lua_type(L, i); |
| |
| switch (type) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| |
| lua_tolstring(L, i, &len); |
| size += len; |
| break; |
| |
| case LUA_TNIL: |
| |
| size += sizeof("nil") - 1; |
| break; |
| |
| case LUA_TBOOLEAN: |
| |
| if (lua_toboolean(L, i)) { |
| size += sizeof("true") - 1; |
| |
| } else { |
| size += sizeof("false") - 1; |
| } |
| |
| break; |
| |
| case LUA_TTABLE: |
| |
| size += ngx_http_lua_calc_strlen_in_table(L, i, i, |
| 0 /* strict */); |
| break; |
| |
| case LUA_TLIGHTUSERDATA: |
| |
| dd("userdata: %p", lua_touserdata(L, i)); |
| |
| if (lua_touserdata(L, i) == NULL) { |
| size += sizeof("null") - 1; |
| break; |
| } |
| |
| continue; |
| |
| default: |
| |
| msg = lua_pushfstring(L, "string, number, boolean, nil, " |
| "ngx.null, or array table expected, " |
| "but got %s", lua_typename(L, type)); |
| |
| return luaL_argerror(L, i, msg); |
| } |
| } |
| |
| if (newline) { |
| size += sizeof("\n") - 1; |
| } |
| |
| if (size == 0) { |
| /* do nothing for empty strings */ |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| tag = (ngx_buf_tag_t) &ngx_http_lua_module; |
| |
| cl = ngx_http_lua_chains_get_free_buf(r->connection->log, r->pool, |
| &ctx->free_bufs, size, tag); |
| |
| if (cl == NULL) { |
| return luaL_error(L, "out of memory"); |
| } |
| |
| b = cl->buf; |
| |
| for (i = 1; i <= nargs; i++) { |
| type = lua_type(L, i); |
| switch (type) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| p = lua_tolstring(L, i, &len); |
| b->last = ngx_copy(b->last, (u_char *) p, len); |
| break; |
| |
| case LUA_TNIL: |
| *b->last++ = 'n'; |
| *b->last++ = 'i'; |
| *b->last++ = 'l'; |
| break; |
| |
| case LUA_TBOOLEAN: |
| if (lua_toboolean(L, i)) { |
| *b->last++ = 't'; |
| *b->last++ = 'r'; |
| *b->last++ = 'u'; |
| *b->last++ = 'e'; |
| |
| } else { |
| *b->last++ = 'f'; |
| *b->last++ = 'a'; |
| *b->last++ = 'l'; |
| *b->last++ = 's'; |
| *b->last++ = 'e'; |
| } |
| |
| break; |
| |
| case LUA_TTABLE: |
| b->last = ngx_http_lua_copy_str_in_table(L, i, b->last); |
| break; |
| |
| case LUA_TLIGHTUSERDATA: |
| *b->last++ = 'n'; |
| *b->last++ = 'u'; |
| *b->last++ = 'l'; |
| *b->last++ = 'l'; |
| break; |
| |
| default: |
| return luaL_error(L, "impossible to reach here"); |
| } |
| } |
| |
| if (newline) { |
| *b->last++ = '\n'; |
| } |
| |
| #if 0 |
| if (b->last != b->end) { |
| return luaL_error(L, "buffer error: %p != %p", b->last, b->end); |
| } |
| #endif |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| newline ? "lua say response" : "lua print response"); |
| |
| rc = ngx_http_lua_send_chain_link(r, ctx, cl); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "nginx output filter error"); |
| return 2; |
| } |
| |
| dd("downstream write: %d, buf len: %d", (int) rc, |
| (int) (b->last - b->pos)); |
| |
| if (!ctx->out) { |
| #if nginx_version >= 1001004 |
| ngx_chain_update_chains(r->pool, |
| #else |
| ngx_chain_update_chains( |
| #endif |
| &ctx->free_bufs, &ctx->busy_bufs, &cl, tag); |
| |
| dd("out lua buf tag: %p, buffered: %x, busy bufs: %p", |
| &ngx_http_lua_module, (int) r->connection->buffered, |
| ctx->busy_bufs); |
| } |
| |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| |
| size_t |
| ngx_http_lua_calc_strlen_in_table(lua_State *L, int index, int arg_i, |
| unsigned strict) |
| { |
| double key; |
| int max; |
| int i; |
| int type; |
| size_t size; |
| size_t len; |
| const char *msg; |
| |
| if (index < 0) { |
| index = lua_gettop(L) + index + 1; |
| } |
| |
| dd("table index: %d", index); |
| |
| max = 0; |
| |
| lua_pushnil(L); /* stack: table key */ |
| while (lua_next(L, index) != 0) { /* stack: table key value */ |
| dd("key type: %s", luaL_typename(L, -2)); |
| |
| if (lua_type(L, -2) == LUA_TNUMBER) { |
| |
| key = lua_tonumber(L, -2); |
| |
| dd("key value: %d", (int) key); |
| |
| if (floor(key) == key && key >= 1) { |
| if (key > max) { |
| max = (int) key; |
| } |
| |
| lua_pop(L, 1); /* stack: table key */ |
| continue; |
| } |
| } |
| |
| /* not an array (non positive integer key) */ |
| lua_pop(L, 2); /* stack: table */ |
| |
| msg = lua_pushfstring(L, "non-array table found"); |
| luaL_argerror(L, arg_i, msg); |
| return 0; |
| } |
| |
| size = 0; |
| |
| for (i = 1; i <= max; i++) { |
| lua_rawgeti(L, index, i); /* stack: table value */ |
| type = lua_type(L, -1); |
| |
| switch (type) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| |
| lua_tolstring(L, -1, &len); |
| size += len; |
| break; |
| |
| case LUA_TNIL: |
| |
| if (strict) { |
| goto bad_type; |
| } |
| |
| size += sizeof("nil") - 1; |
| break; |
| |
| case LUA_TBOOLEAN: |
| |
| if (strict) { |
| goto bad_type; |
| } |
| |
| if (lua_toboolean(L, -1)) { |
| size += sizeof("true") - 1; |
| |
| } else { |
| size += sizeof("false") - 1; |
| } |
| |
| break; |
| |
| case LUA_TTABLE: |
| |
| size += ngx_http_lua_calc_strlen_in_table(L, -1, arg_i, strict); |
| break; |
| |
| case LUA_TLIGHTUSERDATA: |
| |
| if (strict) { |
| goto bad_type; |
| } |
| |
| if (lua_touserdata(L, -1) == NULL) { |
| size += sizeof("null") - 1; |
| break; |
| } |
| |
| continue; |
| |
| default: |
| |
| bad_type: |
| msg = lua_pushfstring(L, "bad data type %s found", |
| lua_typename(L, type)); |
| return luaL_argerror(L, arg_i, msg); |
| } |
| |
| lua_pop(L, 1); /* stack: table */ |
| } |
| |
| return size; |
| } |
| |
| |
| u_char * |
| ngx_http_lua_copy_str_in_table(lua_State *L, int index, u_char *dst) |
| { |
| double key; |
| int max; |
| int i; |
| int type; |
| size_t len; |
| u_char *p; |
| |
| if (index < 0) { |
| index = lua_gettop(L) + index + 1; |
| } |
| |
| max = 0; |
| |
| lua_pushnil(L); /* stack: table key */ |
| while (lua_next(L, index) != 0) { /* stack: table key value */ |
| key = lua_tonumber(L, -2); |
| if (key > max) { |
| max = (int) key; |
| } |
| |
| lua_pop(L, 1); /* stack: table key */ |
| } |
| |
| for (i = 1; i <= max; i++) { |
| lua_rawgeti(L, index, i); /* stack: table value */ |
| type = lua_type(L, -1); |
| switch (type) { |
| case LUA_TNUMBER: |
| case LUA_TSTRING: |
| p = (u_char *) lua_tolstring(L, -1, &len); |
| dst = ngx_copy(dst, p, len); |
| break; |
| |
| case LUA_TNIL: |
| *dst++ = 'n'; |
| *dst++ = 'i'; |
| *dst++ = 'l'; |
| break; |
| |
| case LUA_TBOOLEAN: |
| if (lua_toboolean(L, -1)) { |
| *dst++ = 't'; |
| *dst++ = 'r'; |
| *dst++ = 'u'; |
| *dst++ = 'e'; |
| |
| } else { |
| *dst++ = 'f'; |
| *dst++ = 'a'; |
| *dst++ = 'l'; |
| *dst++ = 's'; |
| *dst++ = 'e'; |
| } |
| |
| break; |
| |
| case LUA_TTABLE: |
| dst = ngx_http_lua_copy_str_in_table(L, -1, dst); |
| break; |
| |
| case LUA_TLIGHTUSERDATA: |
| |
| *dst++ = 'n'; |
| *dst++ = 'u'; |
| *dst++ = 'l'; |
| *dst++ = 'l'; |
| break; |
| |
| default: |
| luaL_error(L, "impossible to reach here"); |
| return NULL; |
| } |
| |
| lua_pop(L, 1); /* stack: table */ |
| } |
| |
| return dst; |
| } |
| |
| |
| /** |
| * Force flush out response content |
| * */ |
| static int |
| ngx_http_lua_ngx_flush(lua_State *L) |
| { |
| ngx_http_request_t *r; |
| ngx_http_lua_ctx_t *ctx; |
| ngx_buf_t *buf; |
| ngx_chain_t *cl; |
| ngx_int_t rc; |
| int n; |
| unsigned wait = 0; |
| ngx_event_t *wev; |
| ngx_http_core_loc_conf_t *clcf; |
| ngx_http_lua_co_ctx_t *coctx; |
| |
| n = lua_gettop(L); |
| if (n > 1) { |
| return luaL_error(L, "attempt to pass %d arguments, but accepted 0 " |
| "or 1", n); |
| } |
| |
| r = ngx_http_lua_get_req(L); |
| |
| if (n == 1 && r == r->main) { |
| luaL_checktype(L, 1, LUA_TBOOLEAN); |
| wait = lua_toboolean(L, 1); |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return luaL_error(L, "no request ctx found"); |
| } |
| |
| ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE |
| | NGX_HTTP_LUA_CONTEXT_ACCESS |
| | NGX_HTTP_LUA_CONTEXT_CONTENT); |
| |
| if (ctx->acquired_raw_req_socket) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "raw request socket acquired"); |
| return 2; |
| } |
| |
| coctx = ctx->cur_co_ctx; |
| if (coctx == NULL) { |
| return luaL_error(L, "no co ctx found"); |
| } |
| |
| if (r->header_only) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "header only"); |
| return 2; |
| } |
| |
| if (ctx->eof) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "seen eof"); |
| return 2; |
| } |
| |
| if (ctx->buffering) { |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua http 1.0 buffering makes ngx.flush() a no-op"); |
| |
| lua_pushnil(L); |
| lua_pushliteral(L, "buffering"); |
| return 2; |
| } |
| |
| #if 1 |
| if (!r->header_sent) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "nothing to flush"); |
| return 2; |
| } |
| #endif |
| |
| if (ctx->flush_buf) { |
| cl = ctx->flush_buf; |
| |
| } else { |
| dd("allocating new flush buf"); |
| buf = ngx_calloc_buf(r->pool); |
| if (buf == NULL) { |
| return luaL_error(L, "out of memory"); |
| } |
| |
| buf->flush = 1; |
| |
| dd("allocating new flush chain"); |
| cl = ngx_alloc_chain_link(r->pool); |
| if (cl == NULL) { |
| return luaL_error(L, "out of memory"); |
| } |
| |
| cl->next = NULL; |
| cl->buf = buf; |
| |
| ctx->flush_buf = cl; |
| } |
| |
| rc = ngx_http_lua_send_chain_link(r, ctx, cl); |
| |
| dd("send chain: %d", (int) rc); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "nginx output filter error"); |
| return 2; |
| } |
| |
| dd("wait:%d, rc:%d, buffered:%d", wait, (int) rc, r->connection->buffered); |
| |
| if (wait && r->connection->buffered) { |
| ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua flush requires waiting: buffered 0x%uxd", |
| (int) r->connection->buffered); |
| |
| coctx->flushing = 1; |
| ctx->flushing_coros++; |
| |
| if (ctx->entered_content_phase) { |
| /* mimic ngx_http_set_write_handler */ |
| r->write_event_handler = ngx_http_lua_content_wev_handler; |
| |
| } else { |
| r->write_event_handler = ngx_http_core_run_phases; |
| } |
| |
| wev = r->connection->write; |
| |
| if (wev->ready && wev->delayed) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "delayed"); |
| return 2; |
| } |
| |
| clcf = ngx_http_get_module_loc_conf(r, 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 (wev->timer_set) { |
| ngx_del_timer(wev); |
| } |
| |
| lua_pushnil(L); |
| lua_pushliteral(L, "connection broken"); |
| return 2; |
| } |
| |
| coctx->cleanup = ngx_http_lua_flush_cleanup; |
| coctx->data = r; |
| |
| return lua_yield(L, 0); |
| } |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua flush asynchronously"); |
| |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| |
| /** |
| * Send last_buf, terminate output stream |
| * */ |
| static int |
| ngx_http_lua_ngx_eof(lua_State *L) |
| { |
| ngx_http_request_t *r; |
| ngx_http_lua_ctx_t *ctx; |
| ngx_int_t rc; |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return luaL_error(L, "no request object found"); |
| } |
| |
| if (lua_gettop(L) != 0) { |
| return luaL_error(L, "no argument is expected"); |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return luaL_error(L, "no ctx found"); |
| } |
| |
| if (ctx->acquired_raw_req_socket) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "raw request socket acquired"); |
| return 2; |
| } |
| |
| if (ctx->eof) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "seen eof"); |
| return 2; |
| } |
| |
| ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE |
| | NGX_HTTP_LUA_CONTEXT_ACCESS |
| | NGX_HTTP_LUA_CONTEXT_CONTENT); |
| |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua send eof"); |
| |
| rc = ngx_http_lua_send_chain_link(r, ctx, NULL /* indicate last_buf */); |
| |
| dd("send chain: %d", (int) rc); |
| |
| if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "nginx output filter error"); |
| return 2; |
| } |
| |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| |
| void |
| ngx_http_lua_inject_output_api(lua_State *L) |
| { |
| lua_pushcfunction(L, ngx_http_lua_ngx_send_headers); |
| lua_setfield(L, -2, "send_headers"); |
| |
| lua_pushcfunction(L, ngx_http_lua_ngx_print); |
| lua_setfield(L, -2, "print"); |
| |
| lua_pushcfunction(L, ngx_http_lua_ngx_say); |
| lua_setfield(L, -2, "say"); |
| |
| lua_pushcfunction(L, ngx_http_lua_ngx_flush); |
| lua_setfield(L, -2, "flush"); |
| |
| lua_pushcfunction(L, ngx_http_lua_ngx_eof); |
| lua_setfield(L, -2, "eof"); |
| } |
| |
| |
| /** |
| * Send out headers |
| * */ |
| static int |
| ngx_http_lua_ngx_send_headers(lua_State *L) |
| { |
| ngx_int_t rc; |
| ngx_http_request_t *r; |
| ngx_http_lua_ctx_t *ctx; |
| |
| r = ngx_http_lua_get_req(L); |
| if (r == NULL) { |
| return luaL_error(L, "no request found"); |
| } |
| |
| 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); |
| |
| if (!r->header_sent) { |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, |
| "lua send headers"); |
| |
| rc = ngx_http_lua_send_header_if_needed(r, ctx); |
| if (rc == NGX_ERROR || rc > NGX_OK) { |
| lua_pushnil(L); |
| lua_pushliteral(L, "nginx output filter error"); |
| return 2; |
| } |
| } |
| |
| lua_pushinteger(L, 1); |
| return 1; |
| } |
| |
| |
| ngx_int_t |
| ngx_http_lua_flush_resume_helper(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) |
| { |
| lua_State *vm; |
| ngx_int_t rc; |
| ngx_connection_t *c; |
| |
| c = r->connection; |
| |
| ctx->cur_co_ctx->cleanup = NULL; |
| |
| /* push the return value 1 */ |
| lua_pushinteger(ctx->cur_co_ctx->co, 1); |
| |
| vm = ngx_http_lua_get_lua_vm(r, ctx); |
| rc = ngx_http_lua_run_thread(vm, r, ctx, 1); |
| |
| 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_flush_cleanup(void *data) |
| { |
| ngx_http_request_t *r; |
| ngx_event_t *wev; |
| ngx_http_lua_ctx_t *ctx; |
| ngx_http_lua_co_ctx_t *coctx = data; |
| |
| coctx->flushing = 0; |
| |
| r = coctx->data; |
| if (r == NULL) { |
| return; |
| } |
| |
| wev = r->connection->write; |
| |
| if (wev && wev->timer_set) { |
| ngx_del_timer(wev); |
| } |
| |
| ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); |
| if (ctx == NULL) { |
| return; |
| } |
| |
| ctx->flushing_coros--; |
| } |
| |