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