treewide: future-proof frozen_string_literal changes
[unicorn.git] / test / unit / test_server.rb
blob5a2252f20bb39c12f6fef732bdad09fe9be125c0
1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
4 # Copyright (c) 2005 Zed A. Shaw
5 # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
6 # the GPLv2+ (GPLv3+ preferred)
8 # Additional work donated by contributors.  See git history
9 # for more information.
11 require './test/test_helper'
13 include Unicorn
15 class TestHandler
17   def call(env)
18     while env['rack.input'].read(4096)
19     end
20     [200, { 'content-type' => 'text/plain' }, ['hello!\n']]
21   rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
22     $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
23     raise e
24   end
25 end
27 class TestRackAfterReply
28   def initialize
29     @called = false
30   end
32   def call(env)
33     while env['rack.input'].read(4096)
34     end
36     env["rack.after_reply"] << -> { @called = true }
38     [200, { 'content-type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
39   rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
40     $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
41     raise e
42   end
43 end
45 class WebServerTest < Test::Unit::TestCase
47   def setup
48     @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
49     @port = unused_port
50     @tester = TestHandler.new
51     redirect_test_io do
52       @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
53       @server.start
54     end
55   end
57   def teardown
58     redirect_test_io do
59       wait_workers_ready("test_stderr.#$$.log", 1)
60       File.truncate("test_stderr.#$$.log", 0)
61       @server.stop(false)
62     end
63     reset_sig_handlers
64   end
66   def test_preload_app_config
67     teardown
68     tmp = Tempfile.new('test_preload_app_config')
69     ObjectSpace.undefine_finalizer(tmp)
70     app = lambda { ||
71       tmp.sysseek(0)
72       tmp.truncate(0)
73       tmp.syswrite($$)
74       lambda { |env| [ 200, { 'content-type' => 'text/plain' }, [ "#$$\n" ] ] }
75     }
76     redirect_test_io do
77       @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
78       @server.start
79     end
80     results = hit(["http://localhost:#@port/"])
81     worker_pid = results[0].to_i
82     assert worker_pid != 0
83     tmp.sysseek(0)
84     loader_pid = tmp.sysread(4096).to_i
85     assert loader_pid != 0
86     assert_equal worker_pid, loader_pid
87     teardown
89     redirect_test_io do
90       @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
91                                :preload_app => true)
92       @server.start
93     end
94     results = hit(["http://localhost:#@port/"])
95     worker_pid = results[0].to_i
96     assert worker_pid != 0
97     tmp.sysseek(0)
98     loader_pid = tmp.sysread(4096).to_i
99     assert_equal $$, loader_pid
100     assert worker_pid != loader_pid
101   ensure
102     tmp.close!
103   end
105   def test_after_reply
106     teardown
108     redirect_test_io do
109       @server = HttpServer.new(TestRackAfterReply.new,
110                                :listeners => [ "127.0.0.1:#@port"])
111       @server.start
112     end
114     sock = tcp_socket('127.0.0.1', @port)
115     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
117     responses = sock.read(4096)
118     assert_match %r{\AHTTP/1.[01] 200\b}, responses
119     assert_match %r{^after_reply_called: false}, responses
121     sock = tcp_socket('127.0.0.1', @port)
122     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
124     responses = sock.read(4096)
125     assert_match %r{\AHTTP/1.[01] 200\b}, responses
126     assert_match %r{^after_reply_called: true}, responses
128     sock.close
129   end
131   def test_simple_server
132     results = hit(["http://localhost:#{@port}/test"])
133     assert_equal 'hello!\n', results[0], "Handler didn't really run"
134   end
136   def test_client_malformed_body
137     bs = 15653984
138     sock = tcp_socket('127.0.0.1', @port)
139     sock.syswrite("PUT /hello HTTP/1.1\r\n")
140     sock.syswrite("Host: example.com\r\n")
141     sock.syswrite("Transfer-Encoding: chunked\r\n")
142     sock.syswrite("Trailer: X-Foo\r\n")
143     sock.syswrite("\r\n")
144     sock.syswrite("%x\r\n" % [ bs ])
145     sock.syswrite("F" * bs)
146     begin
147       File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
148     rescue
149     end
150     assert_nil sock.close
151     next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
152     assert_equal 'hello!\n', next_client
153     lines = File.readlines("test_stderr.#$$.log")
154     lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
155     assert_equal 1, lines.size
156   end
158   def do_test(string, chunk, close_after=nil, shutdown_delay=0)
159     # Do not use instance variables here, because it needs to be thread safe
160     socket = tcp_socket("127.0.0.1", @port);
161     request = StringIO.new(string)
162     chunks_out = 0
164     while data = request.read(chunk)
165       chunks_out += socket.write(data)
166       socket.flush
167       sleep 0.2
168       if close_after and chunks_out > close_after
169         socket.close
170         sleep 1
171       end
172     end
173     sleep(shutdown_delay)
174     socket.write(" ") # Some platforms only raise the exception on attempted write
175     socket.flush
176   end
178   def test_trickle_attack
179     do_test(@valid_request, 3)
180   end
182   def test_close_client
183     assert_raises IOError do
184       do_test(@valid_request, 10, 20)
185     end
186   end
188   def test_bad_client
189     redirect_test_io do
190       do_test("GET /test HTTP/BAD", 3)
191     end
192   end
194   def test_logger_set
195     assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
196   end
198   def test_logger_changed
199     tmp = Logger.new($stdout)
200     @server.logger = tmp
201     assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
202   end
204   def test_bad_client_400
205     sock = tcp_socket('127.0.0.1', @port)
206     sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
207     assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
208     assert_nil sock.close
209   end
211   def test_http_0_9
212     sock = tcp_socket('127.0.0.1', @port)
213     sock.syswrite("GET /hello\r\n")
214     assert_match 'hello!\n', sock.sysread(4096)
215     assert_nil sock.close
216   end
218   def test_header_is_too_long
219     redirect_test_io do
220       long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
221       assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
222         do_test(long, long.length/2, 10)
223       end
224     end
225   end
227   def test_file_streamed_request
228     body = "a" * (Unicorn::Const::MAX_BODY * 2)
229     long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
230     do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
231   end
233   def test_file_streamed_request_bad_body
234     body = "a" * (Unicorn::Const::MAX_BODY * 2)
235     long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
236     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
237                   Errno::EBADF) {
238       do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
239     }
240   end
242   def test_listener_names
243     assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
244   end