unicorn 4.5.0 → 4.6.0pre1
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/GIT-VERSION-GEN +1 -1
- data/bin/unicorn +5 -0
- data/lib/unicorn.rb +2 -0
- data/lib/unicorn/http_request.rb +29 -0
- data/lib/unicorn/http_response.rb +32 -8
- data/lib/unicorn/http_server.rb +6 -2
- data/script/isolate_for_tests +3 -3
- data/t/hijack.ru +42 -0
- data/t/t0005-working_directory_app.rb.sh +4 -1
- data/t/t0200-rack-hijack.sh +27 -0
- data/test/exec/test_exec.rb +1 -0
- metadata +7 -5
data/GIT-VERSION-GEN
CHANGED
data/bin/unicorn
CHANGED
@@ -58,6 +58,11 @@ op = OptionParser.new("", 24, ' ') do |opts|
|
|
58
58
|
ENV["RACK_ENV"] = e
|
59
59
|
end
|
60
60
|
|
61
|
+
opts.on("-N", "--no-default-middleware",
|
62
|
+
"do not load middleware implied by RACK_ENV") do |e|
|
63
|
+
rackup_opts[:no_default_middleware] = true
|
64
|
+
end
|
65
|
+
|
61
66
|
opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
|
62
67
|
rackup_opts[:daemonize] = !!d
|
63
68
|
end
|
data/lib/unicorn.rb
CHANGED
@@ -49,6 +49,8 @@ module Unicorn
|
|
49
49
|
|
50
50
|
pp({ :inner_app => inner_app }) if $DEBUG
|
51
51
|
|
52
|
+
return inner_app if op[:no_default_middleware]
|
53
|
+
|
52
54
|
# return value, matches rackup defaults based on env
|
53
55
|
# Unicorn does not support persistent connections, but Rainbows!
|
54
56
|
# and Zbatery both do. Users accustomed to the Rack::Server default
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -91,6 +91,35 @@ class Unicorn::HttpParser
|
|
91
91
|
|
92
92
|
e[RACK_INPUT] = 0 == content_length ?
|
93
93
|
NULL_IO : @@input_class.new(socket, self)
|
94
|
+
hijack_setup(e, socket)
|
94
95
|
e.merge!(DEFAULTS)
|
95
96
|
end
|
97
|
+
|
98
|
+
# Rack 1.5.0 (protocol version 1.2) adds hijack request support
|
99
|
+
if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
|
100
|
+
DEFAULTS["rack.hijack?"] = true
|
101
|
+
|
102
|
+
# FIXME: asking for clarification about this in
|
103
|
+
# http://mid.gmane.org/20130122100802.GA28585@dcvr.yhbt.net
|
104
|
+
DEFAULTS["rack.version"] = [1, 2]
|
105
|
+
|
106
|
+
RACK_HIJACK = "rack.hijack".freeze
|
107
|
+
RACK_HIJACK_IO = "rack.hijack_io".freeze
|
108
|
+
|
109
|
+
def hijacked?
|
110
|
+
env.include?(RACK_HIJACK_IO)
|
111
|
+
end
|
112
|
+
|
113
|
+
def hijack_setup(e, socket)
|
114
|
+
e[RACK_HIJACK] = proc { e[RACK_HIJACK_IO] ||= socket }
|
115
|
+
end
|
116
|
+
else
|
117
|
+
# old Rack, do nothing.
|
118
|
+
def hijack_setup(e, _)
|
119
|
+
end
|
120
|
+
|
121
|
+
def hijacked?
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
96
125
|
end
|
@@ -25,6 +25,7 @@ module Unicorn::HttpResponse
|
|
25
25
|
def http_response_write(socket, status, headers, body,
|
26
26
|
response_start_sent=false)
|
27
27
|
status = CODES[status.to_i] || status
|
28
|
+
hijack = nil
|
28
29
|
|
29
30
|
http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
|
30
31
|
if headers
|
@@ -33,19 +34,42 @@ module Unicorn::HttpResponse
|
|
33
34
|
"Status: #{status}\r\n" \
|
34
35
|
"Connection: close\r\n"
|
35
36
|
headers.each do |key, value|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
case key
|
38
|
+
when %r{\A(?:Date\z|Connection\z)}i
|
39
|
+
next
|
40
|
+
when "rack.hijack"
|
41
|
+
# this was an illegal key in Rack < 1.5, so it should be
|
42
|
+
# OK to silently discard it for those older versions
|
43
|
+
hijack = hijack_prepare(value)
|
40
44
|
else
|
41
|
-
|
45
|
+
if value =~ /\n/
|
46
|
+
# avoiding blank, key-only cookies with /\n+/
|
47
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
48
|
+
else
|
49
|
+
buf << "#{key}: #{value}\r\n"
|
50
|
+
end
|
42
51
|
end
|
43
52
|
end
|
44
53
|
socket.write(buf << CRLF)
|
45
54
|
end
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
|
56
|
+
if hijack
|
57
|
+
body = nil # ensure we do not close body
|
58
|
+
hijack.call(socket)
|
59
|
+
else
|
60
|
+
body.each { |chunk| socket.write(chunk) }
|
61
|
+
end
|
62
|
+
ensure
|
63
|
+
body.respond_to?(:close) and body.close
|
64
|
+
end
|
65
|
+
|
66
|
+
# Rack 1.5.0 (protocol version 1.2) adds response hijacking support
|
67
|
+
if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
|
68
|
+
def hijack_prepare(value)
|
69
|
+
value
|
70
|
+
end
|
71
|
+
else
|
72
|
+
def hijack_prepare(_)
|
73
|
+
end
|
50
74
|
end
|
51
75
|
end
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -550,17 +550,21 @@ class Unicorn::HttpServer
|
|
550
550
|
# in 3 easy steps: read request, call app, write app response
|
551
551
|
def process_client(client)
|
552
552
|
status, headers, body = @app.call(env = @request.read(client))
|
553
|
+
return if @request.hijacked?
|
553
554
|
|
554
555
|
if 100 == status.to_i
|
555
556
|
client.write(expect_100_response)
|
556
557
|
env.delete(Unicorn::Const::HTTP_EXPECT)
|
557
558
|
status, headers, body = @app.call(env)
|
559
|
+
return if @request.hijacked?
|
558
560
|
end
|
559
561
|
@request.headers? or headers = nil
|
560
562
|
http_response_write(client, status, headers, body,
|
561
563
|
@request.response_start_sent)
|
562
|
-
client.
|
563
|
-
|
564
|
+
unless client.closed? # rack.hijack may've close this for us
|
565
|
+
client.shutdown # in case of fork() in Rack app
|
566
|
+
client.close # flush and uncork socket immediately, no keepalive
|
567
|
+
end
|
564
568
|
rescue => e
|
565
569
|
handle_error(client, e)
|
566
570
|
end
|
data/script/isolate_for_tests
CHANGED
@@ -16,10 +16,10 @@ opts = {
|
|
16
16
|
|
17
17
|
pid = fork do
|
18
18
|
Isolate.now!(opts) do
|
19
|
-
gem 'raindrops', '0.
|
19
|
+
gem 'raindrops', '0.10.0'
|
20
20
|
gem 'kgio-monkey', '0.4.0'
|
21
|
-
gem 'kgio', '2.
|
22
|
-
gem 'rack', '1.
|
21
|
+
gem 'kgio', '2.8.0'
|
22
|
+
gem 'rack', '1.5.1'
|
23
23
|
end
|
24
24
|
end
|
25
25
|
_, status = Process.waitpid2(pid)
|
data/t/hijack.ru
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
use Rack::Lint
|
2
|
+
use Rack::ContentLength
|
3
|
+
use Rack::ContentType, "text/plain"
|
4
|
+
class DieIfUsed
|
5
|
+
def each
|
6
|
+
abort "body.each called after response hijack\n"
|
7
|
+
end
|
8
|
+
|
9
|
+
def close
|
10
|
+
abort "body.close called after response hijack\n"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
run lambda { |env|
|
14
|
+
case env["PATH_INFO"]
|
15
|
+
when "/hijack_req"
|
16
|
+
if env["rack.hijack?"]
|
17
|
+
io = env["rack.hijack"].call
|
18
|
+
if io.respond_to?(:read_nonblock) &&
|
19
|
+
env["rack.hijack_io"].respond_to?(:read_nonblock)
|
20
|
+
|
21
|
+
# exercise both, since we Rack::Lint may use different objects
|
22
|
+
env["rack.hijack_io"].write("HTTP/1.0 200 OK\r\n\r\n")
|
23
|
+
io.write("request.hijacked")
|
24
|
+
io.close
|
25
|
+
return [ 500, {}, DieIfUsed.new ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
[ 500, {}, [ "hijack BAD\n" ] ]
|
29
|
+
when "/hijack_res"
|
30
|
+
r = "response.hijacked"
|
31
|
+
[ 200,
|
32
|
+
{
|
33
|
+
"Content-Length" => r.bytesize.to_s,
|
34
|
+
"rack.hijack" => proc do |io|
|
35
|
+
io.write(r)
|
36
|
+
io.close
|
37
|
+
end
|
38
|
+
},
|
39
|
+
DieIfUsed.new
|
40
|
+
]
|
41
|
+
end
|
42
|
+
}
|
@@ -11,7 +11,10 @@ t_begin "setup and start" && {
|
|
11
11
|
cat > $t_pfx.app/fooapp.rb <<\EOF
|
12
12
|
class Fooapp
|
13
13
|
def self.call(env)
|
14
|
-
|
14
|
+
# Rack::Lint in 1.5.0 requires headers to be a hash
|
15
|
+
h = [%w(Content-Type text/plain), %w(Content-Length 2)]
|
16
|
+
h = Rack::Utils::HeaderHash.new(h)
|
17
|
+
[ 200, h, %w(HI) ]
|
15
18
|
end
|
16
19
|
end
|
17
20
|
EOF
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
t_plan 5 "rack.hijack tests (Rack 1.5+ (Rack::VERSION >= [ 1,2]))"
|
4
|
+
|
5
|
+
t_begin "setup and start" && {
|
6
|
+
unicorn_setup
|
7
|
+
unicorn -D -c $unicorn_config hijack.ru
|
8
|
+
unicorn_wait_start
|
9
|
+
}
|
10
|
+
|
11
|
+
t_begin "check request hijack" && {
|
12
|
+
test "xrequest.hijacked" = x"$(curl -sSfv http://$listen/hijack_req)"
|
13
|
+
}
|
14
|
+
|
15
|
+
t_begin "check response hijack" && {
|
16
|
+
test "xresponse.hijacked" = x"$(curl -sSfv http://$listen/hijack_res)"
|
17
|
+
}
|
18
|
+
|
19
|
+
t_begin "killing succeeds" && {
|
20
|
+
kill $unicorn_pid
|
21
|
+
}
|
22
|
+
|
23
|
+
t_begin "check stderr" && {
|
24
|
+
check_stderr
|
25
|
+
}
|
26
|
+
|
27
|
+
t_done
|
data/test/exec/test_exec.rb
CHANGED
@@ -323,6 +323,7 @@ EOF
|
|
323
323
|
# mobile phone or netbook on a slow connection :)
|
324
324
|
assert lines.size <= 24, "help height fits in an ANSI terminal window"
|
325
325
|
lines.each do |line|
|
326
|
+
line.chomp!
|
326
327
|
assert line.size <= 80, "help width fits in an ANSI terminal window"
|
327
328
|
end
|
328
329
|
end
|
metadata
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
name: !binary |-
|
3
3
|
dW5pY29ybg==
|
4
4
|
version: !ruby/object:Gem::Version
|
5
|
-
version: 4.
|
6
|
-
prerelease:
|
5
|
+
version: 4.6.0pre1
|
6
|
+
prerelease: 5
|
7
7
|
platform: ruby
|
8
8
|
authors:
|
9
9
|
- Unicorn hackers
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-01-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: !binary |-
|
@@ -255,6 +255,7 @@ files:
|
|
255
255
|
- t/detach.ru
|
256
256
|
- t/env.ru
|
257
257
|
- t/heartbeat-timeout.ru
|
258
|
+
- t/hijack.ru
|
258
259
|
- t/listener_names.ru
|
259
260
|
- t/my-tap-lib.sh
|
260
261
|
- t/oob_gc.ru
|
@@ -295,6 +296,7 @@ files:
|
|
295
296
|
- t/t0100-rack-input-tests.sh
|
296
297
|
- t/t0116-client_body_buffer_size.sh
|
297
298
|
- t/t0116.ru
|
299
|
+
- t/t0200-rack-hijack.sh
|
298
300
|
- t/t0600-https-server-basic.sh
|
299
301
|
- t/t9000-preread-input.sh
|
300
302
|
- t/t9001-oob_gc.sh
|
@@ -349,9 +351,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
349
351
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
350
352
|
none: false
|
351
353
|
requirements:
|
352
|
-
- - ! '
|
354
|
+
- - ! '>'
|
353
355
|
- !ruby/object:Gem::Version
|
354
|
-
version:
|
356
|
+
version: 1.3.1
|
355
357
|
requirements: []
|
356
358
|
rubyforge_project: !binary |-
|
357
359
|
bW9uZ3JlbA==
|