Permalink
Browse files

feature: added new API function ngx.resp.get_headers() for fetching a…

…ll the response headers. thanks Tatsuhiko Kubo for the patch in #335.
  • Loading branch information...
agentzh committed Feb 26, 2014
1 parent 4a0036c commit 887f0f99d54d07f78ebbdcd2289de17fb515c05d
Showing with 241 additions and 27 deletions.
  1. +148 −16 src/ngx_http_lua_headers.c
  2. +2 −1 src/ngx_http_lua_headers.h
  3. +4 −3 src/ngx_http_lua_util.c
  4. +2 −2 src/ngx_http_lua_util.h
  5. +81 −1 t/016-resp-header.t
  6. +4 −4 t/062-count.t
View
@@ -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_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_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)
{
@@ -730,14 +852,19 @@ ngx_http_lua_inject_resp_header_api(lua_State *L)
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_setfield(L, -2, "resp");
}
void
-ngx_http_lua_inject_req_header_api(ngx_log_t *log, lua_State *L)
+ngx_http_lua_inject_req_header_api(lua_State *L)
{
- int rc;
-
lua_pushcfunction(L, ngx_http_lua_ngx_req_http_version);
lua_setfield(L, -2, "http_version");
@@ -752,24 +879,29 @@ ngx_http_lua_inject_req_header_api(ngx_log_t *log, lua_State *L)
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) */
- {
- 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";
+void
+ngx_http_lua_create_headers_metatable(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";
- rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1,
- "ngx.req.get_headers __index");
- }
+ lua_pushlightuserdata(L, &ngx_http_lua_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, "=headers metatable");
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));
+ "failed to load Lua code for the metamethod for "
+ "headers: %i: %s", rc, lua_tostring(L, -1));
lua_pop(L, 3);
return;
@@ -13,7 +13,8 @@
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_req_header_api(lua_State *L);
+void ngx_http_lua_create_headers_metatable(ngx_log_t *log, lua_State *L);
#endif /* _NGX_HTTP_LUA_HEADERS_H_INCLUDED_ */
View
@@ -70,7 +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_headers_metatable_key;
ngx_uint_t ngx_http_lua_location_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);
@@ -804,6 +804,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_create_headers_metatable(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 +2152,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);
View
@@ -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_headers_metatable_key;
#ifndef ngx_str_set
View
@@ -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
+
View
@@ -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

0 comments on commit 887f0f9

Please sign in to comment.