Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

describe module inheritance #362

Merged
merged 4 commits into from
May 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Changed

- APIcast module `balancer` method now accepts optional balancer [PR #362](https://github.com/3scale/apicast/pull/362)

## [3.1.0-alpha1] - 2017-05-05

### Changed
Expand Down
6 changes: 3 additions & 3 deletions apicast/src/balancer.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
local round_robin = require 'resty.balancer.round_robin'

local _M = { }
local _M = { default_balancer = round_robin.new() }

function _M.call()
local balancer = round_robin.new()
function _M.call(_, balancer)
balancer = balancer or _M.default_balancer
local host = ngx.var.proxy_host -- NYI: return to lower frame
local peers = balancer:peers(ngx.ctx[host])

Expand Down
47 changes: 42 additions & 5 deletions examples/custom-module/README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
# Custom Module

Module is something, that is executed in each nginx phase: init, init_worker, rewrite, access, content, log, post_action, balancer, header_filter, body_filter, ...
Module is something, that is executed in each nginx phase: init, init_worker, rewrite, access, content, log, post_action, balancer, header_filter, body_filter etc.
It handles processing of each request. There can be only ONE module that is being executed.

The name of the module that is executed is defined by environment variable `APICAST_MODULE` and defaults to `apicast`.
The name of the module that is executed is defined by the environment variable `APICAST_MODULE` and defaults to `apicast`.

This example implements a module that extends apicast default one and adds more logging.
The example described in this README implements a module that extends the default `apicast` module and adds more logging.

There is another example of custom module implementation for IP blacklisting in [`blacklist.lua`](blacklist.lua).

## Starting the gateway

```sh
APICAST_MODULE=$(pwd)/verbose.lua ../../bin/apicast -c $(pwd)/../configuration/local.json
```

This starts apicast with module `verbose.lua` instead of the default [`apicast.lua`](https://github.com/3scale/apicast/blob/master/apicast/src/apicast.lua). Using local configuration so no 3scale account is needed.
This starts APIcast with module `verbose.lua` instead of the default [`apicast.lua`](https://github.com/3scale/apicast/blob/master/apicast/src/apicast.lua). Local configuration file is used, so no 3scale account is needed.

## Testing

```sh
curl 'localhost:8080?user_key=foo'
```

And see in the apicast output:
And see in the APIcast output:

```
2016/11/16 16:52:00 [warn] 98009#0: *5 [lua] verbose.lua:7: call(): upstream response time: 0.001 upstream connect time: 0.000 while logging request, client: 127.0.0.1, server: _, request: "GET /?user_key=foo HTTP/1.1", upstream: "http://127.0.0.1:8081/?user_key=foo", host: "echo"
```

## Writing a custom module

To honour the module inheritance, but still be able to override some methods from the `apicast` module, you'll
need some clever use of metatables. Here is a recommended skeleton of the module inheritance:

```lua
-- load and initialize the parent module
local apicast = require('apicast').new()

-- _NAME and _VERSION are used in User-Agent when connecting to the Service Management API
local _M = { _VERSION = '0.0', _NAME = 'Example Module' }
-- define a table, that is going to be this module metatable
-- if your table does not define a property, __index is going to get used
-- and so on until there are no metatables to check
-- so in this case the inheritance works like local instance created with _M.new() -> _M -> apicast`
local mt = { __index = setmetatable(_M, { __index = apicast }) }

function _M.new()
-- this method is going to get called after this file is required
-- so create a new table for the internal state (global) and set the metatable for inheritance
return setmetatable({}, mt)
end

-- to run some custom code in the log phase let's override the method
function _M.log()
ngx.log(ngx.WARN,
'upstream response time: ', ngx.var.upstream_response_time, ' ',
'upstream connect time: ', ngx.var.upstream_connect_time)
-- and the original apicast method should be executed too
return apicast:log()
end

return _M
```
60 changes: 60 additions & 0 deletions examples/custom-module/blacklist.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
local apicast = require('apicast').new()
local iputils = require("resty.iputils")
local default_balancer = require('balancer').default_balancer
local resty_balancer = require('resty.balancer')

local _M = { _VERSION = '0.0' }
local mt = { __index = setmetatable(_M, { __index = apicast }) }

local ipv4 = {
unspecified = { '0.0.0.0/8' },
broadcast = { '255.255.255.255/32' },
multicast = { '224.0.0.0/4' },
linkLocal = { '169.254.0.0/16' },
loopback = { '127.0.0.0/8' },
private = { '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16' },
reserved = { '192.0.0.0/24' }
}

local blacklist = {}

for _,cidrs in pairs(ipv4) do
local list = iputils.parse_cidrs(cidrs)

for i=1,#list do
table.insert(blacklist, list[i])
end
end


function _M.new()
return setmetatable({
blacklist = blacklist
}, mt)
end

function _M.init()
iputils.enable_lrucache()
apicast:init()
end

local balancer_with_blacklist = resty_balancer.new(function(peers)
local peer, i = default_balancer.mode(peers)

local ip = peer[1]
local blacklisted, err = iputils.ip_in_cidrs(ip, blacklist)

if blacklisted then
return nil, 'blacklisted'
elseif err then
return nil, err
else
return peer, i
end
end)

function _M.balancer()
return apicast:balancer(balancer_with_blacklist)
end

return _M
8 changes: 5 additions & 3 deletions examples/custom-module/verbose.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
local apicast = require('apicast')
local apicast = require('apicast').new()

local _M = { _VERSION = '0.0' }
local mt = { __index = setmetatable(_M, { __index = apicast }) }

function _M.new()
return setmetatable(_M, { __index = apicast.new() })
return setmetatable({}, mt)
end

function _M.log()
ngx.log(ngx.WARN,
'upstream response time: ', ngx.var.upstream_response_time, ' ',
'upstream connect time: ', ngx.var.upstream_connect_time)
return apicast:log()
end

return _M
return _M
1 change: 1 addition & 0 deletions rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dependencies = {
'busted >= 0',
'ldoc >= 0',
'lua-resty-repl >= 0',
'lua-resty-iputils == 0.3.0-1', -- just as dev dependency before gets bumped to runtime
}
build = {
type = "builtin",
Expand Down
24 changes: 24 additions & 0 deletions spec/examples/custom-module/blacklist_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
local _M = require 'examples.custom-module.blacklist'
local apicast = require 'apicast'

describe('blacklist', function()
it('returns new module instance', function()
local blacklist = _M.new()

assert.table(blacklist)
assert.equal(_M.balancer, blacklist.balancer)
assert.equal(_M.init, blacklist.init)
end)

it('has all apicast methods', function()
local blacklist = _M.new()

assert['function'](blacklist.init)

for _,fun in ipairs{'init_worker', 'rewrite', 'post_action', 'access', 'log'} do
assert.equal(apicast[fun], blacklist[fun], fun .. " is not inherited from apicast")
end

assert['function'](blacklist.balancer)
end)
end)