accelerator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .DS_Store
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ logs
6
+ nginx/*_temp
7
+ pkg
8
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2010
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ #Nginx Accelerator
2
+
3
+ Drop-in page caching using nginx, lua, and memcached.
4
+
5
+ ##Features
6
+
7
+ * Listens to Cache-Control max-age header
8
+ * The memcached key is the URI (easy to expire on demand)
9
+ * Really, really fast
10
+
11
+ ##Requirements
12
+
13
+ Nginx build with the following modules:
14
+
15
+ * [LuaJIT](http://wiki.nginx.org/HttpLuaModule)
16
+ * [MemcNginxModule](http://wiki.nginx.org/HttpMemcModule)
17
+ * [LuaRestyMemcachedLibrary](https://github.com/agentzh/lua-resty-memcached)
18
+
19
+ See the [Building OpenResty](#building-openresty) section below for instructions.
20
+
21
+ ##Install
22
+
23
+ luarocks install nginx-accelerator
24
+
25
+ ##Nginx config
26
+
27
+ Drop the following line in any `location` directive within `nginx.conf`:
28
+
29
+ access_by_lua "require('accelerator').access()";
30
+
31
+ For example:
32
+
33
+ http {
34
+ server {
35
+ listen 8080;
36
+
37
+ location = / {
38
+ access_by_lua "require('accelerator').access()";
39
+ }
40
+ }
41
+ }
42
+
43
+ The TTL is based on `Cache-Control: max-age`, but defaults to 10 seconds.
44
+
45
+ To configure your memcached connection information:
46
+
47
+ access_by_lua "require('accelerator').access({ host='127.0.0.1', port=11211 })";
48
+
49
+ ## Ruby client
50
+
51
+ ### Install gem
52
+
53
+ gem install accelerator
54
+
55
+ ### Example
56
+
57
+ cache = Accelerator.new("localhost:11211")
58
+ cache.get("/test")
59
+ cache.set("/test", "body")
60
+ cache.delete("/test")
61
+ cache.expire("/test", 10)
62
+
63
+ ## Running specs
64
+
65
+ ###Install Lua
66
+
67
+ brew install lua
68
+ brew install luarocks
69
+
70
+ ###Install PCRE
71
+
72
+ brew update
73
+ brew install pcre
74
+
75
+ ###Install [OpenResty](http://openresty.org) (nginx)
76
+
77
+ curl -O http://agentzh.org/misc/nginx/ngx_openresty-1.2.4.9.tar.gz
78
+ tar xzvf ngx_openresty-1.2.4.9.tar.gz
79
+ cd ngx_openresty-1.2.4.9/
80
+
81
+ Get your PCRE version:
82
+
83
+ brew info pcre
84
+
85
+ Replace **VERSION** below with the PCRE version:
86
+
87
+ ./configure --with-luajit --with-cc-opt="-I/usr/local/Cellar/pcre/VERSION/include" --with-ld-opt="-L/usr/local/Cellar/pcre/VERSION/lib"
88
+ make
89
+ make install
90
+
91
+ ###Start nginx
92
+
93
+ cd nginx-accelerator
94
+ ./nginx/start
95
+
96
+ ### Run specs
97
+
98
+ bundle install
99
+ spec spec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/bin/accelerator ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("../../lib/accelerator", __FILE__)
data/build ADDED
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ cd src
4
+
5
+ if [ "$1" == "watch" ]
6
+ then
7
+ moonc -t ../lib -w .
8
+ else
9
+ moonc -t ../lib .
10
+ fi
@@ -0,0 +1,95 @@
1
+ module("accelerator", package.seeall)
2
+ local json = require("cjson")
3
+ local memcached = require("resty.memcached")
4
+ local debug
5
+ debug = function(kind, msg)
6
+ if msg then
7
+ msg = kind .. ": " .. msg
8
+ else
9
+ msg = kind
10
+ end
11
+ return ngx.log(ngx.DEBUG, msg)
12
+ end
13
+ local memclient
14
+ memclient = function(opts)
15
+ if opts == nil then
16
+ opts = { }
17
+ end
18
+ local client, err = memcached:new()
19
+ if not client or err then
20
+ error(err or "problem creating client")
21
+ end
22
+ client:set_timeout(1000)
23
+ local ok
24
+ ok, err = client:connect((opts.host or "127.0.0.1"), (opts.port or 11211))
25
+ if not ok or err then
26
+ error(err or "problem connecting")
27
+ end
28
+ return client
29
+ end
30
+ local access
31
+ access = function(opts)
32
+ if ngx.var.request_method ~= "GET" then
33
+ return
34
+ end
35
+ if ngx.is_subrequest then
36
+ return
37
+ end
38
+ local fn
39
+ fn = function()
40
+ local memc = memclient(opts)
41
+ local cache, flags, err = memc:get(ngx.var.request_uri)
42
+ if err then
43
+ error(err)
44
+ end
45
+ if cache then
46
+ debug("read cache", cache)
47
+ cache = json.decode(cache)
48
+ if cache.header then
49
+ ngx.header = cache.header
50
+ end
51
+ if cache.body then
52
+ ngx.say(cache.body)
53
+ end
54
+ end
55
+ if not cache or os.time() - cache.time >= cache.ttl then
56
+ local co = coroutine.create(function()
57
+ cache = cache or { }
58
+ cache.time = os.time()
59
+ memc:set(ngx.var.request_uri, json.encode(cache))
60
+ local res = ngx.location.capture(ngx.var.request_uri)
61
+ if not res then
62
+ return
63
+ end
64
+ local ttl = nil
65
+ do
66
+ local cc = res.header["Cache-Control"]
67
+ if cc then
68
+ res.header["Cache-Control"] = nil
69
+ local x, x
70
+ x, x, ttl = string.find(cc, "max%-age=(%d+)")
71
+ end
72
+ end
73
+ if ttl then
74
+ ttl = tonumber(ttl)
75
+ debug("ttl", ttl)
76
+ end
77
+ res.time = os.time()
78
+ res.ttl = ttl or opts.ttl or 10
79
+ memc:set(ngx.var.request_uri, json.encode(res))
80
+ return debug("write cache")
81
+ end)
82
+ coroutine.resume(co)
83
+ end
84
+ if cache and cache.body then
85
+ return ngx.exit(ngx.HTTP_OK)
86
+ end
87
+ end
88
+ local status, err = pcall(fn)
89
+ if err then
90
+ return ngx.log(ngx.ERR, err)
91
+ end
92
+ end
93
+ return {
94
+ access = access
95
+ }
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'cgi'
3
+ require 'json'
4
+ require 'memcached'
5
+
6
+ class Accelerator
7
+
8
+ def initialize(host="localhost:11211")
9
+ @memc = Memcached.new(host)
10
+ end
11
+
12
+ def delete(uri)
13
+ @memc.delete(key(uri))
14
+ end
15
+
16
+ def expire(uri, ttl=nil)
17
+ if data = get_and_set_time(uri)
18
+ data[:ttl] = ttl if ttl
19
+ data[:time] -= data[:ttl] || 10
20
+ @memc.set(key(uri), data.to_json, 604800, false)
21
+ end
22
+ end
23
+
24
+ def get(uri)
25
+ if data = get_and_parse(uri)
26
+ [ data.delete(:body), data ]
27
+ end
28
+ end
29
+
30
+ def set(uri, body)
31
+ data = get_and_set_time(uri) || { :time => Time.now.to_i }
32
+ data[:body] = body
33
+ @memc.set(key(uri), data.to_json, 604800, false)
34
+ end
35
+
36
+ private
37
+
38
+ def key(k)
39
+ CGI.escape(k).gsub(/%../) { |s| s.downcase }
40
+ end
41
+
42
+ def get_and_parse(uri)
43
+ data = @memc.get(key(uri), nil) rescue nil
44
+ if data
45
+ JSON.parse(data, :symbolize_names => true)
46
+ end
47
+ end
48
+
49
+ def get_and_set_time(uri)
50
+ if data = get_and_parse(uri)
51
+ data[:time] = Time.now.to_i
52
+ end
53
+ data
54
+ end
55
+ end
data/nginx/nginx.conf ADDED
@@ -0,0 +1,34 @@
1
+ worker_processes 1;
2
+ error_log logs/error.log debug;
3
+ events {
4
+ worker_connections 1024;
5
+ }
6
+ http {
7
+ server {
8
+ listen 8080;
9
+
10
+ location = /test {
11
+
12
+ access_by_lua "
13
+ ngx.log(ngx.DEBUG, 'ACCESS PHASE')
14
+ require('accelerator').access({ host='127.0.0.1', port=11211 })
15
+ ";
16
+
17
+ proxy_pass http://127.0.0.1:8081/;
18
+ }
19
+ }
20
+
21
+ server {
22
+ listen 8081;
23
+
24
+ location = / {
25
+
26
+ more_set_headers "Cache-Control: max-age=1";
27
+
28
+ content_by_lua "
29
+ ngx.log(ngx.DEBUG, 'CONTENT PHASE')
30
+ ngx.say(os.time())
31
+ ";
32
+ }
33
+ }
34
+ }
data/nginx/start ADDED
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+
3
+ ./build
4
+ luarocks make
5
+ PATH=/usr/local/openresty/nginx/sbin:$PATH
6
+ export PATH
7
+ nginx -p `pwd`/nginx/ -c nginx.conf -s stop
8
+ nginx -p `pwd`/nginx/ -c nginx.conf
9
+ tail -f nginx/logs/error.log
@@ -0,0 +1,24 @@
1
+ package = "nginx-accelerator"
2
+ version = "1.0-1"
3
+ source = {
4
+ url = "http://github.com/winton/nginx-accelerator"
5
+ }
6
+ description = {
7
+ summary = "Nginx-level memcaching for fun and profit",
8
+ detailed = [[
9
+ Uses information from Cache-Control headers to memcache responses
10
+ at the nginx level.
11
+ ]],
12
+ homepage = "http://github.com/winton/nginx-accelerator",
13
+ license = "MIT"
14
+ }
15
+ dependencies = {
16
+ "lua >= 5.1",
17
+ "lua-cjson >= 2.1.0-1"
18
+ }
19
+ build = {
20
+ type = "builtin",
21
+ modules = {
22
+ ["accelerator"] = "lib/accelerator.lua"
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ root = File.expand_path('../', __FILE__)
3
+ lib = "#{root}/lib"
4
+
5
+ $:.unshift lib unless $:.include?(lib)
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "accelerator"
9
+ s.version = '0.1.0'
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = [ "Winton Welsh" ]
12
+ s.email = [ "mail@wintoni.us" ]
13
+ s.homepage = "http://github.com/winton/nginx-accelerator"
14
+ s.summary = %q{Drop-in page caching using nginx, lua, and memcached}
15
+ s.description = %q{Drop-in page caching using nginx, lua, and memcached.}
16
+
17
+ s.executables = `cd #{root} && git ls-files bin/*`.split("\n").collect { |f| File.basename(f) }
18
+ s.files = `cd #{root} && git ls-files`.split("\n")
19
+ s.require_paths = %w(lib)
20
+ s.test_files = `cd #{root} && git ls-files -- {features,test,spec}/*`.split("\n")
21
+
22
+ s.add_dependency "memcached", "~> 1.5.0"
23
+ s.add_development_dependency "rspec", "~> 1.0"
24
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Accelerator do
4
+
5
+ before(:all) do
6
+ @accelerator = Accelerator.new
7
+ @accelerator.delete("/test")
8
+ end
9
+
10
+ it "should create cache on first request" do
11
+ time = Time.new.to_i
12
+ request.should == time
13
+ body, options = @accelerator.get("/test")
14
+ response_tests(time)
15
+ end
16
+
17
+ it "should return same cache on next request" do
18
+ $expires_at = Time.now.to_f.ceil
19
+ request.should == $last_time
20
+ body, options = @accelerator.get("/test")
21
+ response_tests($last_time)
22
+ end
23
+
24
+ it "should not expire naturally before 1 second" do
25
+ if Time.now.to_f < $expires_at
26
+ expires_in = $expires_at - Time.now.to_f
27
+ request(0.5 * expires_in).should == $last_time
28
+ end
29
+ end
30
+
31
+ it "should expire naturally after 1 second" do
32
+ if Time.now.to_f < $expires_at
33
+ expires_in = $expires_at - Time.now.to_f
34
+ request(expires_in).should == $last_time
35
+ end
36
+ response_tests(Time.new.to_i)
37
+ end
38
+
39
+ it "should obey client expiration" do
40
+ @accelerator.expire("/test")
41
+ request.should == $last_time
42
+ response_tests($last_time)
43
+ end
44
+
45
+ it "should set cache body from the client" do
46
+ @accelerator.set("/test", "123")
47
+ $expires_at = Time.now.to_f.ceil
48
+ request.should == 123
49
+ end
50
+
51
+ it "should not expire naturally before 1 second" do
52
+ if Time.now.to_f < $expires_at
53
+ expires_in = $expires_at - Time.now.to_f
54
+ request(0.5 * expires_in).should == 123
55
+ end
56
+ end
57
+
58
+ it "should expire naturally after 1 second" do
59
+ if Time.now.to_f < $expires_at
60
+ expires_in = $expires_at - Time.now.to_f
61
+ request(expires_in).should == 123
62
+ end
63
+ response_tests(Time.new.to_i)
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ require "pp"
2
+ require "bundler"
3
+
4
+ Bundler.require(:development)
5
+
6
+ $root = File.expand_path('../../', __FILE__)
7
+
8
+ require "#{$root}/lib/accelerator"
9
+
10
+ def request(wait=nil)
11
+ sleep(wait) if wait
12
+ `curl -s http://localhost:8080/test`.strip.to_i
13
+ end
14
+
15
+ def response_tests(time)
16
+ $last_time = time
17
+ body, options = @accelerator.get("/test")
18
+ body.strip.should == time.to_s
19
+ options.should == {
20
+ :ttl => 1,
21
+ :status => 200,
22
+ :header => {
23
+ :"Content-Length" => 11,
24
+ :"Content-Type" => "text/plain"
25
+ },
26
+ :time => time
27
+ }
28
+ end
@@ -0,0 +1,99 @@
1
+ module "accelerator", package.seeall
2
+
3
+
4
+ -- Dependencies
5
+
6
+ json = require "cjson"
7
+ memcached = require "resty.memcached"
8
+
9
+
10
+ -- Debug log
11
+
12
+ debug = (kind, msg) ->
13
+ msg = if msg then kind .. ": " .. msg else kind
14
+ ngx.log(ngx.DEBUG, msg)
15
+
16
+
17
+ -- Create memcached client
18
+
19
+ memclient = (opts={}) ->
20
+ client, err = memcached\new()
21
+ error(err or "problem creating client") if not client or err
22
+
23
+ -- Set memcached connection timeout to 1 sec
24
+ client\set_timeout(1000)
25
+
26
+ -- Connect to memcached server
27
+ ok, err = client\connect((opts.host or "127.0.0.1"), (opts.port or 11211))
28
+ error(err or "problem connecting") if not ok or err
29
+
30
+ client
31
+
32
+
33
+ -- Execute within access_by_lua:
34
+ -- http://wiki.nginx.org/HttpLuaModule#access_by_lua
35
+
36
+ access = (opts) ->
37
+ return if ngx.var.request_method ~= "GET"
38
+ return if ngx.is_subrequest
39
+
40
+ fn = ->
41
+ memc = memclient(opts)
42
+
43
+ cache, flags, err = memc\get(ngx.var.request_uri)
44
+ error(err) if err
45
+
46
+ if cache
47
+ debug("read cache", cache)
48
+ cache = json.decode(cache)
49
+
50
+ if cache.header
51
+ ngx.header = cache.header
52
+
53
+ if cache.body
54
+ ngx.say(cache.body)
55
+
56
+ -- Rewrite cache if cache does not exist or ttl has expired
57
+ if not cache or os.time() - cache.time >= cache.ttl
58
+ co = coroutine.create ->
59
+
60
+ -- Immediately update the time to prevent multiple writes
61
+ cache = cache or {}
62
+ cache.time = os.time()
63
+ memc\set(ngx.var.request_uri, json.encode(cache))
64
+
65
+ -- Make subrequest
66
+ res = ngx.location.capture(ngx.var.request_uri)
67
+ return if not res
68
+
69
+ -- Parse TTL
70
+ ttl = nil
71
+
72
+ if cc = res.header["Cache-Control"]
73
+ res.header["Cache-Control"] = nil
74
+ x, x, ttl = string.find(cc, "max%-age=(%d+)")
75
+
76
+ if ttl
77
+ ttl = tonumber(ttl)
78
+ debug("ttl", ttl)
79
+
80
+ res.time = os.time()
81
+ res.ttl = ttl or opts.ttl or 10
82
+
83
+ -- Write cache
84
+ memc\set(ngx.var.request_uri, json.encode(res))
85
+ debug("write cache")
86
+
87
+ coroutine.resume(co)
88
+
89
+ -- Prevent further phases from executing if body rendered
90
+ if cache and cache.body
91
+ ngx.exit(ngx.HTTP_OK)
92
+
93
+ status, err = pcall(fn)
94
+ ngx.log(ngx.ERR, err) if err
95
+
96
+
97
+ -- Return
98
+
99
+ return { access: access }
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: accelerator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Winton Welsh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: memcached
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.0'
46
+ description: Drop-in page caching using nginx, lua, and memcached.
47
+ email:
48
+ - mail@wintoni.us
49
+ executables:
50
+ - accelerator
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE
57
+ - README.md
58
+ - Rakefile
59
+ - bin/accelerator
60
+ - build
61
+ - lib/accelerator.lua
62
+ - lib/accelerator.rb
63
+ - nginx-accelerator-1.0-1.rockspec
64
+ - nginx-accelerator.gemspec
65
+ - nginx/nginx.conf
66
+ - nginx/start
67
+ - spec/accelerator_spec.rb
68
+ - spec/spec_helper.rb
69
+ - src/accelerator.moon
70
+ homepage: http://github.com/winton/nginx-accelerator
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.23
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Drop-in page caching using nginx, lua, and memcached
94
+ test_files:
95
+ - spec/accelerator_spec.rb
96
+ - spec/spec_helper.rb