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.

Files changed (108) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +29 -116
  3. data/Gemfile +8 -0
  4. data/README.md +44 -78
  5. data/Rakefile +28 -18
  6. data/bin/thin +4 -4
  7. data/examples/async.ru +21 -0
  8. data/examples/thin.conf.rb +39 -0
  9. data/lib/thin/async.rb +108 -0
  10. data/lib/thin/backends/prefork.rb +44 -0
  11. data/lib/thin/backends/single_process.rb +28 -0
  12. data/lib/thin/chunked_body.rb +28 -0
  13. data/lib/thin/configurator.rb +118 -0
  14. data/lib/thin/connection.rb +246 -172
  15. data/lib/thin/listener.rb +114 -0
  16. data/lib/thin/request.rb +94 -76
  17. data/lib/thin/response.rb +112 -45
  18. data/lib/thin/runner.rb +134 -197
  19. data/lib/thin/server.rb +203 -252
  20. data/lib/thin/system.rb +49 -0
  21. data/lib/thin/version.rb +12 -27
  22. data/lib/thin.rb +2 -44
  23. data/man/index.txt +3 -0
  24. data/man/thin-conf.5.ronn +121 -0
  25. data/man/thin.1.ronn +105 -0
  26. data/site/.gitignore +2 -0
  27. data/site/README.md +21 -0
  28. data/site/Rakefile +20 -0
  29. data/site/config.ru +4 -0
  30. data/site/public/images/grid.png +0 -0
  31. data/site/public/javascripts/dd_belatedpng.js +13 -0
  32. data/site/public/javascripts/modernizr-1.6.min.js +30 -0
  33. data/site/public/man/thin-conf.5.html +220 -0
  34. data/site/public/man/thin.1.html +177 -0
  35. data/site/site/assets/javascripts/main.coffee +2 -0
  36. data/site/site/assets/stylesheets/_config.scss +55 -0
  37. data/site/site/assets/stylesheets/main.scss +24 -0
  38. data/site/site/helpers.rb +17 -0
  39. data/site/site/layouts/base.erb +55 -0
  40. data/site/site/layouts/default.erb +17 -0
  41. data/site/site/pages/about.md +5 -0
  42. data/site/site/pages/index.erb +10 -0
  43. data/site/site/partials/.gitkeep +0 -0
  44. data/test/fixtures/big.txt +1 -0
  45. data/test/fixtures/small.txt +1 -0
  46. data/test/fixtures/thin.conf.rb +15 -0
  47. data/test/integration/async_test.rb +35 -0
  48. data/test/integration/big_request_test.rb +30 -0
  49. data/test/integration/config.ru +57 -0
  50. data/test/integration/daemonize_test.rb +26 -0
  51. data/test/integration/env_test.rb +44 -0
  52. data/test/integration/error_test.rb +37 -0
  53. data/test/integration/file_sending_test.rb +24 -0
  54. data/test/integration/keep_alive_test.rb +35 -0
  55. data/test/integration/robustness_test.rb +37 -0
  56. data/test/integration/single_process_test.rb +15 -0
  57. data/test/integration/socket_family_test.rb +38 -0
  58. data/test/integration/worker_test.rb +22 -0
  59. data/test/test_helper.rb +195 -0
  60. data/test/unit/configurator_test.rb +43 -0
  61. data/test/unit/connection_test.rb +94 -0
  62. data/test/unit/listener_test.rb +74 -0
  63. data/test/unit/request_test.rb +74 -0
  64. data/test/unit/response_test.rb +90 -0
  65. data/test/unit/server_test.rb +29 -0
  66. data/test/unit/system_test.rb +17 -0
  67. data/thin.gemspec +26 -0
  68. data/v2.todo +21 -0
  69. metadata +138 -93
  70. checksums.yaml +0 -7
  71. data/example/adapter.rb +0 -32
  72. data/example/async_app.ru +0 -126
  73. data/example/async_chat.ru +0 -247
  74. data/example/async_tailer.ru +0 -100
  75. data/example/config.ru +0 -22
  76. data/example/monit_sockets +0 -20
  77. data/example/monit_unixsock +0 -20
  78. data/example/myapp.rb +0 -1
  79. data/example/ramaze.ru +0 -12
  80. data/example/thin.god +0 -80
  81. data/example/thin_solaris_smf.erb +0 -36
  82. data/example/thin_solaris_smf.readme.txt +0 -150
  83. data/example/vlad.rake +0 -72
  84. data/ext/thin_parser/common.rl +0 -59
  85. data/ext/thin_parser/ext_help.h +0 -14
  86. data/ext/thin_parser/extconf.rb +0 -6
  87. data/ext/thin_parser/parser.c +0 -1447
  88. data/ext/thin_parser/parser.h +0 -49
  89. data/ext/thin_parser/parser.rl +0 -152
  90. data/ext/thin_parser/thin.c +0 -435
  91. data/lib/rack/adapter/loader.rb +0 -75
  92. data/lib/rack/adapter/rails.rb +0 -178
  93. data/lib/rack/handler/thin.rb +0 -38
  94. data/lib/thin/backends/base.rb +0 -169
  95. data/lib/thin/backends/swiftiply_client.rb +0 -66
  96. data/lib/thin/backends/tcp_server.rb +0 -34
  97. data/lib/thin/backends/unix_server.rb +0 -56
  98. data/lib/thin/command.rb +0 -53
  99. data/lib/thin/controllers/cluster.rb +0 -178
  100. data/lib/thin/controllers/controller.rb +0 -189
  101. data/lib/thin/controllers/service.rb +0 -76
  102. data/lib/thin/controllers/service.sh.erb +0 -39
  103. data/lib/thin/daemonizing.rb +0 -199
  104. data/lib/thin/headers.rb +0 -47
  105. data/lib/thin/logging.rb +0 -174
  106. data/lib/thin/stats.html.erb +0 -216
  107. data/lib/thin/stats.rb +0 -52
  108. 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
@@ -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