htttee 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +1 -2
  2. data/.travis.yml +6 -0
  3. data/README.md +13 -1
  4. data/bin/htttee +2 -2
  5. data/bin/htttee-exec +8 -7
  6. data/config.ru +1 -1
  7. data/htttee.gemspec +4 -3
  8. data/lib/htttee/client.rb +4 -6
  9. data/lib/htttee/client/consumer.rb +38 -34
  10. data/lib/htttee/server.rb +45 -39
  11. data/lib/htttee/server/api.rb +155 -139
  12. data/lib/htttee/server/chunked_body.rb +13 -15
  13. data/lib/htttee/server/middleware/async_fixer.rb +12 -15
  14. data/lib/htttee/server/middleware/dechunker.rb +44 -45
  15. data/lib/htttee/server/middleware/rechunker.rb +15 -0
  16. data/lib/htttee/server/mock.rb +44 -46
  17. data/lib/htttee/server/pubsub_redis.rb +4 -0
  18. data/lib/htttee/server/sse/body.rb +71 -0
  19. data/lib/htttee/server/sse/middleware.rb +96 -0
  20. data/lib/htttee/version.rb +2 -4
  21. data/spec/client_spec.rb +102 -2
  22. data/spec/helpers/rackup.rb +1 -1
  23. data/spec/spec_helper.rb +3 -3
  24. metadata +72 -46
  25. data/deploy/after_restart.rb +0 -1
  26. data/deploy/before_restart.rb +0 -4
  27. data/deploy/before_symlink.rb +0 -2
  28. data/deploy/cookbooks/cloudkick-plugins/recipes/default.rb +0 -8
  29. data/deploy/cookbooks/cloudkick-plugins/recipes/resque.rb +0 -18
  30. data/deploy/cookbooks/cloudkick-plugins/templates/default/resque.sh.erb +0 -4
  31. data/deploy/cookbooks/god/recipes/default.rb +0 -50
  32. data/deploy/cookbooks/god/templates/default/config.erb +0 -3
  33. data/deploy/cookbooks/god/templates/default/god-inittab.erb +0 -3
  34. data/deploy/cookbooks/main/attributes/owner_name.rb +0 -3
  35. data/deploy/cookbooks/main/attributes/recipes.rb +0 -1
  36. data/deploy/cookbooks/main/libraries/dnapi.rb +0 -7
  37. data/deploy/cookbooks/main/recipes/default.rb +0 -2
  38. data/deploy/cookbooks/nginx/files/default/chunkin-nginx-module-v0.22rc1.zip +0 -0
  39. data/deploy/cookbooks/nginx/files/default/nginx-1.0.0.tar.gz +0 -0
  40. data/deploy/cookbooks/nginx/recipes/default.rb +0 -2
  41. data/deploy/cookbooks/nginx/recipes/install.rb +0 -0
  42. data/deploy/cookbooks/nginx/templates/default/nginx.conf.erb +0 -41
  43. data/deploy/cookbooks/resque/recipes/default.rb +0 -47
  44. data/deploy/cookbooks/resque/templates/default/resque.rb.erb +0 -73
  45. data/deploy/cookbooks/resque/templates/default/resque.yml.erb +0 -3
  46. data/deploy/cookbooks/resque/templates/default/resque_scheduler.rb.erb +0 -73
  47. data/deploy/solo.rb +0 -7
data/.gitignore CHANGED
@@ -2,5 +2,4 @@
2
2
  .DS_Store
3
3
 
4
4
  Gemfile.lock
5
- pkg
6
- vendor
5
+ pkg
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - rbx-19mode
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://htttee.engineyard.com/SOMEUNIQUESTRING
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://htttee.engineyard.com/'
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 = EY::Tea::Client.new(:endpoint => opts[:endpoint])
37
+ client = HTTTee::Client.new(:endpoint => opts[:endpoint])
38
38
 
39
39
  client.up(input, opts[:uuid], opts[:'content-type'])
@@ -1,16 +1,17 @@
1
1
  #!/bin/sh
2
2
 
3
- stdout_uuid="$(head -c4096 /dev/urandom | sha256sum | awk '{ print $1 }')"
4
- stderr_uuid="$(head -c4096 /dev/urandom | sha256sum | awk '{ print $1 }')"
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
- bin/htttee --uuid $stdout_uuid --endpoint http://127.0.0.1:9292/ < $stdout_uuid &
10
- bin/htttee --uuid $stderr_uuid --endpoint http://127.0.0.1:9292/ < $stderr_uuid &
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:9292/$stdout_uuid"
13
- echo "stderr: http://127.0.0.1:9292/$stderr_uuid"
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
@@ -3,4 +3,4 @@ $:.unshift File.join(File.dirname(__FILE__), 'lib')
3
3
  require 'htttee/server'
4
4
 
5
5
  use Rack::CommonLogger
6
- run EY::Tea::Server.app
6
+ run HTTTee::Server.app
@@ -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 = '0.5.0'
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', '~> 0.3.1.pre.i'
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
@@ -2,12 +2,10 @@ require 'net/http'
2
2
  require 'rack/client'
3
3
  #require 'uuidtools'
4
4
 
5
- module EY
6
- module Tea
7
- module Client
8
- def self.new(*a)
9
- Consumer.new(*a)
10
- end
5
+ module HTTTee
6
+ module Client
7
+ def self.new(*a)
8
+ Consumer.new(*a)
11
9
  end
12
10
  end
13
11
  end
@@ -1,48 +1,52 @@
1
- module EY
2
- module Tea
3
- module Client
4
- class Consumer < Rack::Client::Base
5
- def initialize(options = {})
6
- @base_uri = URI.parse(options.fetch(:endpoint, 'http://tea.engineyard.com/'))
7
- inner_app = options.fetch(:app, Rack::Client::Handler::NetHTTP.new)
8
-
9
- super(inner_app)
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
- def up(io, uuid, content_type = 'text/plain')
13
- post("/#{uuid}", {'Content-Type' => content_type, 'Transfer-Encoding' => 'chunked'}, io)
14
- end
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
- def down(uuid)
17
- get("/#{uuid}") do |status, headers, response_body|
18
- response_body.each do |chunk|
19
- yield chunk
20
- end
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
- def build_env(request_method, url, headers = {}, body = nil)
25
- uri = @base_uri.nil? ? URI.parse(url) : @base_uri + url
29
+ def build_env(request_method, url, headers = {}, body = nil)
30
+ uri = @base_uri.nil? ? URI.parse(url) : @base_uri + url
26
31
 
27
- env = super(request_method, uri.to_s, headers, body)
32
+ env = super(request_method, uri.to_s, headers, body)
28
33
 
29
- env['HTTP_HOST'] ||= http_host_for(uri)
30
- env['HTTP_USER_AGENT'] ||= http_user_agent
34
+ env['HTTP_HOST'] ||= http_host_for(uri)
35
+ env['HTTP_USER_AGENT'] ||= http_user_agent
31
36
 
32
- env
33
- end
37
+ env
38
+ end
34
39
 
35
- def http_host_for(uri)
36
- if uri.to_s.include?(":#{uri.port}")
37
- [uri.host, uri.port].join(':')
38
- else
39
- uri.host
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
- def http_user_agent
44
- "htttee (rack-client #{Rack::Client::VERSION} (app: #{@app.class}))"
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
@@ -5,59 +5,61 @@ require 'em-redis'
5
5
 
6
6
  EM.epoll
7
7
 
8
- module EY
9
- module Tea
10
- module Server
8
+ module HTTTee
9
+ module Server
11
10
 
12
- def self.app
13
- mocking? ? mock_app : rack_app
14
- end
11
+ def self.app
12
+ mocking? ? mock_app : rack_app
13
+ end
15
14
 
16
- def self.api(host = (ENV['REDIS_HOST'] || 'localhost' ), port = (ENV['REDIS_PORT'] || 6379).to_i)
17
- Api.new(host, port)
18
- end
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
- def self.rack_app
21
- Rack::Builder.app do |builder|
22
- builder.use AsyncFixer
23
- builder.use Dechunker
24
- builder.run Server.api
25
- end
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
- def self.mock_app
29
- Rack::Builder.app do |builder|
30
- builder.use Mock::ThinMuxer
31
- builder.use Mock::EchoUri
32
- builder.run Server.rack_app
33
- end
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
- def self.mock!
37
- require 'htttee/server/mock'
38
- @mocking = true
39
+ def self.mock!
40
+ require 'htttee/server/mock'
41
+ @mocking = true
39
42
 
40
- @mock_uri = Mock.boot_forking_server
41
- end
43
+ @mock_uri = Mock.boot_forking_server
44
+ end
42
45
 
43
- def self.reset!
44
- raise "Can't reset in non-mocked mode." unless mocking?
46
+ def self.reset!
47
+ raise "Can't reset in non-mocked mode." unless mocking?
45
48
 
46
- EM.run do
47
- EM::Protocols::Redis.connect.flush_all do
48
- EM.stop
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
- def self.mocking?
54
- @mocking
55
- end
56
+ def self.mocking?
57
+ @mocking
58
+ end
56
59
 
57
- def self.mock_uri
58
- raise "Not in mock mode!" unless mocking?
59
- @mock_uri
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'
@@ -1,200 +1,216 @@
1
- module EY
2
- module Tea
3
- module Server
4
- class Api
5
- STREAMING, FIN = ?0, ?1
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
- AsyncResponse = [-1, {}, []].freeze
7
+ AsyncResponse = [-1, {}, []].freeze
9
8
 
10
- attr_accessor :redis
9
+ attr_accessor :redis
11
10
 
12
- def initialize(host, port)
13
- @host, @port = host, port
14
- end
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
- redis.set(state_key(uuid), STREAMING)
15
+ def call(env)
16
+ uuid = env['PATH_INFO'].sub(/^\//, '')
17
+ body = env['rack.response_body']
31
18
 
32
- set_input_callback(env, uuid, body)
33
- set_input_errback(env, uuid, body)
34
- set_input_each(env, uuid, body)
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
- def get(env, uuid)
38
- body = ChunkedBody.new
24
+ AsyncResponse
25
+ end
39
26
 
40
- with_state_for(uuid) do |state|
41
- case state
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
- def set_input_callback(env, uuid, body)
50
- rack_input(env).callback do
51
- async_callback(env).call [204, {}, body]
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
- redis.set(state_key(uuid), FIN) do
54
- finish(channel(uuid))
55
- body.succeed
56
- end
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
- def set_input_errback(env, uuid, body)
61
- rack_input(env).errback do |error|
62
- async_callback(env).call [500, {}, body]
45
+ def set_input_callback(env, uuid, body)
46
+ rack_input(env).callback do
47
+ async_callback(env).call [204, {}, body]
63
48
 
64
- body.call [error.inspect]
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
- def set_input_each(env, uuid, body)
70
- rack_input(env).each do |chunk|
71
- unless chunk.empty?
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
- def four_oh_four_response(env, body)
78
- env['async.callback'].call [404, {'Transfer-Encoding' => 'chunked'}, body]
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
- def open_stream_response(env, uuid, body)
84
- start_response(env, body)
85
- stream_data_to(body, data_key(uuid)) do
86
- with_state_for(uuid) do |state|
87
- if state == FIN
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
- def closed_stream_response(env, uuid, body)
97
- start_response(env, body)
98
- stream_data_to(body, data_key(uuid)) do
99
- body.succeed
100
- end
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
- def stream_data_to(body, key, offset = 0, chunk_size = 1024, &block)
104
- redis.substr(key, offset, offset + chunk_size) do |chunk|
105
- if chunk.nil? || chunk.empty?
106
- yield
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.call [chunk]
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
- def respond_with(env, body, data)
115
- start_response(env, body, data)
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
- def start_response(env, body, data = nil)
120
- env['async.callback'].call [200, {'Transfer-Encoding' => 'chunked', 'Content-Type' => 'text/plain'}, body]
121
-
122
- body.call [data] unless data.nil? || data.empty?
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
- def subscribe_and_stream(env, uuid, body)
126
- subscribe channel(uuid) do |type, message, *extra|
127
- case type
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
- when FIN then body.succeed
130
- when STREAMING then body.call [message]
131
- end
132
+ when FIN then body.succeed
133
+ when STREAMING then body.call [message]
132
134
  end
133
135
  end
134
136
 
135
- def with_state_for(uuid, &block)
136
- redis.get(state_key(uuid), &block)
137
+ async_close(env).callback do
138
+ conn.close_connection
137
139
  end
140
+ end
138
141
 
139
- def with_state_and_data_for(uuid, &block)
140
- redis.multi_get(state_key(uuid), data_key(uuid), &block)
141
- end
142
+ def with_state_for(uuid, &block)
143
+ redis.get(state_key(uuid), &block)
144
+ end
142
145
 
143
- def data_key(uuid)
144
- "#{uuid}:data"
145
- end
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
- def state_key(uuid)
148
- "#{uuid}:state"
149
- end
150
+ def data_key(uuid)
151
+ "#{uuid}:data"
152
+ end
150
153
 
151
- def channel(uuid)
152
- uuid
153
- end
154
+ def state_key(uuid)
155
+ "#{uuid}:state"
156
+ end
154
157
 
155
- def rack_input(rack_env)
156
- rack_env['rack.input']
157
- end
158
+ def channel(uuid)
159
+ uuid
160
+ end
158
161
 
159
- def async_callback(rack_env)
160
- rack_env['async.callback']
161
- end
162
+ def rack_input(rack_env)
163
+ rack_env['rack.input']
164
+ end
162
165
 
163
- def publish(channel, data)
164
- redis.publish channel, Yajl::Encoder.encode([STREAMING, data])
165
- end
166
+ def async_callback(rack_env)
167
+ rack_env['async.callback']
168
+ end
166
169
 
167
- def finish(channel)
168
- redis.publish channel, Yajl::Encoder.encode([FIN])
169
- end
170
+ def async_close(rack_env)
171
+ rack_env['async.close']
172
+ end
170
173
 
171
- def subscribe(channel, &block)
172
- conn = pubsub
173
- conn.subscribe channel do |type, chan, data|
174
- case type.upcase
175
- when SUBSCRIBE then block.call(SUBSCRIBE, chan)
176
- when MESSAGE
177
- state, data = Yajl::Parser.parse(data)
178
- case state
179
- when STREAMING then block.call(STREAMING, data)
180
- when FIN
181
- conn.unsubscribe channel
182
- block.call(FIN, data)
183
- end
184
- else
185
- debugger
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
- def pubsub
192
- EM::Protocols::PubSubRedis.connect(@host, @port)
193
- end
201
+ conn
202
+ end
194
203
 
195
- def redis
196
- @redis ||= EM::Protocols::Redis.connect(@host, @port)
197
- end
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