thin 1.8.2 → 2.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of thin might be problematic. Click here for more details.
- data/.gitignore +9 -0
- data/CHANGELOG +29 -116
- data/Gemfile +8 -0
- data/README.md +44 -78
- data/Rakefile +28 -18
- data/bin/thin +4 -4
- data/examples/async.ru +21 -0
- data/examples/thin.conf.rb +39 -0
- data/lib/thin/async.rb +108 -0
- data/lib/thin/backends/prefork.rb +44 -0
- data/lib/thin/backends/single_process.rb +28 -0
- data/lib/thin/chunked_body.rb +28 -0
- data/lib/thin/configurator.rb +118 -0
- data/lib/thin/connection.rb +246 -172
- data/lib/thin/listener.rb +114 -0
- data/lib/thin/request.rb +94 -76
- data/lib/thin/response.rb +112 -45
- data/lib/thin/runner.rb +134 -197
- data/lib/thin/server.rb +203 -252
- data/lib/thin/system.rb +49 -0
- data/lib/thin/version.rb +12 -27
- data/lib/thin.rb +2 -44
- data/man/index.txt +3 -0
- data/man/thin-conf.5.ronn +121 -0
- data/man/thin.1.ronn +105 -0
- data/site/.gitignore +2 -0
- data/site/README.md +21 -0
- data/site/Rakefile +20 -0
- data/site/config.ru +4 -0
- data/site/public/images/grid.png +0 -0
- data/site/public/javascripts/dd_belatedpng.js +13 -0
- data/site/public/javascripts/modernizr-1.6.min.js +30 -0
- data/site/public/man/thin-conf.5.html +220 -0
- data/site/public/man/thin.1.html +177 -0
- data/site/site/assets/javascripts/main.coffee +2 -0
- data/site/site/assets/stylesheets/_config.scss +55 -0
- data/site/site/assets/stylesheets/main.scss +24 -0
- data/site/site/helpers.rb +17 -0
- data/site/site/layouts/base.erb +55 -0
- data/site/site/layouts/default.erb +17 -0
- data/site/site/pages/about.md +5 -0
- data/site/site/pages/index.erb +10 -0
- data/site/site/partials/.gitkeep +0 -0
- data/test/fixtures/big.txt +1 -0
- data/test/fixtures/small.txt +1 -0
- data/test/fixtures/thin.conf.rb +15 -0
- data/test/integration/async_test.rb +35 -0
- data/test/integration/big_request_test.rb +30 -0
- data/test/integration/config.ru +57 -0
- data/test/integration/daemonize_test.rb +26 -0
- data/test/integration/env_test.rb +44 -0
- data/test/integration/error_test.rb +37 -0
- data/test/integration/file_sending_test.rb +24 -0
- data/test/integration/keep_alive_test.rb +35 -0
- data/test/integration/robustness_test.rb +37 -0
- data/test/integration/single_process_test.rb +15 -0
- data/test/integration/socket_family_test.rb +38 -0
- data/test/integration/worker_test.rb +22 -0
- data/test/test_helper.rb +195 -0
- data/test/unit/configurator_test.rb +43 -0
- data/test/unit/connection_test.rb +94 -0
- data/test/unit/listener_test.rb +74 -0
- data/test/unit/request_test.rb +74 -0
- data/test/unit/response_test.rb +90 -0
- data/test/unit/server_test.rb +29 -0
- data/test/unit/system_test.rb +17 -0
- data/thin.gemspec +26 -0
- data/v2.todo +21 -0
- metadata +138 -93
- checksums.yaml +0 -7
- data/example/adapter.rb +0 -32
- data/example/async_app.ru +0 -126
- data/example/async_chat.ru +0 -247
- data/example/async_tailer.ru +0 -100
- data/example/config.ru +0 -22
- data/example/monit_sockets +0 -20
- data/example/monit_unixsock +0 -20
- data/example/myapp.rb +0 -1
- data/example/ramaze.ru +0 -12
- data/example/thin.god +0 -80
- data/example/thin_solaris_smf.erb +0 -36
- data/example/thin_solaris_smf.readme.txt +0 -150
- data/example/vlad.rake +0 -72
- data/ext/thin_parser/common.rl +0 -59
- data/ext/thin_parser/ext_help.h +0 -14
- data/ext/thin_parser/extconf.rb +0 -6
- data/ext/thin_parser/parser.c +0 -1447
- data/ext/thin_parser/parser.h +0 -49
- data/ext/thin_parser/parser.rl +0 -152
- data/ext/thin_parser/thin.c +0 -435
- data/lib/rack/adapter/loader.rb +0 -75
- data/lib/rack/adapter/rails.rb +0 -178
- data/lib/rack/handler/thin.rb +0 -38
- data/lib/thin/backends/base.rb +0 -169
- data/lib/thin/backends/swiftiply_client.rb +0 -66
- data/lib/thin/backends/tcp_server.rb +0 -34
- data/lib/thin/backends/unix_server.rb +0 -56
- data/lib/thin/command.rb +0 -53
- data/lib/thin/controllers/cluster.rb +0 -178
- data/lib/thin/controllers/controller.rb +0 -189
- data/lib/thin/controllers/service.rb +0 -76
- data/lib/thin/controllers/service.sh.erb +0 -39
- data/lib/thin/daemonizing.rb +0 -199
- data/lib/thin/headers.rb +0 -47
- data/lib/thin/logging.rb +0 -174
- data/lib/thin/stats.html.erb +0 -216
- data/lib/thin/stats.rb +0 -52
- data/lib/thin/statuses.rb +0 -48
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ErrorTest < IntegrationTestCase
|
4
|
+
def test_raise
|
5
|
+
thin :log => "/dev/null"
|
6
|
+
|
7
|
+
get "/raise"
|
8
|
+
|
9
|
+
assert_status 500
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_raise_without_middlewares
|
13
|
+
thin :env => "none", :log => "/dev/null"
|
14
|
+
|
15
|
+
get "/raise"
|
16
|
+
|
17
|
+
assert_status 500
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_logs_errors
|
21
|
+
thin :env => "none"
|
22
|
+
|
23
|
+
get "/raise"
|
24
|
+
assert_match "[ERROR] ouch", read_log
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_parse_error
|
28
|
+
thin :log => "/dev/null"
|
29
|
+
|
30
|
+
socket do |s|
|
31
|
+
s.write "!!!WTH??YO111!\r\n"
|
32
|
+
s.flush
|
33
|
+
|
34
|
+
assert_match "HTTP/1.1 400 Bad Request", s.read
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FileSendingTest < IntegrationTestCase
|
4
|
+
def test_small_file
|
5
|
+
thin :env => "none"
|
6
|
+
|
7
|
+
get "/small.txt"
|
8
|
+
|
9
|
+
assert_status 200
|
10
|
+
assert_header "Transfer-Encoding", "chunked"
|
11
|
+
assert_equal File.size(File.expand_path("../../fixtures/small.txt", __FILE__)), @response.body.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_big_file
|
15
|
+
thin :env => "none"
|
16
|
+
|
17
|
+
# Just big enough (>16K) to trigger EM mapped streamer.
|
18
|
+
get "/big.txt"
|
19
|
+
|
20
|
+
assert_status 200
|
21
|
+
assert_header "Transfer-Encoding", "chunked"
|
22
|
+
assert_equal File.size(File.expand_path("../../fixtures/big.txt", __FILE__)), @response.body.size
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class KeepAliveTest < IntegrationTestCase
|
4
|
+
def test_enabled_by_default_on_http_1_1
|
5
|
+
thin
|
6
|
+
|
7
|
+
get '/'
|
8
|
+
|
9
|
+
assert_status 200
|
10
|
+
assert_header "Connection", "keep-alive"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_disabled_on_http_1_0
|
14
|
+
thin
|
15
|
+
|
16
|
+
socket do |s|
|
17
|
+
s.write("GET / HTTP/1.0\r\n")
|
18
|
+
s.write("\r\n")
|
19
|
+
s.flush
|
20
|
+
|
21
|
+
assert_match "Connection: close", s.readpartial(1024)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_limited
|
26
|
+
thin do
|
27
|
+
keep_alive_requests 0
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/'
|
31
|
+
|
32
|
+
assert_status 200
|
33
|
+
assert_header "Connection", "close"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RobustnessTest < IntegrationTestCase
|
4
|
+
def test_should_not_crash_when_header_too_large
|
5
|
+
thin :log => "/dev/null"
|
6
|
+
|
7
|
+
100.times do
|
8
|
+
begin
|
9
|
+
socket do |s|
|
10
|
+
s.write("GET / HTTP/1.1\r\n")
|
11
|
+
s.write("Host: localhost\r\n")
|
12
|
+
s.write("Connection: close\r\n")
|
13
|
+
10000.times do
|
14
|
+
s.write("X-Foo: #{'x' * 100}\r\n")
|
15
|
+
s.flush
|
16
|
+
end
|
17
|
+
s.write("\r\n")
|
18
|
+
s.read
|
19
|
+
end
|
20
|
+
rescue Errno::EPIPE, Errno::ECONNRESET
|
21
|
+
# Ignore.
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_incomplete_request
|
27
|
+
thin do
|
28
|
+
timeout 1
|
29
|
+
end
|
30
|
+
|
31
|
+
socket do |s|
|
32
|
+
s.write "GET /?this HTTP/1.1\r\n"
|
33
|
+
s.write "Host:"
|
34
|
+
assert_equal "", s.read
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SingleProcessTest < IntegrationTestCase
|
4
|
+
def test_stop_with_int_signal
|
5
|
+
thin :env => "none" do
|
6
|
+
worker_processes 0
|
7
|
+
end
|
8
|
+
|
9
|
+
Process.kill "INT", @pid
|
10
|
+
_, status = Process.wait2 @pid
|
11
|
+
@pid = nil
|
12
|
+
|
13
|
+
assert status.success?
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SocketFamilyTest < IntegrationTestCase
|
4
|
+
def test_ipv4
|
5
|
+
thin do
|
6
|
+
listen PORT
|
7
|
+
end
|
8
|
+
|
9
|
+
get "/"
|
10
|
+
|
11
|
+
assert_status 200
|
12
|
+
assert_response_equals "ok"
|
13
|
+
assert_header "Content-Type", "text/html"
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_ipv6
|
17
|
+
thin do
|
18
|
+
listen "[::]:#{PORT}"
|
19
|
+
end
|
20
|
+
|
21
|
+
get "/"
|
22
|
+
|
23
|
+
assert_status 200
|
24
|
+
assert_response_equals "ok"
|
25
|
+
assert_header "Content-Type", "text/html"
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_unix_socket
|
29
|
+
thin do
|
30
|
+
listen UNIX_SOCKET
|
31
|
+
end
|
32
|
+
|
33
|
+
unix_socket do |s|
|
34
|
+
s.write "GET / HTTP/1.1\r\n\r\n"
|
35
|
+
assert_match "HTTP/1.1 200 OK", s.read
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class WorkerTest < IntegrationTestCase
|
4
|
+
def test_restart_worker_on_exit
|
5
|
+
thin do
|
6
|
+
worker_processes 1
|
7
|
+
end
|
8
|
+
|
9
|
+
assert_raise(EOFError) { get "/exit" }
|
10
|
+
get "/"
|
11
|
+
|
12
|
+
assert_status 200
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_timeout
|
16
|
+
thin do
|
17
|
+
timeout 1
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_raise(EOFError) { get "/sleep?sec=2" }
|
21
|
+
end
|
22
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require(:default, :test)
|
3
|
+
|
4
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
5
|
+
require "thin"
|
6
|
+
require "test/unit"
|
7
|
+
require "mocha"
|
8
|
+
require "net/http"
|
9
|
+
require "timeout"
|
10
|
+
|
11
|
+
class Test::Unit::TestCase
|
12
|
+
include Mocha::API # fix mocha API not being included in minitest
|
13
|
+
|
14
|
+
# Silences any stream for the duration of the block.
|
15
|
+
#
|
16
|
+
# silence_stream(STDOUT) do
|
17
|
+
# puts 'This will never be seen'
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# puts 'But this will'
|
21
|
+
#
|
22
|
+
# (Taken from ActiveSupport)
|
23
|
+
def silence_stream(stream)
|
24
|
+
old_stream = stream.dup
|
25
|
+
stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
|
26
|
+
stream.sync = true
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
stream.reopen(old_stream)
|
30
|
+
end
|
31
|
+
|
32
|
+
def silence_streams
|
33
|
+
silence_stream($stdout) { silence_stream($stderr) { yield } }
|
34
|
+
end
|
35
|
+
|
36
|
+
def capture_streams
|
37
|
+
out = StringIO.new
|
38
|
+
silence_streams do
|
39
|
+
$stdout = out
|
40
|
+
$stderr = out
|
41
|
+
yield
|
42
|
+
out.read
|
43
|
+
end
|
44
|
+
ensure
|
45
|
+
$stdout = STDOUT
|
46
|
+
$stderr = STDERR
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ConfigWriter < BasicObject
|
51
|
+
def initialize(&block)
|
52
|
+
@lines = []
|
53
|
+
instance_eval(&block) if block
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_missing(name, *args)
|
57
|
+
@lines << name.to_s + " " + args.map { |arg| arg.inspect }.join(", ")
|
58
|
+
end
|
59
|
+
|
60
|
+
def write(file)
|
61
|
+
::File.open(file, "w") do |f|
|
62
|
+
f << "# Generated during tests by ConfigWriter in test_helper.rb\n"
|
63
|
+
f << @lines.join("\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class IntegrationTestCase < Test::Unit::TestCase
|
69
|
+
PORT = 8181
|
70
|
+
UNIX_SOCKET = "/tmp/thin-test.sock"
|
71
|
+
LOG_FILE = "test.log"
|
72
|
+
PID_FILE = "test.pid"
|
73
|
+
|
74
|
+
# Start a new thin server from the command line utility mimicing real world usage.
|
75
|
+
# @param runner_options Options to pass to the command line utility.
|
76
|
+
# @param configuration Block of configuration to pass to the configurator.
|
77
|
+
def thin(runner_options={}, &configuration)
|
78
|
+
raise "Server already started in process #" + File.read(PID_FILE) if File.exist?(PID_FILE)
|
79
|
+
|
80
|
+
# Cleanup
|
81
|
+
File.delete LOG_FILE if File.exist?(LOG_FILE)
|
82
|
+
File.delete UNIX_SOCKET if File.exist?(UNIX_SOCKET)
|
83
|
+
|
84
|
+
root = File.expand_path('../..', __FILE__)
|
85
|
+
|
86
|
+
# Generate a config file from the configuration block
|
87
|
+
config_file = "test.conf.rb"
|
88
|
+
config = ConfigWriter.new do
|
89
|
+
worker_processes 1
|
90
|
+
end
|
91
|
+
config.instance_eval(&configuration) if configuration
|
92
|
+
config.write(config_file)
|
93
|
+
|
94
|
+
# Command line options
|
95
|
+
runner_options = { :config => config_file, :port => PORT, :log => LOG_FILE, :pid => PID_FILE }.merge(runner_options)
|
96
|
+
|
97
|
+
# Launch the server from the shell
|
98
|
+
command = "bundle exec ruby -I#{root}/lib " +
|
99
|
+
"#{root}/bin/thin " +
|
100
|
+
runner_options.map { |k, v| (k.size > 1 ? "-" : "") + "-#{k}" + (TrueClass === v ? "" : "=#{v}") }.join(" ") + " " +
|
101
|
+
File.expand_path("../integration/config.ru", __FILE__)
|
102
|
+
launcher_pid = silence_stream($stdout) { spawn command }
|
103
|
+
|
104
|
+
tries = 0
|
105
|
+
wait = 5 #sec
|
106
|
+
until running?
|
107
|
+
sleep 0.1
|
108
|
+
tries += 1
|
109
|
+
raise "Failed to start server under #{wait} sec" if tries > wait/0.1
|
110
|
+
end
|
111
|
+
|
112
|
+
@pid = File.read(PID_FILE).to_i
|
113
|
+
|
114
|
+
launcher_pid
|
115
|
+
end
|
116
|
+
|
117
|
+
def teardown
|
118
|
+
if @pid
|
119
|
+
Process.kill "INT", @pid
|
120
|
+
begin
|
121
|
+
Process.wait @pid
|
122
|
+
rescue Errno::ECHILD
|
123
|
+
# Process is not a child. We ping until process dies.
|
124
|
+
sleep 0.1 while Process.kill 0, @pid rescue false
|
125
|
+
end
|
126
|
+
@pid = nil
|
127
|
+
end
|
128
|
+
raise "Didn't delete PID file." if File.exist?(PID_FILE)
|
129
|
+
@response = nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def running?
|
133
|
+
return true if File.exist?(UNIX_SOCKET)
|
134
|
+
get("/")
|
135
|
+
true
|
136
|
+
rescue Errno::ECONNREFUSED
|
137
|
+
false
|
138
|
+
rescue
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
def get(path, host="localhost")
|
143
|
+
@response = Timeout.timeout(3) { Net::HTTP.get_response(URI.parse("http://#{host}:#{PORT}" + path)) }
|
144
|
+
end
|
145
|
+
|
146
|
+
def post(path, params={})
|
147
|
+
Timeout.timeout(3) do
|
148
|
+
uri = URI.parse("http://localhost:#{PORT}" + path)
|
149
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
150
|
+
|
151
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
152
|
+
request.set_form_data(params)
|
153
|
+
|
154
|
+
@response = http.request(request)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def socket
|
159
|
+
@response = nil
|
160
|
+
socket = TCPSocket.new("localhost", PORT)
|
161
|
+
yield socket
|
162
|
+
ensure
|
163
|
+
socket.close rescue nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def unix_socket
|
167
|
+
@response = nil
|
168
|
+
socket = UNIXSocket.new(UNIX_SOCKET)
|
169
|
+
yield socket
|
170
|
+
ensure
|
171
|
+
socket.close rescue nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def assert_status(status)
|
175
|
+
assert_equal status, @response.code.to_i
|
176
|
+
end
|
177
|
+
|
178
|
+
def assert_response_equals(string)
|
179
|
+
assert_equal string, @response.body
|
180
|
+
end
|
181
|
+
|
182
|
+
def assert_response_includes(*strings)
|
183
|
+
strings.each do |string|
|
184
|
+
assert @response.body.include?(string), "expected response to include #{string}, but got: #{@response.body}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def assert_header(key, value)
|
189
|
+
assert_equal value, @response[key]
|
190
|
+
end
|
191
|
+
|
192
|
+
def read_log
|
193
|
+
File.read(LOG_FILE)
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require "thin/configurator"
|
3
|
+
|
4
|
+
class ConfiguratorTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@config = Thin::Configurator.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_worker_processes
|
10
|
+
@config.worker_processes 5
|
11
|
+
assert_equal 5, @config.options[:worker_processes]
|
12
|
+
assert_raise(ArgumentError) { @config.worker_processes "5" }
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_use_epoll
|
16
|
+
@config.use_epoll true
|
17
|
+
@config.use_epoll false
|
18
|
+
assert_raise(ArgumentError) { @config.use_epoll 1 }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_listen
|
22
|
+
@config.listen 3000
|
23
|
+
@config.listen "0.0.0.0:3000"
|
24
|
+
assert_raise(ArgumentError) { @config.listen false }
|
25
|
+
assert_equal 2, @config.options[:listeners].size
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_before_fork
|
29
|
+
@config.before_fork { :ok }
|
30
|
+
assert_equal :ok, @config.options[:before_fork].call(:server)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_load_from_file
|
34
|
+
Thin::Configurator.load(File.expand_path("../../fixtures/thin.conf.rb", __FILE__))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_apply
|
38
|
+
server = Thin::Server.new {}
|
39
|
+
@config.worker_processes 10000
|
40
|
+
@config.apply(server)
|
41
|
+
assert_equal 10000, server.worker_processes
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'thin/connection'
|
3
|
+
|
4
|
+
class ConnectionTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@connection = Thin::Connection.new(nil)
|
7
|
+
@connection.server = self
|
8
|
+
@connection.post_init
|
9
|
+
|
10
|
+
@connection.stubs(:send_data)
|
11
|
+
@connection.stubs(:close_connection_after_writing)
|
12
|
+
@connection.stubs(:socket_address).returns("127.0.0.1")
|
13
|
+
end
|
14
|
+
|
15
|
+
#### Server methods
|
16
|
+
def app
|
17
|
+
proc do |env|
|
18
|
+
[200, {"Content-Type" => "text/plain"}, ["ok"]]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
def threaded?; false end
|
22
|
+
def prefork?; false end
|
23
|
+
####
|
24
|
+
|
25
|
+
def teardown
|
26
|
+
@connection.unbind
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_send_response_body
|
30
|
+
request = <<-EOS
|
31
|
+
GET / HTTP/1.1
|
32
|
+
Host: localhost:3000
|
33
|
+
|
34
|
+
EOS
|
35
|
+
|
36
|
+
@connection.expects(:send_data).with(includes("Content-Type: text/plain"))
|
37
|
+
@connection.expects(:send_data).with("ok")
|
38
|
+
@connection.expects(:close_connection_after_writing).once
|
39
|
+
|
40
|
+
@connection.receive_data(request)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_parse_get_request
|
44
|
+
request = <<-EOS
|
45
|
+
GET /path?yo=dude HTTP/1.1
|
46
|
+
Host: localhost:3000
|
47
|
+
X-Special: awesome
|
48
|
+
|
49
|
+
EOS
|
50
|
+
|
51
|
+
@connection.expects(:process)
|
52
|
+
@connection.receive_data(request)
|
53
|
+
|
54
|
+
env = @connection.request.env
|
55
|
+
|
56
|
+
assert_equal "GET", env["REQUEST_METHOD"]
|
57
|
+
assert_equal "/path", env["PATH_INFO"]
|
58
|
+
assert_equal "HTTP/1.1", env["SERVER_PROTOCOL"]
|
59
|
+
assert_equal "HTTP/1.1", env["HTTP_VERSION"]
|
60
|
+
assert_equal "yo=dude", env["QUERY_STRING"]
|
61
|
+
assert_equal "localhost:3000", env["HTTP_HOST"]
|
62
|
+
assert_equal "awesome", env["HTTP_X_SPECIAL"]
|
63
|
+
|
64
|
+
# rack. values
|
65
|
+
assert_respond_to env["rack.input"], :read
|
66
|
+
assert_equal "http", env["rack.url_scheme"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_parse_post_request
|
70
|
+
request = <<-EOS
|
71
|
+
POST /path HTTP/1.1
|
72
|
+
Host: localhost:3000
|
73
|
+
Content-Type: text/plain
|
74
|
+
Content-Length: 2
|
75
|
+
|
76
|
+
hi
|
77
|
+
EOS
|
78
|
+
|
79
|
+
@connection.expects(:process)
|
80
|
+
@connection.receive_data(request)
|
81
|
+
|
82
|
+
env = @connection.request.env
|
83
|
+
|
84
|
+
assert_equal "POST", env["REQUEST_METHOD"]
|
85
|
+
assert_equal "/path", env["PATH_INFO"]
|
86
|
+
assert_equal "hi", env["rack.input"].read
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_async_response_do_not_send_response
|
90
|
+
@connection.expects(:send_response).never
|
91
|
+
|
92
|
+
@connection.process_response(Thin::Response::ASYNC)
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ListenerTest < Test::Unit::TestCase
|
4
|
+
def test_parse_integer_port_addresses
|
5
|
+
listener = Thin::Listener.new(3000)
|
6
|
+
assert_equal "", listener.host
|
7
|
+
assert_equal 3000, listener.port
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_parse_star_port_addresses
|
11
|
+
listener = Thin::Listener.new("*:3000")
|
12
|
+
assert_equal "", listener.host
|
13
|
+
assert_equal 3000, listener.port
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_parse_ipv4_and_port
|
17
|
+
listener = Thin::Listener.new("127.0.0.1:3000")
|
18
|
+
assert_equal "127.0.0.1", listener.host
|
19
|
+
assert_equal 3000, listener.port
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_parse_ipv6_and_port
|
23
|
+
listener = Thin::Listener.new("[::]:3000")
|
24
|
+
assert_equal "::", listener.host
|
25
|
+
assert_equal 3000, listener.port
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_parse_unix_socket
|
29
|
+
listener = Thin::Listener.new("/file.sock")
|
30
|
+
assert_nil listener.host
|
31
|
+
assert_nil listener.port
|
32
|
+
assert_equal "/file.sock", listener.socket_file
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_parse_relative_unix_socket_with_prefix
|
36
|
+
listener = Thin::Listener.new("unix:file.sock")
|
37
|
+
assert_nil listener.host
|
38
|
+
assert_nil listener.port
|
39
|
+
assert_equal "file.sock", listener.socket_file
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_parse_invalid_address
|
43
|
+
assert_raise(ArgumentError) { Thin::Listener.new("file.sock") }
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_ipv4_socket
|
47
|
+
listener = Thin::Listener.new(3000)
|
48
|
+
assert_kind_of Socket, listener.socket
|
49
|
+
ensure
|
50
|
+
listener.close
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_ipv6_socket
|
54
|
+
listener = Thin::Listener.new("[::]:3000")
|
55
|
+
assert_kind_of Socket, listener.socket
|
56
|
+
ensure
|
57
|
+
listener.close
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_unix_socket
|
61
|
+
listener = Thin::Listener.new("/tmp/thin-test.sock")
|
62
|
+
assert_kind_of Socket, listener.socket
|
63
|
+
ensure
|
64
|
+
listener.close
|
65
|
+
assert ! File.exist?("tmp/thin-test.sock")
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_set_socket_option
|
69
|
+
listener = Thin::Listener.new(3000, :tcp_no_delay => true)
|
70
|
+
assert listener.socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY)
|
71
|
+
ensure
|
72
|
+
listener.close
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require "thin/request"
|
3
|
+
require "rack"
|
4
|
+
|
5
|
+
class RequestTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@request = Thin::Request.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_env_contains_requires_rack_variables
|
11
|
+
assert_respond_to @request.env['rack.input'], :read
|
12
|
+
assert_kind_of IO, @request.env['rack.errors']
|
13
|
+
assert_equal [1, 1], @request.env['rack.version']
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_convert_parser_headers_to_rack_env
|
17
|
+
@request.headers = {
|
18
|
+
"Host" => "localhost:9292",
|
19
|
+
"Connection" => "close"
|
20
|
+
}
|
21
|
+
|
22
|
+
assert_equal "localhost:9292", @request.env["HTTP_HOST"]
|
23
|
+
assert_equal "close", @request.env["HTTP_CONNECTION"]
|
24
|
+
assert ! @request.env.key?("HOST")
|
25
|
+
assert ! @request.env.key?("CONNECTION")
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_do_not_prefix_content_type_and_length
|
29
|
+
@request.headers = {
|
30
|
+
"Content-Type" => "text/html",
|
31
|
+
"Content-Length" => "1"
|
32
|
+
}
|
33
|
+
|
34
|
+
assert ! @request.env.key?("HTTP_CONTENT_TYPE")
|
35
|
+
assert ! @request.env.key?("HTTP_CONTENT_LENGTH")
|
36
|
+
assert_equal "text/html", @request.env["CONTENT_TYPE"]
|
37
|
+
assert_equal "1", @request.env["CONTENT_LENGTH"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_extracts_server_name_and_port_from_host
|
41
|
+
@request.headers = {
|
42
|
+
"Host" => "localhost:3000"
|
43
|
+
}
|
44
|
+
assert_equal "localhost", @request.env["SERVER_NAME"]
|
45
|
+
assert_equal "3000", @request.env["SERVER_PORT"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_defaults_server_name_and_port
|
49
|
+
@request.headers = {}
|
50
|
+
assert_equal "localhost", @request.env["SERVER_NAME"]
|
51
|
+
assert_equal "80", @request.env["SERVER_PORT"]
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_validate_through_rack_lint
|
55
|
+
@request.multithread = false
|
56
|
+
@request.multiprocess = false
|
57
|
+
@request.method = "GET"
|
58
|
+
@request.path = "/info"
|
59
|
+
@request.fragment = "hello"
|
60
|
+
@request.query_string = "hey=there&yo=dude"
|
61
|
+
@request.headers = {
|
62
|
+
"Host" => "localhost:9292",
|
63
|
+
"Connection" => "close",
|
64
|
+
"Content-Type" => "text/plain"
|
65
|
+
}
|
66
|
+
@request.body << "ok"
|
67
|
+
|
68
|
+
app = proc do |env|
|
69
|
+
[200, {"Content-Type" => "text/plain"}, ["ok"]]
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_nothing_raised { Rack::Lint.new(app).call(@request.env) }
|
73
|
+
end
|
74
|
+
end
|