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'
18 while env['rack.input'].read(4096)
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")
27 class TestRackAfterReply
33 while env['rack.input'].read(4096)
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")
45 class WebServerTest < Test::Unit::TestCase
48 @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
50 @tester = TestHandler.new
52 @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
59 wait_workers_ready("test_stderr.#$$.log", 1)
60 File.truncate("test_stderr.#$$.log", 0)
66 def test_preload_app_config
68 tmp = Tempfile.new('test_preload_app_config')
69 ObjectSpace.undefine_finalizer(tmp)
74 lambda { |env| [ 200, { 'content-type' => 'text/plain' }, [ "#$$\n" ] ] }
77 @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
80 results = hit(["http://localhost:#@port/"])
81 worker_pid = results[0].to_i
82 assert worker_pid != 0
84 loader_pid = tmp.sysread(4096).to_i
85 assert loader_pid != 0
86 assert_equal worker_pid, loader_pid
90 @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
94 results = hit(["http://localhost:#@port/"])
95 worker_pid = results[0].to_i
96 assert worker_pid != 0
98 loader_pid = tmp.sysread(4096).to_i
99 assert_equal $$, loader_pid
100 assert worker_pid != loader_pid
109 @server = HttpServer.new(TestRackAfterReply.new,
110 :listeners => [ "127.0.0.1:#@port"])
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
131 def test_simple_server
132 results = hit(["http://localhost:#{@port}/test"])
133 assert_equal 'hello!\n', results[0], "Handler didn't really run"
136 def test_client_malformed_body
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)
147 File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
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
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)
164 while data = request.read(chunk)
165 chunks_out += socket.write(data)
168 if close_after and chunks_out > close_after
173 sleep(shutdown_delay)
174 socket.write(" ") # Some platforms only raise the exception on attempted write
178 def test_trickle_attack
179 do_test(@valid_request, 3)
182 def test_close_client
183 assert_raises IOError do
184 do_test(@valid_request, 10, 20)
190 do_test("GET /test HTTP/BAD", 3)
195 assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
198 def test_logger_changed
199 tmp = Logger.new($stdout)
201 assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
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
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
218 def test_header_is_too_long
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)
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)
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,
238 do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
242 def test_listener_names
243 assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names