-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
230 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
local setmetatable = setmetatable | ||
|
||
------------ | ||
--- HTTP | ||
-- HTTP client | ||
-- @module http_ng.backend | ||
|
||
local _M = {} | ||
|
||
local mt = { __index = _M } | ||
|
||
function _M.new(backend, options) | ||
local opts = options or {} | ||
return setmetatable({ | ||
backend = backend, cache_store = opts.cache_store | ||
}, mt) | ||
end | ||
|
||
--- Send request and return the response | ||
-- @tparam http_ng.request request | ||
-- @treturn http_ng.response | ||
function _M:send(request) | ||
local cache_store = self.cache_store | ||
local backend = self.backend | ||
|
||
if cache_store then | ||
local res, err = cache_store:get(request) | ||
|
||
if not res then | ||
|
||
res, err = backend:send(request) | ||
if res and not err then | ||
cache_store:set(res) | ||
end | ||
end | ||
|
||
return res, err | ||
else | ||
return backend:send(request) | ||
end | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
local lrucache = require 'resty.lrucache' | ||
local ngx_re = require 'ngx.re' | ||
local http_headers = require 'resty.http_ng.headers' | ||
|
||
local setmetatable = setmetatable | ||
local gsub = string.gsub | ||
local lower = string.lower | ||
local format = string.format | ||
|
||
local _M = { default_size = 1000 } | ||
|
||
local mt = { __index = _M } | ||
|
||
function _M.new(size) | ||
return setmetatable({ cache = lrucache.new(size or _M.default_size) }, mt) | ||
end | ||
|
||
local function request_cache_key(request) | ||
-- TODO: verify if this is correct cache key and what are the implications | ||
-- FIXME: missing headers, ... | ||
return format('%s:%s', request.method, request.url) | ||
end | ||
|
||
function _M:get(request) | ||
local cache = self.cache | ||
if not cache then | ||
return nil, 'not initialized' | ||
end | ||
|
||
-- TODO: verify it is valid request per the RFC: https://tools.ietf.org/html/rfc7234#section-3 | ||
|
||
local cache_key = request_cache_key(request) | ||
|
||
if not cache_key then return end | ||
|
||
local res, stale = cache:get(cache_key) | ||
|
||
-- TODO: handle stale responses per the RFC: https://tools.ietf.org/html/rfc7234#section-4.2.4 | ||
local serve_stale = false | ||
|
||
if res then | ||
return res | ||
elseif serve_stale then | ||
-- TODO: generate Warning header per the RFC: https://tools.ietf.org/html/rfc7234#section-4.2.4 | ||
return stale | ||
end | ||
end | ||
|
||
|
||
local function parse_cache_control(value) | ||
if not value then return end | ||
|
||
local res, err = ngx_re.split(value, '\\s*,\\s*', 'oj') | ||
|
||
local cache_control = {} | ||
|
||
local t = {} | ||
|
||
for i=1, #res do | ||
local res, err = ngx_re.split(res[i], '=', 'oj', nil, 2, t) | ||
|
||
if err then | ||
ngx.log(ngx.WARN, err) | ||
else | ||
-- TODO: selectively handle quoted strings per the RFC: https://tools.ietf.org/html/rfc7234#section-5.2 | ||
cache_control[gsub(lower(res[1]), '-', '_')] = tonumber(res[2]) or res[2] or true | ||
end | ||
end | ||
|
||
if err then | ||
ngx.log(ngx.WARN, err) | ||
end | ||
|
||
return cache_control | ||
end | ||
|
||
local function response_cache_key(response) | ||
return request_cache_key(response.request) | ||
end | ||
|
||
local function response_ttl(response) | ||
local cache_control = parse_cache_control(response.headers.cache_control) | ||
|
||
return cache_control.max_age | ||
end | ||
|
||
|
||
function _M:set(response) | ||
local cache = self.cache | ||
|
||
if not cache then | ||
return nil, 'not initialized' | ||
end | ||
|
||
-- TODO: verify it is valid response per the RFC: https://tools.ietf.org/html/rfc7234#section-3 | ||
if not response then return end | ||
|
||
local cache_key = response_cache_key(response) | ||
|
||
if not cache_key then return end | ||
|
||
local ttl = response_ttl(response) | ||
|
||
if ttl then | ||
local res = { | ||
body = response.body, | ||
headers = http_headers.new(response.headers), | ||
status = response.status | ||
} | ||
|
||
cache:set(cache_key, res, ttl) | ||
end | ||
end | ||
|
||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
local _M = require 'resty.http_ng.backend.cache' | ||
local fake_backend = require 'fake_backend_helper' | ||
local spy = require 'luassert.spy' | ||
local http_response = require 'resty.http_ng.response' | ||
local cache_store = require 'resty.http_ng.cache_store' | ||
|
||
local inspect = require 'inspect' | ||
describe('cache backend', function() | ||
describe('GET method', function() | ||
local function cache(res, options) | ||
local fake = fake_backend.new(res) | ||
return _M.new(fake, options) | ||
end | ||
|
||
it('accesses the url', function() | ||
local res = spy.new(function(req) return http_response.new(req, 200, { }, 'ok') end) | ||
|
||
local response, err = cache(res):send{method = 'GET', url = 'http://example.com/' } | ||
|
||
assert.falsy(err) | ||
assert.truthy(response) | ||
|
||
assert.spy(res).was.called(1) | ||
|
||
assert.equal('ok', response.body) | ||
end) | ||
|
||
it('accesses caches the call', function() | ||
local res = spy.new(function(req) | ||
return http_response.new(req, 200, { | ||
cache_control = 'private, max-age=10' | ||
}, 'ok') | ||
end) | ||
|
||
local backend = cache(res, { cache_store = cache_store.new() }) | ||
|
||
local function check() | ||
local response, err = backend:send{method = 'GET', url = 'http://example.com/' } | ||
|
||
assert.spy(res).was.called(1) | ||
|
||
assert.truthy(response) | ||
assert.falsy(err) | ||
|
||
assert.equal('ok', response.body) | ||
end | ||
|
||
check() | ||
check() | ||
end) | ||
end) | ||
end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
local _M = require 'resty.http_ng.cache_store' | ||
|
||
describe('HTTP cache store', function() | ||
|
||
describe('.new', function() | ||
local cache_store = _M.new() | ||
|
||
assert.truthy(cache_store.get) | ||
assert.truthy(cache_store.set) | ||
end) | ||
|
||
describe(':get', function() | ||
pending('fetches response from cache') | ||
end) | ||
|
||
describe(':set', function() | ||
pending('stores response in cache') | ||
end) | ||
end) |