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
@@ -1,21 +1,19 @@
1
- module EY
2
- module Tea
3
- module Server
4
- class ChunkedBody < Thin::DeferrableBody
5
- def call(body)
6
- body.each do |fragment|
7
- @body_callback.call(chunk(fragment))
8
- end
1
+ module HTTTee
2
+ module Server
3
+ class ChunkedBody < Thin::DeferrableBody
4
+ def call(body)
5
+ body.each do |fragment|
6
+ @body_callback.call(chunk(fragment))
9
7
  end
8
+ end
10
9
 
11
- def succeed(*a)
12
- @body_callback.call("0\r\n\r\n")
13
- super
14
- end
10
+ def succeed(*a)
11
+ @body_callback.call("0\r\n\r\n")
12
+ super
13
+ end
15
14
 
16
- def chunk(fragment)
17
- "#{fragment.size.to_s(16)}\r\n#{fragment}\r\n"
18
- end
15
+ def chunk(fragment)
16
+ "#{fragment.size.to_s(16)}\r\n#{fragment}\r\n"
19
17
  end
20
18
  end
21
19
  end
@@ -1,20 +1,17 @@
1
+ module HTTTee
2
+ module Server
3
+ class AsyncFixer
4
+ def initialize(app)
5
+ @app = app
6
+ end
1
7
 
2
- module EY
3
- module Tea
4
- module Server
5
- class AsyncFixer
6
- def initialize(app)
7
- @app = app
8
- end
9
-
10
- def call(env)
11
- tuple = @app.call(env)
8
+ def call(env)
9
+ tuple = @app.call(env)
12
10
 
13
- if tuple.first == -1
14
- Thin::Connection::AsyncResponse
15
- else
16
- tuple
17
- end
11
+ if tuple.first == -1
12
+ Thin::Connection::AsyncResponse
13
+ else
14
+ tuple
18
15
  end
19
16
  end
20
17
  end
@@ -1,59 +1,58 @@
1
- module EY
2
- module Tea
3
- module Server
4
- class Dechunker
5
- def initialize(app)
6
- @app = app
7
- end
1
+ module HTTTee
2
+ module Server
3
+ class Dechunker
4
+ def initialize(app)
5
+ @app = app
6
+ end
8
7
 
9
- def call(env)
10
- env['rack.input'] = ChunkedBody.new(env['rack.input']) if chunked?(env)
8
+ def call(env)
9
+ env['rack.input'] = ChunkedBody.new(env['rack.input']) if chunked?(env)
11
10
 
12
- @app.call(env)
13
- end
11
+ @app.call(env)
12
+ end
14
13
 
15
- def chunked?(env)
16
- env['HTTP_TRANSFER_ENCODING'] == 'chunked'
17
- end
14
+ def chunked?(env)
15
+ env['HTTP_TRANSFER_ENCODING'] == 'chunked'
16
+ end
18
17
 
19
- class ChunkedBody
20
- extend Forwardable
18
+ class ChunkedBody
19
+ extend Forwardable
21
20
 
22
- CRLF = "\r\n"
21
+ CRLF = "\r\n"
23
22
 
24
- attr_reader :input
23
+ attr_reader :input
25
24
 
26
- def_delegators :input, :callback, :errback
25
+ def_delegators :input, :callback, :errback
27
26
 
28
- def initialize(input)
29
- @input, @buffer = input, ''
30
- end
27
+ def initialize(input)
28
+ @input, @buffer = input, ''
29
+ end
31
30
 
32
- def each(&blk)
33
- @input.each do |chunk|
34
- dechunk(chunk, &blk)
35
- end
31
+ def each(&blk)
32
+ @input.each do |chunk|
33
+ dechunk(chunk, &blk)
36
34
  end
35
+ end
36
+
37
+ def dechunk(chunk, &blk)
38
+ @buffer << chunk
39
+
40
+ loop do
41
+ return unless @buffer[CRLF]
42
+
43
+ string_length, remainder = @buffer.split(CRLF, 2)
44
+ length = string_length.to_i(16)
45
+
46
+ if length == 0
47
+ @buffer = ''
48
+ @input.succeed
49
+ return
50
+ elsif remainder.size >= length + 2 # length + CRLF
51
+ data, @buffer = remainder[0...length], remainder[(length+2)..-1]
37
52
 
38
- def dechunk(chunk, &blk)
39
- @buffer << chunk
40
-
41
- loop do
42
- return unless @buffer[CRLF]
43
-
44
- string_length, remainder = @buffer.split(CRLF, 2)
45
- length = string_length.to_i(16)
46
-
47
- if length == 0
48
- @buffer = ''
49
- @input.succeed
50
- return
51
- elsif remainder.size >= length + 2 # length + CRLF
52
- data, @buffer = remainder.split(CRLF, 2)
53
- blk.call(data)
54
- else
55
- return
56
- end
53
+ blk.call(data)
54
+ else
55
+ return
57
56
  end
58
57
  end
59
58
  end
@@ -0,0 +1,15 @@
1
+ module HTTTee
2
+ module Server
3
+ class Rechunker
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ env['rack.response_body'] = ChunkedBody.new
10
+
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,66 +1,64 @@
1
1
  require 'rack/mux'
2
2
 
3
- module EY
4
- module Tea
5
- module Server
6
- module Mock
3
+ module HTTTee
4
+ module Server
5
+ module Mock
7
6
 
8
- def self.boot_forking_server
9
- o,i = IO.pipe
7
+ def self.boot_forking_server
8
+ o,i = IO.pipe
10
9
 
11
- if pid = fork
12
- at_exit { Process.kill(:SIGTERM, pid) }
10
+ if pid = fork
11
+ at_exit { Process.kill(:SIGTERM, pid) }
13
12
 
14
- i.close
15
- URI.parse(o.read)
16
- else
17
- o.close
18
- process_child(i)
19
- end
13
+ i.close
14
+ URI.parse(o.read)
15
+ else
16
+ o.close
17
+ process_child(i)
20
18
  end
19
+ end
21
20
 
22
- def self.process_child(i)
23
- EM.run do
24
- client = Rack::Client.new { run EY::Tea::Server.mock_app }
25
-
26
- uri = client.get("/mux-uri").body
27
- i << uri
28
- i.close
29
- end
21
+ def self.process_child(i)
22
+ EM.run do
23
+ client = Rack::Client.new { run HTTTee::Server.mock_app }
30
24
 
31
- exit
25
+ uri = client.get("/mux-uri").body
26
+ i << uri
27
+ i.close
32
28
  end
33
29
 
34
- class ThinMuxer
35
- def initialize(app)
36
- @app = Rack::Mux.new(async_safe(app), thin_options)
37
- end
30
+ exit
31
+ end
38
32
 
39
- def call(env)
40
- @app.call(env)
41
- end
33
+ class ThinMuxer
34
+ def initialize(app)
35
+ @app = Rack::Mux.new(async_safe(app), thin_options)
36
+ end
42
37
 
43
- def async_safe(app)
44
- AsyncFixer.new(app)
45
- end
38
+ def call(env)
39
+ @app.call(env)
40
+ end
46
41
 
47
- def thin_options
48
- { :server => Thin, :environment => 'none' }
49
- end
42
+ def async_safe(app)
43
+ AsyncFixer.new(app)
44
+ end
45
+
46
+ def thin_options
47
+ { :server => Thin, :environment => 'none' }
50
48
  end
49
+ end
51
50
 
52
- class EchoUri
51
+ class EchoUri
53
52
 
54
- def initialize(app)
55
- @app = app
56
- end
53
+ def initialize(app)
54
+ @app = app
55
+ end
57
56
 
58
- def call(env)
59
- if env['PATH_INFO'] == '/mux-uri'
60
- [200, {'Content-Type' => 'text/plain'}, [env['X-Mux-Uri']]]
61
- else
62
- @app.call(env)
63
- end
57
+ def call(env)
58
+ if env['PATH_INFO'] == '/mux-uri'
59
+ [200, {'Content-Type' => 'text/plain'}, [env['X-Mux-Uri']]]
60
+ else
61
+ @app.call(env)
64
62
  end
65
63
  end
66
64
  end
@@ -73,6 +73,10 @@ module EventMachine
73
73
  # These commands should be first
74
74
  auth_and_select_db
75
75
  end
76
+
77
+ def unbind
78
+ @logger.debug { "Disconnected" } if @logger
79
+ end
76
80
  end
77
81
  end
78
82
  end
@@ -0,0 +1,71 @@
1
+ module HTTTee
2
+ module Server
3
+ module SSE
4
+ class Body
5
+ include EventMachine::Deferrable
6
+
7
+ MESSAGE = 'message'
8
+ CTRL = 'ctrl'
9
+ EOF = 'eof'
10
+
11
+ CTRL_TABLE = {
12
+ "\n" => 'newline',
13
+ "\r" => 'carriage-return',
14
+ "\r \n" => 'crlf',
15
+ }
16
+
17
+ def initialize(body)
18
+ @body = body
19
+ end
20
+
21
+ def call(chunks)
22
+ chunks.each do |chunk|
23
+ send_chunk(chunk)
24
+ end
25
+ end
26
+
27
+ def send_chunk(chunk)
28
+ data, ctrl, remaining = chunk.partition(%r{\r \n|\n|\r})
29
+
30
+ send_data(data) unless data.empty?
31
+ send_ctrl(CTRL_TABLE[ctrl]) unless ctrl.empty?
32
+ send_chunk(remaining) unless remaining.empty?
33
+ end
34
+
35
+ def send_data(data)
36
+ send_event(MESSAGE)
37
+
38
+ send_message(data)
39
+ end
40
+
41
+ def send_ctrl(type)
42
+ send_event(CTRL)
43
+
44
+ send_message(type)
45
+ end
46
+
47
+ def send_message(data)
48
+ send "data", data, "\n\n"
49
+ end
50
+
51
+ def send_event(event)
52
+ send "event", event, "\n"
53
+ end
54
+
55
+ def send(type, data, term)
56
+ @body.call(["#{type}: #{data}#{term}"])
57
+ end
58
+
59
+ def succeed
60
+ send_ctrl(EOF)
61
+
62
+ @body.succeed
63
+ end
64
+
65
+ def each(&blk)
66
+ @body.each(&blk)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,96 @@
1
+ module HTTTee
2
+ module Server
3
+ module SSE
4
+ class Middleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ if sse_request?(env)
11
+ wrap(env)
12
+ elsif sse_supported?(env)
13
+ return sse_shell
14
+ end
15
+
16
+ @app.call(env)
17
+ end
18
+
19
+ def wrap(env)
20
+ env['rack.response_body'] = SSE::Body.new(env['rack.response_body'])
21
+
22
+ cb = env['async.callback']
23
+
24
+ env['async.callback'] = lambda do |(status, headers, body)|
25
+ cb.call([status, headers.merge('Content-Type' => 'text/event-stream', 'Connection' => 'Keep-Alive'), body])
26
+ end
27
+ end
28
+
29
+ def sse_request?(env)
30
+ env['HTTP_ACCEPT'] == 'text/event-stream'
31
+ end
32
+
33
+ def sse_supported?(env)
34
+ env['HTTP_USER_AGENT'].to_s =~ /WebKit/ &&
35
+ env['HTTP_ACCEPT'] =~ %r{text/html}
36
+ end
37
+
38
+ def sse_shell
39
+ [200, {'Content-Type' => 'text/html'}, [<<-HTML]]
40
+ <!DOCTYPE html>
41
+ <html>
42
+ <head>
43
+ <meta charset="utf-8" />
44
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
45
+ </head>
46
+ <body><pre><code></code></pre>
47
+ <script>
48
+ var source = new EventSource(window.location.pathname);
49
+ var body = $("html,body");
50
+ var cursor = $("pre code")[0];
51
+ var resetLine = false;
52
+
53
+ function append(data) {
54
+ if(resetLine == true) {
55
+ cursor.innerHTML = data;
56
+ } else {
57
+ cursor.innerHTML += data;
58
+ }
59
+
60
+ resetLine = false;
61
+ }
62
+
63
+ source.onmessage = function(e) {
64
+ append(e.data);
65
+ };
66
+
67
+ source.onerror = function(e) {
68
+ console.log(e);
69
+ };
70
+
71
+ source.addEventListener('ctrl', function(e) {
72
+ if(e.data == 'newline') {
73
+ append("\\n");
74
+ cursor = $("<code></code>").insertAfter(cursor)[0];
75
+ } else if(e.data == 'crlf' || e.data == 'carriage-return') {
76
+ resetLine = true;
77
+ } else if(e.data == 'eof') {
78
+ source.close();
79
+ } else {
80
+ console.log(e);
81
+ }
82
+ }, false);
83
+
84
+ window.setInterval(function(){
85
+ window.scrollTo(0, document.body.scrollHeight);
86
+ }, 100);
87
+
88
+ </script>
89
+ </body>
90
+ </html>
91
+ HTML
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end