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.
- 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
@@ -1,21 +1,19 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
def succeed(*a)
|
11
|
+
@body_callback.call("0\r\n\r\n")
|
12
|
+
super
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
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
|
-
|
3
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
1
|
+
module HTTTee
|
2
|
+
module Server
|
3
|
+
class Dechunker
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
def call(env)
|
9
|
+
env['rack.input'] = ChunkedBody.new(env['rack.input']) if chunked?(env)
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
@app.call(env)
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
def chunked?(env)
|
15
|
+
env['HTTP_TRANSFER_ENCODING'] == 'chunked'
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
class ChunkedBody
|
19
|
+
extend Forwardable
|
21
20
|
|
22
|
-
|
21
|
+
CRLF = "\r\n"
|
23
22
|
|
24
|
-
|
23
|
+
attr_reader :input
|
25
24
|
|
26
|
-
|
25
|
+
def_delegators :input, :callback, :errback
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def initialize(input)
|
28
|
+
@input, @buffer = input, ''
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
39
|
-
|
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
|
data/lib/htttee/server/mock.rb
CHANGED
@@ -1,66 +1,64 @@
|
|
1
1
|
require 'rack/mux'
|
2
2
|
|
3
|
-
module
|
4
|
-
module
|
5
|
-
module
|
6
|
-
module Mock
|
3
|
+
module HTTTee
|
4
|
+
module Server
|
5
|
+
module Mock
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
def self.boot_forking_server
|
8
|
+
o,i = IO.pipe
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
if pid = fork
|
11
|
+
at_exit { Process.kill(:SIGTERM, pid) }
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
25
|
+
uri = client.get("/mux-uri").body
|
26
|
+
i << uri
|
27
|
+
i.close
|
32
28
|
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
@app = Rack::Mux.new(async_safe(app), thin_options)
|
37
|
-
end
|
30
|
+
exit
|
31
|
+
end
|
38
32
|
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
class ThinMuxer
|
34
|
+
def initialize(app)
|
35
|
+
@app = Rack::Mux.new(async_safe(app), thin_options)
|
36
|
+
end
|
42
37
|
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
def call(env)
|
39
|
+
@app.call(env)
|
40
|
+
end
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
51
|
+
class EchoUri
|
53
52
|
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
def initialize(app)
|
54
|
+
@app = app
|
55
|
+
end
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
@@ -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
|