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.
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