diff --git a/README.markdown b/README.markdown index 5d8a5ed36e..e7ad112bad 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 d52e440041..e4599da831 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 @@ -338,7 +339,7 @@ ngx_http_lua_ngx_req_get_headers(lua_State *L) lua_createtable(L, 0, count); if (!raw) { - lua_pushlightuserdata(L, &ngx_http_lua_req_get_headers_metatable_key); + lua_pushlightuserdata(L, &ngx_http_lua_get_headers_metatable_key); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); } @@ -388,6 +389,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_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) { @@ -717,6 +839,27 @@ ngx_http_lua_ngx_req_header_set_helper(lua_State *L) } +void +ngx_http_lua_inject_req_header_api(lua_State *L) +{ + lua_pushcfunction(L, ngx_http_lua_ngx_req_http_version); + lua_setfield(L, -2, "http_version"); + + lua_pushcfunction(L, ngx_http_lua_ngx_req_raw_header); + lua_setfield(L, -2, "raw_header"); + + lua_pushcfunction(L, ngx_http_lua_ngx_req_header_clear); + lua_setfield(L, -2, "clear_header"); + + lua_pushcfunction(L, ngx_http_lua_ngx_req_header_set); + lua_setfield(L, -2, "set_header"); + + lua_pushcfunction(L, ngx_http_lua_ngx_req_get_headers); + lua_setfield(L, -2, "get_headers"); + +} + + void ngx_http_lua_inject_resp_header_api(lua_State *L) { @@ -730,46 +873,38 @@ ngx_http_lua_inject_resp_header_api(lua_State *L) 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; - lua_pushcfunction(L, ngx_http_lua_ngx_req_http_version); - lua_setfield(L, -2, "http_version"); + lua_createtable(L, 0, 1); /* .resp */ - lua_pushcfunction(L, ngx_http_lua_ngx_req_raw_header); - lua_setfield(L, -2, "raw_header"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_header_clear); - lua_setfield(L, -2, "clear_header"); + lua_pushcfunction(L, ngx_http_lua_ngx_resp_get_headers); + lua_setfield(L, -2, "get_headers"); - lua_pushcfunction(L, ngx_http_lua_ngx_req_header_set); - lua_setfield(L, -2, "set_header"); + lua_setfield(L, -2, "resp"); +} - lua_pushcfunction(L, ngx_http_lua_ngx_req_get_headers); - lua_setfield(L, -2, "get_headers"); - lua_pushlightuserdata(L, &ngx_http_lua_req_get_headers_metatable_key); - lua_createtable(L, 0, 1); /* metatable for ngx.req.get_headers(_, true) */ +void +ngx_http_lua_inject_metatable_header_api(ngx_log_t *log, lua_State *L) +{ + int rc; + 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"; - { - 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"; + lua_pushlightuserdata(L, &ngx_http_lua_get_headers_metatable_key); + /* metatable for ngx.req.get_headers(_, true) + * and ngx.resp.get_headers(_, true) */ + lua_createtable(L, 0, 1); - rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, - "ngx.req.get_headers __index"); - } + rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, + "ngx_req.get_headers and 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.req.get_headers: %i: %s", rc, lua_tostring(L, -1)); + "ngx.req.get_headers, ngx.resp.get_headers: %i: %s", rc, lua_tostring(L, -1)); lua_pop(L, 3); return; @@ -777,6 +912,7 @@ ngx_http_lua_inject_req_header_api(ngx_log_t *log, lua_State *L) lua_setfield(L, -2, "__index"); lua_rawset(L, LUA_REGISTRYINDEX); + } diff --git a/src/ngx_http_lua_headers.h b/src/ngx_http_lua_headers.h index 01cc7bf9e4..2c22df72b4 100644 --- a/src/ngx_http_lua_headers.h +++ b/src/ngx_http_lua_headers.h @@ -12,8 +12,9 @@ #include "ngx_http_lua_common.h" +void ngx_http_lua_inject_req_header_api(lua_State *L); 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_metatable_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 7d4f2bf00b..768b96e011 100644 --- a/src/ngx_http_lua_util.c +++ b/src/ngx_http_lua_util.c @@ -70,8 +70,7 @@ 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; - +char ngx_http_lua_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 +781,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); @@ -804,6 +803,7 @@ ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, ngx_http_lua_inject_req_api(log, L); ngx_http_lua_inject_resp_header_api(L); + ngx_http_lua_inject_metatable_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); @@ -2151,7 +2151,7 @@ ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L) lua_createtable(L, 0 /* narr */, 23 /* nrec */); /* .req */ - ngx_http_lua_inject_req_header_api(log, L); + ngx_http_lua_inject_req_header_api(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); diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h index 53dc8a0fe0..083318a080 100644 --- a/src/ngx_http_lua_util.h +++ b/src/ngx_http_lua_util.h @@ -54,8 +54,8 @@ extern char ngx_http_lua_coroutine_parents_key; /* coroutine anchoring table key in Lua VM registry */ 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.req.get_headers() and ngx.resp.get_headers() */ +extern char ngx_http_lua_get_headers_metatable_key; #ifndef ngx_str_set diff --git a/t/016-resp-header.t b/t/016-resp-header.t index f597755582..9815a44012 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 b77b61df62..4ff699511f 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