htttee 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/benburkert/htttee.png)](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
|