thin 1.8.0 → 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 -107
  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.rb +2 -44
  10. data/lib/thin/async.rb +108 -0
  11. data/lib/thin/backends/prefork.rb +44 -0
  12. data/lib/thin/backends/single_process.rb +28 -0
  13. data/lib/thin/chunked_body.rb +28 -0
  14. data/lib/thin/configurator.rb +118 -0
  15. data/lib/thin/connection.rb +246 -172
  16. data/lib/thin/listener.rb +114 -0
  17. data/lib/thin/request.rb +94 -76
  18. data/lib/thin/response.rb +112 -45
  19. data/lib/thin/runner.rb +134 -197
  20. data/lib/thin/server.rb +203 -252
  21. data/lib/thin/system.rb +49 -0
  22. data/lib/thin/version.rb +11 -26
  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 -56
  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 -40
  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,57 @@
1
+ require "thin/async"
2
+
3
+ class App
4
+ def call(env)
5
+ request = Rack::Request.new(env)
6
+
7
+ case request.path_info
8
+ when "/"
9
+ Rack::Response.new do |response|
10
+ response.write "ok"
11
+ end.finish
12
+
13
+ when "/env"
14
+ Rack::Response.new do |response|
15
+ env.each_pair do |key, value|
16
+ response.write "#{key}: #{value}\n"
17
+ end
18
+ response.write "\n" + request.body.read
19
+ end.finish
20
+
21
+ when "/eval"
22
+ Rack::Response.new do |response|
23
+ response.write eval(request["code"]).to_s
24
+ end.finish
25
+
26
+ when "/raise"
27
+ raise "ouch"
28
+
29
+ when "/exit"
30
+ exit!
31
+
32
+ when "/sleep"
33
+ sleep request["sec"].to_f
34
+
35
+ when "/async"
36
+ Thin::AsyncResponse.new(env) do |response|
37
+ response << "one\n"
38
+ EM.next_tick do
39
+ response << "two\n"
40
+ response.done # close the connection
41
+ end
42
+ end.finish
43
+
44
+ else
45
+ Rack::Response.new do |response|
46
+ response.status = 404
47
+ response.write "not found"
48
+ end.finish
49
+
50
+ end
51
+ end
52
+ end
53
+
54
+ use Rack::Static, :urls => ["/small.txt", "/big.txt"],
55
+ :root => File.expand_path("../../fixtures", __FILE__)
56
+
57
+ run App.new
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ class DaemonizeTest < IntegrationTestCase
4
+ def test_do_not_daemonize
5
+ pid = thin
6
+
7
+ assert_equal @pid, pid, "Launcher PID should be the same as master PID"
8
+ Process.kill 0, @pid
9
+
10
+ get "/"
11
+
12
+ assert_status 200
13
+ end
14
+
15
+ def test_daemonize
16
+ pid = thin :daemonize => true
17
+
18
+ assert_not_equal @pid, pid, "Launcher PID should not be the same as master PID"
19
+ Process.kill 0, pid
20
+ Process.kill 0, @pid
21
+
22
+ get "/"
23
+
24
+ assert_status 200
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+
3
+ class EnvTest < IntegrationTestCase
4
+ def test_get
5
+ thin
6
+
7
+ get '/env?hi=there'
8
+
9
+ assert_status 200
10
+ assert_response_includes "REMOTE_ADDR: 127.0.0.1"
11
+ assert_response_includes "SERVER_SOFTWARE: thin"
12
+ assert_response_includes "HTTP_HOST: localhost:8181"
13
+ assert_response_includes "SERVER_PORT: 8181"
14
+ assert_response_includes "SERVER_NAME: localhost"
15
+ assert_response_includes "REQUEST_METHOD: GET"
16
+ assert_response_includes "PATH_INFO: /env"
17
+ assert_response_includes "QUERY_STRING: hi=there"
18
+ assert_response_includes "rack.url_scheme: http"
19
+ assert_response_includes "rack.multithread: false"
20
+ assert_response_includes "rack.multiprocess: true"
21
+ assert_response_includes "rack.run_once: false"
22
+ end
23
+
24
+ def test_post
25
+ thin
26
+
27
+ post '/env', :hi => "there"
28
+
29
+ assert_status 200
30
+ assert_response_includes "REMOTE_ADDR: 127.0.0.1"
31
+ assert_response_includes "SERVER_SOFTWARE: thin"
32
+ assert_response_includes "HTTP_HOST: localhost:8181"
33
+ assert_response_includes "SERVER_PORT: 8181"
34
+ assert_response_includes "SERVER_NAME: localhost"
35
+ assert_response_includes "REQUEST_METHOD: POST"
36
+ assert_response_includes "PATH_INFO: /env"
37
+ assert_response_includes "QUERY_STRING: \n"
38
+ assert_response_includes "rack.url_scheme: http"
39
+ assert_response_includes "rack.multithread: false"
40
+ assert_response_includes "rack.multiprocess: true"
41
+ assert_response_includes "rack.run_once: false"
42
+ assert_response_includes "\n\nhi=there"
43
+ end
44
+ end
@@ -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