Skip to content

Commit

Permalink
feat(vars) add Lua API and directive to support faster indexed access…
Browse files Browse the repository at this point in the history
… to variables

This commit introduces faster ways of accessing Nginx variables inside OpenResty applications by using the indexed access capability of Nginx. Preliminary tests showed time savings of 60%-70% when compared to traditional hash based variable access methods.

Co-authored-by: Datong Sun <datong.sun@konghq.com>
  • Loading branch information
fffonion and dndx authored Sep 13, 2021
1 parent 12d5abd commit 4766a41
Show file tree
Hide file tree
Showing 6 changed files with 944 additions and 2 deletions.
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Table of Contents
* [Name](#name)
* [Description](#description)
* [Install](#install)
* [Directives](#directives)
* [lua_kong_load_var_index](#lua_kong_load_var_index)
* [Methods](#methods)
* [resty.kong.tls.request\_client\_certificate](#restykongtlsrequest_client_certificate)
* [resty.kong.tls.disable\_session\_reuse](#restykongtlsdisable_session_reuse)
Expand All @@ -19,6 +21,7 @@ Table of Contents
* [resty.kong.tls.set\_upstream\_ssl\_verify\_depth](#restykongtlsset_upstream_ssl_verify_depth)
* [resty.kong.grpc.set\_authority](#restykonggrpcset_authority)
* [resty.kong.tls.disable\_proxy\_ssl](#restykongtlsdisable_proxy_ssl)
* [resty.kong.var.patch\_metatable](#restykongvarpatch_metatable)
* [License](#license)

Description
Expand Down Expand Up @@ -46,6 +49,35 @@ This module can be installed just like any ordinary Nginx C module, using the

```

Directives
=======

lua_kong_load_var_index
-------------------------------------------
**syntax:** *lua_kong_load_var_index $variable;*

**context:** *http*

Ensure *variable* is indexed. Note that variables defined by `set` directive
are always indexed by default and does not need to be defined here again.

Common variables defined by other modules that are already indexed:

- `$proxy_host`
- `$proxy_internal_body_length`
- `$proxy_internal_chunked`
- `$remote_addr`
- `$remote_user`
- `$request`
- `$http_referer`
- `$http_user_agent`
- `$host`

See [resty.kong.var.patch\_metatable](#restykongvarpatch_metatable) on how to enable
indexed variable access.

[Back to TOC](#table-of-contents)

Methods
=======

Expand Down Expand Up @@ -297,6 +329,28 @@ called from the current session.

[Back to TOC](#table-of-contents)

resty.kong.var.patch\_metatable
----------------------------------
**syntax:** *resty.kong.var.patch_metatable()*

**context:** *init_by_lua*

**subsystems:** *http*

Indexed variable access is a faster way of accessing Nginx variables for OpenResty.
This method patches the metatable of `ngx.var` to enable index access to variables
that supports it. It should be called once in the `init` phase which will be effective for
all subsequent `ngx.var` uses.

For variables that does not have indexed access, the slower hash based lookup will
be used instead (this is the OpenResty default behavior).

To ensure a variable can be accessed using index, you can use the [lua_kong_load_var_index](#lua_kong_load_var_index)
directive.

[Back to TOC](#table-of-contents)


License
=======

Expand Down
168 changes: 168 additions & 0 deletions lualib/resty/kong/var.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
local ffi = require "ffi"
local base = require "resty.core.base"


local C = ffi.C
local ffi_new = ffi.new
local ffi_str = ffi.string
local type = type
local error = error
local tostring = tostring
local tonumber = tonumber
local getmetatable = getmetatable
local get_request = base.get_request
local get_size_ptr = base.get_size_ptr
local subsystem = ngx.config.subsystem
local NGX_OK = ngx.OK
local NGX_ERROR = ngx.ERROR
local NGX_DECLINED = ngx.DECLINED


local variable_index = {}
local metatable_patched

local ngx_lua_kong_ffi_var_get_by_index
local ngx_lua_kong_ffi_var_set_by_index
local ngx_lua_kong_ffi_var_load_indexes


if subsystem == "http" then
ffi.cdef[[
int ngx_http_lua_kong_ffi_var_get_by_index(ngx_http_request_t *r,
unsigned int var_index, char **value, size_t *value_len, char **err);

int ngx_http_lua_kong_ffi_var_set_by_index(ngx_http_request_t *r,
unsigned int index, const unsigned char *value, size_t value_len,
char **err);

unsigned int ngx_http_lua_kong_ffi_var_load_indexes(ngx_str_t **names);
]]

ngx_lua_kong_ffi_var_get_by_index = C.ngx_http_lua_kong_ffi_var_get_by_index
ngx_lua_kong_ffi_var_set_by_index = C.ngx_http_lua_kong_ffi_var_set_by_index
ngx_lua_kong_ffi_var_load_indexes = C.ngx_http_lua_kong_ffi_var_load_indexes
end


local value_ptr = ffi_new("unsigned char *[1]")
local errmsg = base.get_errmsg_ptr()

local function load_indexes()
if ngx.get_phase() ~= "init" then
error("load_indexes can only be called in init phase")
end

local count = ngx_lua_kong_ffi_var_load_indexes(nil)
count = tonumber(count)

local names_buf = ffi_new("ngx_str_t *[?]", count)

local rc = ngx_lua_kong_ffi_var_load_indexes(names_buf)

if rc == NGX_OK then
for i = 0, count - 1 do
local name = ffi_str(names_buf[i].data, names_buf[i].len)
variable_index[name] = i
end
end

return variable_index
end

local function var_get_by_index(index)
local r = get_request()
if not r then
error("no request found")
end

local value_len = get_size_ptr()

local rc = ngx_lua_kong_ffi_var_get_by_index(r, index, value_ptr, value_len, errmsg)

if rc == NGX_OK then
return ffi_str(value_ptr[0], value_len[0])
end

if rc == NGX_DECLINED then
return nil
end

assert(rc == NGX_ERROR)
error(ffi_str(errmsg[0]), 2)
end

local function var_set_by_index(index, value)
local r = get_request()
if not r then
error("no request found")
end

local value_len
if value == nil then
value_len = 0

else
if type(value) ~= 'string' then
value = tostring(value)
end

value_len = #value
end

local rc = ngx_lua_kong_ffi_var_set_by_index(r, index, value,
value_len, errmsg)

if rc == NGX_OK then
return
end

assert(rc == NGX_ERROR)
error(ffi_str(errmsg[0]), 2)
end

local function patch_metatable()
if ngx.get_phase() ~= "init" then
error("patch_metatable can only be called in init phase")
end

if metatable_patched then
error("patch_metatable should only be called once")
end

metatable_patched = true

load_indexes()

local mt = getmetatable(ngx.var)
local orig_get = mt.__index
local orig_set = mt.__newindex

mt.__index = function(self, name)
local index = variable_index[name]
if index then
return var_get_by_index(index)
end

return orig_get(self, name)
end

mt.__newindex = function(self, name, value)
local index = variable_index[name]
if index then
return var_set_by_index(index, value)
end

return orig_set(self, name, value)
end
end

if subsystem == "stream" then
patch_metatable = function() end
load_indexes = function() end
end


return {
patch_metatable = patch_metatable,
load_indexes = load_indexes,
}
Loading

0 comments on commit 4766a41

Please sign in to comment.