rack-worker 0.0.1.rc2 → 0.0.1.rc3
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/Gemfile +8 -0
- data/README.md +50 -2
- data/lib/rack/worker/version.rb +1 -1
- data/lib/rack/worker.rb +6 -5
- data/rack-worker.gemspec +2 -0
- data/test/test_helper.rb +14 -0
- data/test/worker_test.rb +110 -0
- metadata +18 -3
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
# Rack::Worker
|
2
2
|
|
3
|
-
|
3
|
+
Rack middleware that implements the Worker Pattern.
|
4
|
+
|
5
|
+
It processes GET requests with a worker backend and only serves them straight from a cache.
|
6
|
+
While processing the request it serves empty HTTP 202 responses.
|
7
|
+
Your web frontend is never blocked processing the request.
|
8
|
+
|
9
|
+
|
10
|
+
## How it works
|
11
|
+
|
12
|
+
When GET requests hit your app, the middleware tries to serve them from the cache.
|
13
|
+
|
14
|
+
If the request is not found, it stores the environment data in the cache. A worker
|
15
|
+
process will then use the `App.call(env)` convention from Rack to run the request through
|
16
|
+
your webapp in the background as if it were a normal Rack request. The status, headers,
|
17
|
+
and body are then stored in the cache so they can be served.
|
18
|
+
|
19
|
+
What makes this technique different from a standard HTTP caching approach is that your
|
20
|
+
web server never processes the long HTTP request. The middleware will return empty
|
21
|
+
HTTP 202 responses unless the response is found in the cache. Every request that generates
|
22
|
+
a 202 will only queue one background job per URL.
|
4
23
|
|
5
24
|
## Installation
|
6
25
|
|
@@ -18,7 +37,36 @@ Or install it yourself as:
|
|
18
37
|
|
19
38
|
## Usage
|
20
39
|
|
21
|
-
|
40
|
+
```ruby
|
41
|
+
class App < Sinatra::Base
|
42
|
+
use Rack::Worker
|
43
|
+
|
44
|
+
get '/long_ass_request' do
|
45
|
+
long_ass_work
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
That's it! Now GETs to `/long_ass_request` will be processed in the background and only
|
51
|
+
serve HTTP 202 responses until they are processed, after which they will return whatever your
|
52
|
+
app would have returned.
|
53
|
+
|
54
|
+
If you already have `queue_classic` and `dalli` installed, everything will *just work*.
|
55
|
+
|
56
|
+
See configuration for setting an expiry time on records.
|
57
|
+
|
58
|
+
## Configuration
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
Rack::Worker.cache = Dalli::Client.new(nil, {:expires_in => 300})
|
62
|
+
```
|
63
|
+
The `cache` can be anything that responds to `get(key)` and `set(key, string)`
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
Rack::Worker.queue = QC
|
67
|
+
```
|
68
|
+
The `queue` can be anything that responds to `enqueue(method, *params)`
|
69
|
+
|
22
70
|
|
23
71
|
## Contributing
|
24
72
|
|
data/lib/rack/worker/version.rb
CHANGED
data/lib/rack/worker.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rack/worker/version'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
module Rack
|
4
5
|
class Worker
|
@@ -40,8 +41,8 @@ module Rack
|
|
40
41
|
else
|
41
42
|
unless cache.get("env-#{key}")
|
42
43
|
cache.set("env-#{key}", env.to_json)
|
43
|
-
|
44
|
-
|
44
|
+
name = @app.is_a?(Class) ? @app.name : @app.class.name
|
45
|
+
queue.enqueue("#{self.class.name}.process_request", name, key)
|
45
46
|
end
|
46
47
|
[202, {"Content-type" => "text/plain"}, []]
|
47
48
|
end
|
@@ -51,7 +52,7 @@ module Rack
|
|
51
52
|
def self.process_request(classname, id)
|
52
53
|
env = cache.get("env-#{id}")
|
53
54
|
return unless env
|
54
|
-
env =
|
55
|
+
env = JSON.parse(env)
|
55
56
|
app = classname_to_class(classname)
|
56
57
|
status, headers, body = app.call(env.merge('rack.worker_qc' => true))
|
57
58
|
set_response(id, status, headers, body)
|
@@ -68,11 +69,11 @@ module Rack
|
|
68
69
|
def get_response(key)
|
69
70
|
response = cache.get("response-#{key}")
|
70
71
|
return unless response
|
71
|
-
|
72
|
+
JSON.parse(response)
|
72
73
|
end
|
73
74
|
|
74
75
|
def key(env)
|
75
|
-
env['REQUEST_PATH'] + '?' + env['QUERY_STRING']
|
76
|
+
(env['REQUEST_PATH'] || env['PATH_INFO']) + '?' + env['QUERY_STRING']
|
76
77
|
end
|
77
78
|
end
|
78
79
|
end
|
data/rack-worker.gemspec
CHANGED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.require :test
|
3
|
+
require 'webmock/test_unit'
|
4
|
+
require "#{File.dirname(__FILE__)}/../lib/rack-worker"
|
5
|
+
|
6
|
+
class Rack::Worker::TestCase < Test::Unit::TestCase
|
7
|
+
include Rack::Test::Methods
|
8
|
+
include RR::Adapters::TestUnit
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
super
|
12
|
+
WebMock.reset!
|
13
|
+
end
|
14
|
+
end
|
data/test/worker_test.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module QueueTest
|
4
|
+
def test_processes_request_in_queue
|
5
|
+
Rack::Worker.cache = Object.new
|
6
|
+
mock(Rack::Worker.cache).get('response-/foo?') { false }
|
7
|
+
mock(Rack::Worker.cache).get('env-/foo?') { false }
|
8
|
+
mock(Rack::Worker.cache).get('env-/foo?') { {'rack.input' => []}.to_json }
|
9
|
+
mock(Rack::Worker.cache).set('env-/foo?', is_a(String))
|
10
|
+
mock(Rack::Worker.cache).set('response-/foo?', is_a(String))
|
11
|
+
|
12
|
+
mock_queue = Object.new
|
13
|
+
def mock_queue.enqueue(function_call, *args)
|
14
|
+
eval("#{function_call} *args")
|
15
|
+
end
|
16
|
+
Rack::Worker.queue = mock_queue
|
17
|
+
|
18
|
+
get '/foo'
|
19
|
+
assert_equal 202, last_response.status
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class WorkerTest < Rack::Worker::TestCase
|
24
|
+
include QueueTest
|
25
|
+
class TestClassApp
|
26
|
+
def self.call(env)
|
27
|
+
[200, {"Content-Type" => "text/test"}, ['Hello, world']]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def app
|
32
|
+
Rack::Builder.new do
|
33
|
+
use Rack::Worker
|
34
|
+
run TestClassApp
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_returns_empty_202_and_queues_when_not_in_cache_or_queue
|
39
|
+
Rack::Worker.cache = Object.new
|
40
|
+
mock(Rack::Worker.cache).get('response-/foo?') { false }
|
41
|
+
mock(Rack::Worker.cache).get('env-/foo?') { false }
|
42
|
+
stub(Rack::Worker.cache).set
|
43
|
+
|
44
|
+
Rack::Worker.queue = Object.new
|
45
|
+
mock(Rack::Worker.queue).enqueue('Rack::Worker.process_request', is_a(String), '/foo?')
|
46
|
+
|
47
|
+
get '/foo'
|
48
|
+
assert_equal 202, last_response.status
|
49
|
+
assert_equal '', last_response.body
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_returns_empty_202_and_does_not_queue_when_not_in_cache_and_in_queue
|
53
|
+
Rack::Worker.cache = Object.new
|
54
|
+
mock(Rack::Worker.cache).get('response-/foo?') { false }
|
55
|
+
mock(Rack::Worker.cache).get('env-/foo?') { true }
|
56
|
+
|
57
|
+
get '/foo'
|
58
|
+
assert_equal 202, last_response.status
|
59
|
+
assert_equal '', last_response.body
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_returns_response_in_queue
|
63
|
+
json = "{\"Hello\":\"World\"}"
|
64
|
+
Rack::Worker.cache = Object.new
|
65
|
+
mock(Rack::Worker.cache).get('response-/foo?') do
|
66
|
+
[302, {"Content-Type" => "application/json"}, [json]].to_json
|
67
|
+
end
|
68
|
+
|
69
|
+
get '/foo'
|
70
|
+
assert_equal 302, last_response.status
|
71
|
+
assert_equal 'application/json', last_response.headers['Content-Type']
|
72
|
+
assert_equal json, last_response.body
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
class SinatraTest < Rack::Worker::TestCase
|
78
|
+
include QueueTest
|
79
|
+
|
80
|
+
class TestClassApp < Sinatra::Base
|
81
|
+
get '*' do
|
82
|
+
headers 'Content-Type' => 'text/test'
|
83
|
+
'Hello, world'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def app
|
88
|
+
Rack::Builder.new do
|
89
|
+
use Rack::Worker
|
90
|
+
run TestClassApp
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class SinatraUseTest < Rack::Worker::TestCase
|
96
|
+
include QueueTest
|
97
|
+
|
98
|
+
class TestClassApp < Sinatra::Base
|
99
|
+
use Rack::Worker
|
100
|
+
|
101
|
+
get '*' do
|
102
|
+
headers 'Content-Type' => 'text/test'
|
103
|
+
'Hello, world'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def app
|
108
|
+
TestClassApp
|
109
|
+
end
|
110
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-worker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.rc3
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,18 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
date: 2012-04-12 00:00:00.000000000 Z
|
13
|
-
dependencies:
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: &70334749515180 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70334749515180
|
14
25
|
description: Rack middleware that implements the Worker Pattern
|
15
26
|
email:
|
16
27
|
- christopher.continanza@gmail.com
|
@@ -27,6 +38,8 @@ files:
|
|
27
38
|
- lib/rack/worker.rb
|
28
39
|
- lib/rack/worker/version.rb
|
29
40
|
- rack-worker.gemspec
|
41
|
+
- test/test_helper.rb
|
42
|
+
- test/worker_test.rb
|
30
43
|
homepage: ''
|
31
44
|
licenses: []
|
32
45
|
post_install_message:
|
@@ -53,4 +66,6 @@ specification_version: 3
|
|
53
66
|
summary: Processes GET requests with a worker backend and only serves them straight
|
54
67
|
from a cache. Your web frontend is never blocked processing the request. Implementation
|
55
68
|
of the Worker Pattern
|
56
|
-
test_files:
|
69
|
+
test_files:
|
70
|
+
- test/test_helper.rb
|
71
|
+
- test/worker_test.rb
|