diff --git a/README.markdown b/README.markdown index b9ea535..5fff727 100644 --- a/README.markdown +++ b/README.markdown @@ -94,6 +94,7 @@ Table of Contents * [ngx.req.append_body](#ngxreqappend_body) * [ngx.req.finish_body](#ngxreqfinish_body) * [ngx.req.socket](#ngxreqsocket) + * [ngx.resp.get_headers](#ngxrespget_headers) * [ngx.exec](#ngxexec) * [ngx.redirect](#ngxredirect) * [ngx.send_headers](#ngxsend_headers) @@ -3277,6 +3278,33 @@ This function was first introduced in the `v0.5.0rc1` release. [Back to TOC](#table-of-contents) +ngx.resp.get_headers +-------------------- +**syntax:** *headers = ngx.resp.get_headers(max_headers?, raw?)* + +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua, log_by_lua** + +Returns a Lua table holding all the current response headers. + +```lua + +local h = ngx.resp.get_headers() +for k, v in pairs(h) do + ... +end +``` + +To read an individual header: + +```lua + +ngx.say("ETag: ", ngx.resp.get_headers()["ETag"]) +``` + +This function has the same signature as [ngx.req.get_headers](#ngxreqget_headers) except getting response headers instead of request headers. + +[Back to TOC](#table-of-contents) + ngx.exec -------- **syntax:** *ngx.exec(uri, args?)* diff --git a/src/ngx_http_lua_headers.c b/src/ngx_http_lua_headers.c index d52e440..4f42e4a 100644 --- a/src/ngx_http_lua_headers.c +++ b/src/ngx_http_lua_headers.c @@ -25,6 +25,7 @@ static int ngx_http_lua_ngx_header_set(lua_State *L); static int ngx_http_lua_ngx_req_get_headers(lua_State *L); static int ngx_http_lua_ngx_req_header_clear(lua_State *L); static int ngx_http_lua_ngx_req_header_set(lua_State *L); +static int ngx_http_lua_ngx_resp_get_headers(lua_State *L); static int @@ -389,6 +390,127 @@ ngx_http_lua_ngx_req_get_headers(lua_State *L) static int +ngx_http_lua_ngx_resp_get_headers(lua_State *L) +{ + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_request_t *r; + u_char *lowcase_key = NULL; + size_t lowcase_key_sz = 0; + ngx_uint_t i; + int n; + int max; + int raw = 0; + int count = 0; + + n = lua_gettop(L); + + if (n >= 1) { + if (lua_isnil(L, 1)) { + max = NGX_HTTP_LUA_MAX_HEADERS; + + } else { + max = luaL_checkinteger(L, 1); + } + + if (n >= 2) { + raw = lua_toboolean(L, 2); + } + + } else { + max = NGX_HTTP_LUA_MAX_HEADERS; + } + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request object found"); + } + + ngx_http_lua_check_fake_request(L, r); + + part = &r->headers_out.headers.part; + count = part->nelts; + while (part->next) { + part = part->next; + count += part->nelts; + } + + if (max > 0 && count > max) { + count = max; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua exceeding request header limit %d", max); + } + + lua_createtable(L, 0, count); + + if (!raw) { + lua_pushlightuserdata(L, &ngx_http_lua_resp_get_headers_metatable_key); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_setmetatable(L, -2); + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + dd("stack top: %d", lua_gettop(L)); + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + if (raw) { + lua_pushlstring(L, (char *) header[i].key.data, header[i].key.len); + + } else { + /* nginx does not even bother initializing output header entry's + * "lowcase_key" field. so we cannot count on that at all. */ + if (header[i].key.len > lowcase_key_sz) { + lowcase_key_sz = header[i].key.len * 2; + + /* we allocate via Lua's GC to prevent in-request + * leaks in the nginx request memory pools */ + lowcase_key = lua_newuserdata(L, lowcase_key_sz); + lua_insert(L, 1); + } + + ngx_strlow(lowcase_key, header[i].key.data, header[i].key.len); + lua_pushlstring(L, (char *) lowcase_key, header[i].key.len); + } + + /* stack: [udata] table key */ + + lua_pushlstring(L, (char *) header[i].value.data, + header[i].value.len); /* stack: [udata] table key + value */ + + ngx_http_lua_set_multi_value_table(L, -3); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua response header: \"%V: %V\"", + &header[i].key, &header[i].value); + + if (--count == 0) { + return 1; + } + } + + return 1; +} + + +static int ngx_http_lua_ngx_header_get(lua_State *L) { ngx_http_request_t *r; @@ -718,22 +840,6 @@ ngx_http_lua_ngx_req_header_set_helper(lua_State *L) void -ngx_http_lua_inject_resp_header_api(lua_State *L) -{ - lua_newtable(L); /* .header */ - - lua_createtable(L, 0, 2); /* metatable for .header */ - lua_pushcfunction(L, ngx_http_lua_ngx_header_get); - lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, ngx_http_lua_ngx_header_set); - lua_setfield(L, -2, "__newindex"); - lua_setmetatable(L, -2); - - lua_setfield(L, -2, "header"); -} - - -void ngx_http_lua_inject_req_header_api(ngx_log_t *log, lua_State *L) { int rc; @@ -780,6 +886,58 @@ ngx_http_lua_inject_req_header_api(ngx_log_t *log, lua_State *L) } +void +ngx_http_lua_inject_resp_header_api(ngx_log_t *log, lua_State *L) +{ + int rc; + + lua_newtable(L); /* .header */ + + lua_createtable(L, 0, 2); /* metatable for .header */ + lua_pushcfunction(L, ngx_http_lua_ngx_header_get); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, ngx_http_lua_ngx_header_set); + lua_setfield(L, -2, "__newindex"); + lua_setmetatable(L, -2); + + lua_setfield(L, -2, "header"); + + + lua_createtable(L, 0, 1); /* .resp */ + + lua_pushcfunction(L, ngx_http_lua_ngx_resp_get_headers); + lua_setfield(L, -2, "get_headers"); + + lua_pushlightuserdata(L, &ngx_http_lua_resp_get_headers_metatable_key); + lua_createtable(L, 0, 1); /* metatable for ngx.resp.get_headers(_, true) */ + + { + const char buf[] = + "local tb, key = ...\n" + "local new_key = string.gsub(string.lower(key), '_', '-')\n" + "if new_key ~= key then return tb[new_key] else return nil end"; + + rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, + "ngx.resp.get_headers __index"); + } + + if (rc != 0) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "failed to load Lua code of the metamethod for " + "ngx.resp.get_headers: %i: %s", rc, lua_tostring(L, -1)); + + lua_pop(L, 3); + return; + } + + lua_setfield(L, -2, "__index"); + lua_rawset(L, LUA_REGISTRYINDEX); + + lua_setfield(L, -2, "resp"); + +} + + #ifndef NGX_HTTP_LUA_NO_FFI_API int ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, int max) diff --git a/src/ngx_http_lua_headers.h b/src/ngx_http_lua_headers.h index 01cc7bf..1766a42 100644 --- a/src/ngx_http_lua_headers.h +++ b/src/ngx_http_lua_headers.h @@ -12,8 +12,8 @@ #include "ngx_http_lua_common.h" -void ngx_http_lua_inject_resp_header_api(lua_State *L); void ngx_http_lua_inject_req_header_api(ngx_log_t *log, lua_State *L); +void ngx_http_lua_inject_resp_header_api(ngx_log_t *log, lua_State *L); #endif /* _NGX_HTTP_LUA_HEADERS_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_util.c b/src/ngx_http_lua_util.c index 7d4f2bf..e7b9565 100644 --- a/src/ngx_http_lua_util.c +++ b/src/ngx_http_lua_util.c @@ -71,7 +71,7 @@ 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; - +char ngx_http_lua_resp_get_headers_metatable_key; ngx_uint_t ngx_http_lua_location_hash = 0; ngx_uint_t ngx_http_lua_content_length_hash = 0; @@ -782,7 +782,7 @@ 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.* */ + lua_createtable(L, 0 /* narr */, 98 /* nrec */); /* ngx.* */ ngx_http_lua_inject_arg_api(L); @@ -803,7 +803,7 @@ ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, #endif ngx_http_lua_inject_req_api(log, L); - ngx_http_lua_inject_resp_header_api(L); + ngx_http_lua_inject_resp_header_api(log, 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); diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h index 53dc8a0..5351818 100644 --- a/src/ngx_http_lua_util.h +++ b/src/ngx_http_lua_util.h @@ -57,6 +57,9 @@ extern char ngx_http_lua_coroutines_key; /* key to the metatable for ngx.req.get_headers() */ extern char ngx_http_lua_req_get_headers_metatable_key; +/* key to the metatable for ngx.resp.get_headers() */ +extern char ngx_http_lua_resp_get_headers_metatable_key; + #ifndef ngx_str_set #define ngx_str_set(str, text) \ diff --git a/t/016-resp-header.t b/t/016-resp-header.t index f597755..9815a44 100644 --- a/t/016-resp-header.t +++ b/t/016-resp-header.t @@ -9,7 +9,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3 + 10); +plan tests => repeat_each() * (blocks() * 3 + 13); #no_diff(); no_long_string(); @@ -1138,3 +1138,83 @@ foo: 32 --- no_error_log [error] + + +=== TEST 57: random access resp headers +--- config + location /resp-header { + content_by_lua ' + ngx.header["Foo"] = "bar" + ngx.header["Bar"] = "baz" + ngx.say("Foo: ", ngx.resp.get_headers()["Foo"] or "nil") + ngx.say("foo: ", ngx.resp.get_headers()["foo"] or "nil") + ngx.say("Bar: ", ngx.resp.get_headers()["Bar"] or "nil") + ngx.say("bar: ", ngx.resp.get_headers()["bar"] or "nil") + '; + } +--- request +GET /resp-header +--- response_headers +Foo: bar +Bar: baz +--- response_body +Foo: bar +foo: bar +Bar: baz +bar: baz + + + +=== TEST 58: iterating through raw resp headers +--- config + location /resp-header { + content_by_lua ' + ngx.header["Foo"] = "bar" + ngx.header["Bar"] = "baz" + local h = {} + for k, v in pairs(ngx.resp.get_headers(nil, true)) do + h[k] = v + end + ngx.say("Foo: ", h["Foo"] or "nil") + ngx.say("foo: ", h["foo"] or "nil") + ngx.say("Bar: ", h["Bar"] or "nil") + ngx.say("bar: ", h["bar"] or "nil") + '; + } +--- request +GET /resp-header +--- response_headers +Foo: bar +Bar: baz +--- response_body +Foo: bar +foo: nil +Bar: baz +bar: nil + + + +=== TEST 59: removed response headers +--- config + location /resp-header { + content_by_lua ' + ngx.header["Foo"] = "bar" + ngx.header["Foo"] = nil + ngx.header["Bar"] = "baz" + ngx.say("Foo: ", ngx.resp.get_headers()["Foo"] or "nil") + ngx.say("foo: ", ngx.resp.get_headers()["foo"] or "nil") + ngx.say("Bar: ", ngx.resp.get_headers()["Bar"] or "nil") + ngx.say("bar: ", ngx.resp.get_headers()["bar"] or "nil") + '; + } +--- request +GET /resp-header +--- response_headers +!Foo +Bar: baz +--- response_body +Foo: nil +foo: nil +Bar: baz +bar: baz + diff --git a/t/062-count.t b/t/062-count.t index b77b61d..4ff6995 100644 --- a/t/062-count.t +++ b/t/062-count.t @@ -35,7 +35,7 @@ __DATA__ --- request GET /test --- response_body -ngx: 97 +ngx: 98 --- no_error_log [error] @@ -56,7 +56,7 @@ ngx: 97 --- request GET /test --- response_body -97 +98 --- no_error_log [error] @@ -84,7 +84,7 @@ GET /test --- request GET /test --- response_body -n = 97 +n = 98 --- no_error_log [error] @@ -301,7 +301,7 @@ GET /t --- response_body_like: 404 Not Found --- error_code: 404 --- error_log -ngx. entry count: 97 +ngx. entry count: 98