htttee 0.5.0 → 0.6.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 +1 -2
- data/.travis.yml +6 -0
- data/README.md +13 -1
- data/bin/htttee +2 -2
- data/bin/htttee-exec +8 -7
- data/config.ru +1 -1
- data/htttee.gemspec +4 -3
- data/lib/htttee/client.rb +4 -6
- data/lib/htttee/client/consumer.rb +38 -34
- data/lib/htttee/server.rb +45 -39
- data/lib/htttee/server/api.rb +155 -139
- data/lib/htttee/server/chunked_body.rb +13 -15
- data/lib/htttee/server/middleware/async_fixer.rb +12 -15
- data/lib/htttee/server/middleware/dechunker.rb +44 -45
- data/lib/htttee/server/middleware/rechunker.rb +15 -0
- data/lib/htttee/server/mock.rb +44 -46
- data/lib/htttee/server/pubsub_redis.rb +4 -0
- data/lib/htttee/server/sse/body.rb +71 -0
- data/lib/htttee/server/sse/middleware.rb +96 -0
- data/lib/htttee/version.rb +2 -4
- data/spec/client_spec.rb +102 -2
- data/spec/helpers/rackup.rb +1 -1
- data/spec/spec_helper.rb +3 -3
- metadata +72 -46
- data/deploy/after_restart.rb +0 -1
- data/deploy/before_restart.rb +0 -4
- data/deploy/before_symlink.rb +0 -2
- data/deploy/cookbooks/cloudkick-plugins/recipes/default.rb +0 -8
- data/deploy/cookbooks/cloudkick-plugins/recipes/resque.rb +0 -18
- data/deploy/cookbooks/cloudkick-plugins/templates/default/resque.sh.erb +0 -4
- data/deploy/cookbooks/god/recipes/default.rb +0 -50
- data/deploy/cookbooks/god/templates/default/config.erb +0 -3
- data/deploy/cookbooks/god/templates/default/god-inittab.erb +0 -3
- data/deploy/cookbooks/main/attributes/owner_name.rb +0 -3
- data/deploy/cookbooks/main/attributes/recipes.rb +0 -1
- data/deploy/cookbooks/main/libraries/dnapi.rb +0 -7
- data/deploy/cookbooks/main/recipes/default.rb +0 -2
- data/deploy/cookbooks/nginx/files/default/chunkin-nginx-module-v0.22rc1.zip +0 -0
- data/deploy/cookbooks/nginx/files/default/nginx-1.0.0.tar.gz +0 -0
- data/deploy/cookbooks/nginx/recipes/default.rb +0 -2
- data/deploy/cookbooks/nginx/recipes/install.rb +0 -0
- data/deploy/cookbooks/nginx/templates/default/nginx.conf.erb +0 -41
- data/deploy/cookbooks/resque/recipes/default.rb +0 -47
- data/deploy/cookbooks/resque/templates/default/resque.rb.erb +0 -73
- data/deploy/cookbooks/resque/templates/default/resque.yml.erb +0 -3
- data/deploy/cookbooks/resque/templates/default/resque_scheduler.rb.erb +0 -73
- data/deploy/solo.rb +0 -7
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# htttee - unix's tee-as-a-service
|
2
2
|
|
3
|
+
[](https://travis-ci.org/benburkert/htttee)
|
4
|
+
|
3
5
|
|
4
6
|
## What is 'tee'?
|
5
7
|
|
@@ -29,7 +31,7 @@ In one terminal:
|
|
29
31
|
|
30
32
|
In another terminal:
|
31
33
|
|
32
|
-
curl http://
|
34
|
+
curl http://localhost:3000/SOMEUNIQUESTRING
|
33
35
|
|
34
36
|
Or to see the chunked information:
|
35
37
|
|
@@ -56,6 +58,16 @@ final few numbers chunked through:
|
|
56
58
|
|
57
59
|
Connection closed by foreign host.
|
58
60
|
|
61
|
+
## Browser Support
|
62
|
+
|
63
|
+
Some browsers don't behave well when recieving a chunked response of plain/text data. For example,
|
64
|
+
lots of them buffer the first 256 bytes or so before doing anything. [SSE](http://www.html5rocks.com/en/tutorials/eventsource/basics/)
|
65
|
+
is used to get around this browser limitation. If the request for a stream originates from a browser
|
66
|
+
that supports SSE and the request is for 'text/html' then SSE setup page is returned. That page
|
67
|
+
has a little javascript for setting up the stream and parsing the events. The simplicity of the SSE
|
68
|
+
protocol makes it difficult to sanely send newline characters. So there is a `ctrl` event has been
|
69
|
+
added that makes it easy to send newline characters and other 'special' characters. This could even
|
70
|
+
allow for terminal emulation in the future.
|
59
71
|
|
60
72
|
## Running the server
|
61
73
|
|
data/bin/htttee
CHANGED
@@ -24,7 +24,7 @@ end
|
|
24
24
|
opts = Trollop::options do
|
25
25
|
opt :uuid, "The UUID of the stream.", :short => '-u', :type => String
|
26
26
|
opt :'content-type', "The content-type of the stream.", :short => '-c', :type => String, :default => 'text/plain'
|
27
|
-
opt :endpoint, "The endpoint of the htttee service.", :short => '-e', :type => String, :default => 'http://
|
27
|
+
opt :endpoint, "The endpoint of the htttee service.", :short => '-e', :type => String, :default => 'http://localhost:3000/'
|
28
28
|
opt :quiet, "Don't echo to stdout.", :short => '-q', :default => false
|
29
29
|
end
|
30
30
|
|
@@ -34,6 +34,6 @@ $stdin.sync = true
|
|
34
34
|
$stdout.sync = true
|
35
35
|
|
36
36
|
input = opts[:quiet] ? $stdin : MultiplexedIO.new($stdin, $stdout)
|
37
|
-
client =
|
37
|
+
client = HTTTee::Client.new(:endpoint => opts[:endpoint])
|
38
38
|
|
39
39
|
client.up(input, opts[:uuid], opts[:'content-type'])
|
data/bin/htttee-exec
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
set -e
|
4
|
+
|
5
|
+
stdout_uuid="$(openssl rand -hex 16)"
|
6
|
+
stderr_uuid="$(openssl rand -hex 16)"
|
5
7
|
|
6
|
-
mkfifo $stdout_uuid
|
7
8
|
mkfifo $stderr_uuid
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
~/code/htttee/stubs/htttee --uuid $stdout_uuid --endpoint http://127.0.0.1:3000/ < $stdout_uuid &
|
11
|
+
~/code/htttee/stubs/htttee --uuid $stderr_uuid --endpoint http://127.0.0.1:3000/ < $stderr_uuid &
|
11
12
|
|
12
|
-
echo "stdout: http://127.0.0.1:
|
13
|
-
echo "stderr: http://127.0.0.1:
|
13
|
+
echo "stdout: http://127.0.0.1:3000/$stdout_uuid" >&2
|
14
|
+
echo "stderr: http://127.0.0.1:3000/$stderr_uuid" >&2
|
14
15
|
|
15
16
|
exec 1>$stdout_uuid
|
16
17
|
exec 2>$stderr_uuid
|
data/config.ru
CHANGED
data/htttee.gemspec
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'lib', 'htttee', 'version')
|
2
|
+
|
1
3
|
Gem::Specification.new do |s|
|
2
4
|
s.name = "htttee"
|
3
|
-
s.version =
|
5
|
+
s.version = HTTTee::VERSION
|
4
6
|
s.platform = Gem::Platform::RUBY
|
5
7
|
s.authors = ["Ben Burkert"]
|
6
8
|
s.email = ["ben@benburkert.com"]
|
@@ -15,7 +17,7 @@ Gem::Specification.new do |s|
|
|
15
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
18
|
s.require_paths = ["lib"]
|
17
19
|
|
18
|
-
s.add_dependency 'rack-client', '
|
20
|
+
s.add_dependency 'rack-client', '>= 0.4.2'
|
19
21
|
s.add_dependency 'trollop'
|
20
22
|
|
21
23
|
s.add_development_dependency 'sinatra'
|
@@ -26,5 +28,4 @@ Gem::Specification.new do |s|
|
|
26
28
|
|
27
29
|
s.add_development_dependency 'rspec'
|
28
30
|
s.add_development_dependency 'rake'
|
29
|
-
# s.add_development_dependency 'ruby-debug19'
|
30
31
|
end
|
data/lib/htttee/client.rb
CHANGED
@@ -1,48 +1,52 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
1
|
+
module HTTTee
|
2
|
+
module Client
|
3
|
+
class Consumer < Rack::Client::Base
|
4
|
+
def initialize(options = {})
|
5
|
+
@base_uri = URI.parse(options.fetch(:endpoint, 'http://localhost:3000/'))
|
6
|
+
inner_app = options.fetch(:app, Rack::Client::Handler::NetHTTP.new)
|
7
|
+
|
8
|
+
super(inner_app)
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def up(io, uuid, content_type = 'text/plain')
|
12
|
+
headers = {
|
13
|
+
'Content-Type' => content_type,
|
14
|
+
'Transfer-Encoding' => 'chunked',
|
15
|
+
'Connection' => 'Keep-Alive',
|
16
|
+
}
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
post("/#{uuid}", headers, io)
|
19
|
+
end
|
20
|
+
|
21
|
+
def down(uuid)
|
22
|
+
get("/#{uuid}") do |status, headers, response_body|
|
23
|
+
response_body.each do |chunk|
|
24
|
+
yield chunk
|
21
25
|
end
|
22
26
|
end
|
27
|
+
end
|
23
28
|
|
24
|
-
|
25
|
-
|
29
|
+
def build_env(request_method, url, headers = {}, body = nil)
|
30
|
+
uri = @base_uri.nil? ? URI.parse(url) : @base_uri + url
|
26
31
|
|
27
|
-
|
32
|
+
env = super(request_method, uri.to_s, headers, body)
|
28
33
|
|
29
|
-
|
30
|
-
|
34
|
+
env['HTTP_HOST'] ||= http_host_for(uri)
|
35
|
+
env['HTTP_USER_AGENT'] ||= http_user_agent
|
31
36
|
|
32
|
-
|
33
|
-
|
37
|
+
env
|
38
|
+
end
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
40
|
+
def http_host_for(uri)
|
41
|
+
if uri.to_s.include?(":#{uri.port}")
|
42
|
+
[uri.host, uri.port].join(':')
|
43
|
+
else
|
44
|
+
uri.host
|
41
45
|
end
|
46
|
+
end
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
48
|
+
def http_user_agent
|
49
|
+
"htttee (rack-client #{Rack::Client::VERSION} (app: #{@app.class}))"
|
46
50
|
end
|
47
51
|
end
|
48
52
|
end
|
data/lib/htttee/server.rb
CHANGED
@@ -5,59 +5,61 @@ require 'em-redis'
|
|
5
5
|
|
6
6
|
EM.epoll
|
7
7
|
|
8
|
-
module
|
9
|
-
module
|
10
|
-
module Server
|
8
|
+
module HTTTee
|
9
|
+
module Server
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def self.app
|
12
|
+
mocking? ? mock_app : rack_app
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def self.api(host = (ENV['REDIS_HOST'] || 'localhost' ), port = (ENV['REDIS_PORT'] || 6379).to_i)
|
16
|
+
Api.new(host, port)
|
17
|
+
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
def self.rack_app
|
20
|
+
Rack::Builder.app do |builder|
|
21
|
+
builder.use AsyncFixer
|
22
|
+
builder.use Dechunker
|
23
|
+
builder.use Rechunker
|
24
|
+
builder.use SSE::Middleware
|
25
|
+
builder.run Server.api
|
26
26
|
end
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
def self.mock_app
|
30
|
+
Rack::Builder.app do |builder|
|
31
|
+
builder.use Mock::ThinMuxer
|
32
|
+
builder.use Mock::EchoUri
|
33
|
+
builder.use Rechunker
|
34
|
+
builder.use SSE::Middleware
|
35
|
+
builder.run Server.rack_app
|
34
36
|
end
|
37
|
+
end
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
def self.mock!
|
40
|
+
require 'htttee/server/mock'
|
41
|
+
@mocking = true
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
@mock_uri = Mock.boot_forking_server
|
44
|
+
end
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
def self.reset!
|
47
|
+
raise "Can't reset in non-mocked mode." unless mocking?
|
45
48
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
49
|
+
EM.run do
|
50
|
+
EM::Protocols::Redis.connect.flush_all do
|
51
|
+
EM.stop
|
50
52
|
end
|
51
53
|
end
|
54
|
+
end
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
def self.mocking?
|
57
|
+
@mocking
|
58
|
+
end
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
60
|
+
def self.mock_uri
|
61
|
+
raise "Not in mock mode!" unless mocking?
|
62
|
+
@mock_uri
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -72,3 +74,7 @@ require 'htttee/server/chunked_body'
|
|
72
74
|
|
73
75
|
require 'htttee/server/middleware/async_fixer'
|
74
76
|
require 'htttee/server/middleware/dechunker'
|
77
|
+
require 'htttee/server/middleware/rechunker'
|
78
|
+
|
79
|
+
require 'htttee/server/sse/body'
|
80
|
+
require 'htttee/server/sse/middleware'
|
data/lib/htttee/server/api.rb
CHANGED
@@ -1,200 +1,216 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
SUBSCRIBE, UNSUBSCRIBE, MESSAGE = 'SUBSCRIBE', 'UNSUBSCRIBE', 'MESSAGE'
|
1
|
+
module HTTTee
|
2
|
+
module Server
|
3
|
+
class Api
|
4
|
+
STREAMING, FIN = ?0, ?1
|
5
|
+
SUBSCRIBE, UNSUBSCRIBE, MESSAGE = 'SUBSCRIBE', 'UNSUBSCRIBE', 'MESSAGE'
|
7
6
|
|
8
|
-
|
7
|
+
AsyncResponse = [-1, {}, []].freeze
|
9
8
|
|
10
|
-
|
9
|
+
attr_accessor :redis
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def call(env)
|
17
|
-
uuid = env['PATH_INFO'].sub(/^\//, '')
|
18
|
-
|
19
|
-
case env['REQUEST_METHOD']
|
20
|
-
when 'POST' then post(env, uuid)
|
21
|
-
when 'GET' then get(env, uuid)
|
22
|
-
end
|
23
|
-
|
24
|
-
AsyncResponse
|
25
|
-
end
|
26
|
-
|
27
|
-
def post(env, uuid)
|
28
|
-
body = Thin::DeferrableBody.new
|
11
|
+
def initialize(host, port)
|
12
|
+
@host, @port = host, port
|
13
|
+
end
|
29
14
|
|
30
|
-
|
15
|
+
def call(env)
|
16
|
+
uuid = env['PATH_INFO'].sub(/^\//, '')
|
17
|
+
body = env['rack.response_body']
|
31
18
|
|
32
|
-
|
33
|
-
|
34
|
-
|
19
|
+
case env['REQUEST_METHOD']
|
20
|
+
when 'POST' then post(env, uuid, body)
|
21
|
+
when 'GET' then get(env, uuid, body)
|
35
22
|
end
|
36
23
|
|
37
|
-
|
38
|
-
|
24
|
+
AsyncResponse
|
25
|
+
end
|
39
26
|
|
40
|
-
|
41
|
-
|
42
|
-
when NilClass then four_oh_four_response(env, body)
|
43
|
-
when STREAMING then open_stream_response(env, uuid, body)
|
44
|
-
when FIN then closed_stream_response(env, uuid, body)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
27
|
+
def post(env, uuid, body)
|
28
|
+
redis.set(state_key(uuid), STREAMING)
|
48
29
|
|
49
|
-
|
50
|
-
|
51
|
-
|
30
|
+
set_input_callback(env, uuid, body)
|
31
|
+
set_input_errback(env, uuid, body)
|
32
|
+
set_input_each(env, uuid, body)
|
33
|
+
end
|
52
34
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
35
|
+
def get(env, uuid, body)
|
36
|
+
with_state_for(uuid) do |state|
|
37
|
+
case state
|
38
|
+
when NilClass then four_oh_four_response(env, body)
|
39
|
+
when STREAMING then open_stream_response(env, uuid, body)
|
40
|
+
when FIN then closed_stream_response(env, uuid, body)
|
57
41
|
end
|
58
42
|
end
|
43
|
+
end
|
59
44
|
|
60
|
-
|
61
|
-
|
62
|
-
|
45
|
+
def set_input_callback(env, uuid, body)
|
46
|
+
rack_input(env).callback do
|
47
|
+
async_callback(env).call [204, {}, body]
|
63
48
|
|
64
|
-
|
49
|
+
redis.set(state_key(uuid), FIN) do
|
50
|
+
finish(channel(uuid))
|
65
51
|
body.succeed
|
66
52
|
end
|
67
53
|
end
|
68
54
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
redis.pipeline(['append', data_key(uuid), chunk], ['publish', channel(uuid), Yajl::Encoder.encode([STREAMING, chunk])])
|
73
|
-
end
|
55
|
+
async_close(env).callback do
|
56
|
+
redis.set(state_key(uuid), FIN) do
|
57
|
+
finish(channel(uuid))
|
74
58
|
end
|
75
59
|
end
|
60
|
+
end
|
76
61
|
|
77
|
-
|
78
|
-
|
62
|
+
def set_input_errback(env, uuid, body)
|
63
|
+
rack_input(env).errback do |error|
|
64
|
+
async_callback(env).call [500, {}, body]
|
79
65
|
|
66
|
+
body.call [error.inspect]
|
80
67
|
body.succeed
|
81
68
|
end
|
69
|
+
end
|
82
70
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
body.succeed
|
89
|
-
else
|
90
|
-
subscribe_and_stream(env, uuid, body)
|
91
|
-
end
|
92
|
-
end
|
71
|
+
def set_input_each(env, uuid, body)
|
72
|
+
rack_input(env).each do |chunk|
|
73
|
+
unless chunk.empty?
|
74
|
+
redis.pipeline ['append', data_key(uuid), chunk],
|
75
|
+
['publish', channel(uuid), encode(STREAMING, chunk)]
|
93
76
|
end
|
94
77
|
end
|
78
|
+
end
|
95
79
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
80
|
+
def four_oh_four_response(env, body)
|
81
|
+
env['async.callback'].call [404, {'Transfer-Encoding' => 'chunked'}, body]
|
82
|
+
|
83
|
+
body.succeed
|
84
|
+
end
|
102
85
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
86
|
+
def open_stream_response(env, uuid, body)
|
87
|
+
start_response(env, body)
|
88
|
+
stream_data_to(body, data_key(uuid)) do
|
89
|
+
with_state_for(uuid) do |state|
|
90
|
+
if state == FIN
|
91
|
+
body.succeed
|
107
92
|
else
|
108
|
-
body
|
109
|
-
stream_data_to(body, key, offset + chunk.size, chunk_size, &block)
|
93
|
+
subscribe_and_stream(env, uuid, body)
|
110
94
|
end
|
111
95
|
end
|
112
96
|
end
|
97
|
+
end
|
113
98
|
|
114
|
-
|
115
|
-
|
99
|
+
def closed_stream_response(env, uuid, body)
|
100
|
+
start_response(env, body)
|
101
|
+
stream_data_to(body, data_key(uuid)) do
|
116
102
|
body.succeed
|
117
103
|
end
|
104
|
+
end
|
118
105
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
106
|
+
def stream_data_to(body, key, offset = 0, chunk_size = 1024, &block)
|
107
|
+
redis.substr(key, offset, offset + chunk_size) do |chunk|
|
108
|
+
if chunk.nil? || chunk.empty?
|
109
|
+
yield
|
110
|
+
else
|
111
|
+
body.call [chunk]
|
112
|
+
stream_data_to(body, key, offset + chunk.size, chunk_size, &block)
|
113
|
+
end
|
123
114
|
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def respond_with(env, body, data)
|
118
|
+
start_response(env, body, data)
|
119
|
+
body.succeed
|
120
|
+
end
|
121
|
+
|
122
|
+
def start_response(env, body, data = nil)
|
123
|
+
env['async.callback'].call [200, {'Transfer-Encoding' => 'chunked', 'Content-Type' => 'text/plain'}, body]
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
125
|
+
body.call [data] unless data.nil? || data.empty?
|
126
|
+
end
|
127
|
+
|
128
|
+
def subscribe_and_stream(env, uuid, body)
|
129
|
+
conn = subscribe channel(uuid) do |type, message, *extra|
|
130
|
+
case type
|
128
131
|
#when SUBSCRIBE then start_response(env, body, data)
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
+
when FIN then body.succeed
|
133
|
+
when STREAMING then body.call [message]
|
132
134
|
end
|
133
135
|
end
|
134
136
|
|
135
|
-
|
136
|
-
|
137
|
+
async_close(env).callback do
|
138
|
+
conn.close_connection
|
137
139
|
end
|
140
|
+
end
|
138
141
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
+
def with_state_for(uuid, &block)
|
143
|
+
redis.get(state_key(uuid), &block)
|
144
|
+
end
|
142
145
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
+
def with_state_and_data_for(uuid, &block)
|
147
|
+
redis.multi_get(state_key(uuid), data_key(uuid), &block)
|
148
|
+
end
|
146
149
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
+
def data_key(uuid)
|
151
|
+
"#{uuid}:data"
|
152
|
+
end
|
150
153
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
+
def state_key(uuid)
|
155
|
+
"#{uuid}:state"
|
156
|
+
end
|
154
157
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
+
def channel(uuid)
|
159
|
+
uuid
|
160
|
+
end
|
158
161
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
+
def rack_input(rack_env)
|
163
|
+
rack_env['rack.input']
|
164
|
+
end
|
162
165
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
+
def async_callback(rack_env)
|
167
|
+
rack_env['async.callback']
|
168
|
+
end
|
166
169
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
+
def async_close(rack_env)
|
171
|
+
rack_env['async.close']
|
172
|
+
end
|
170
173
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
174
|
+
def publish(channel, data)
|
175
|
+
redis.publish channel, encode(STREAMING, data)
|
176
|
+
end
|
177
|
+
|
178
|
+
def finish(channel)
|
179
|
+
redis.publish channel, encode(FIN)
|
180
|
+
end
|
181
|
+
|
182
|
+
def subscribe(channel, &block)
|
183
|
+
conn = pubsub
|
184
|
+
|
185
|
+
conn.subscribe channel do |type, chan, data|
|
186
|
+
case type.upcase
|
187
|
+
when SUBSCRIBE then block.call(SUBSCRIBE, chan)
|
188
|
+
when MESSAGE
|
189
|
+
state, data = Yajl::Parser.parse(data)
|
190
|
+
case state
|
191
|
+
when STREAMING then block.call(STREAMING, data)
|
192
|
+
when FIN
|
193
|
+
conn.unsubscribe channel
|
194
|
+
block.call(FIN, data)
|
187
195
|
end
|
196
|
+
else
|
197
|
+
''
|
188
198
|
end
|
189
199
|
end
|
190
200
|
|
191
|
-
|
192
|
-
|
193
|
-
end
|
201
|
+
conn
|
202
|
+
end
|
194
203
|
|
195
|
-
|
196
|
-
|
197
|
-
|
204
|
+
def pubsub
|
205
|
+
EM::Protocols::PubSubRedis.connect(@host, @port)
|
206
|
+
end
|
207
|
+
|
208
|
+
def redis
|
209
|
+
@@redis ||= EM::Protocols::Redis.connect(@host, @port)
|
210
|
+
end
|
211
|
+
|
212
|
+
def encode(*parts)
|
213
|
+
Yajl::Encoder.encode(parts)
|
198
214
|
end
|
199
215
|
end
|
200
216
|
end
|