unicorn 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +1 -0
- data/GNUmakefile +26 -27
- data/Manifest +1 -2
- data/SIGNALS +4 -0
- data/TODO +0 -6
- data/bin/unicorn_rails +5 -11
- data/ext/unicorn/http11/ext_help.h +0 -3
- data/ext/unicorn/http11/http11.c +76 -41
- data/ext/unicorn/http11/http11_parser.h +1267 -21
- data/ext/unicorn/http11/http11_parser.rl +69 -65
- data/ext/unicorn/http11/http11_parser_common.rl +6 -3
- data/lib/unicorn.rb +112 -87
- data/lib/unicorn/configurator.rb +1 -2
- data/lib/unicorn/const.rb +4 -4
- data/lib/unicorn/http_request.rb +64 -72
- data/lib/unicorn/http_response.rb +14 -15
- data/lib/unicorn/{socket.rb → socket_helper.rb} +0 -13
- data/test/exec/test_exec.rb +23 -0
- data/test/unit/test_http_parser.rb +117 -3
- data/test/unit/test_request.rb +40 -14
- data/test/unit/test_response.rb +1 -1
- data/test/unit/test_socket_helper.rb +5 -45
- data/test/unit/test_upload.rb +18 -1
- data/unicorn.gemspec +4 -4
- metadata +4 -6
- data/ext/unicorn/http11/http11_parser.c +0 -1221
data/lib/unicorn/configurator.rb
CHANGED
data/lib/unicorn/const.rb
CHANGED
@@ -42,6 +42,9 @@ module Unicorn
|
|
42
42
|
503 => 'Service Unavailable',
|
43
43
|
504 => 'Gateway Time-out',
|
44
44
|
505 => 'HTTP Version not supported'
|
45
|
+
}.inject({}) { |hash,(code,msg)|
|
46
|
+
hash[code] = "#{code} #{msg}"
|
47
|
+
hash
|
45
48
|
}
|
46
49
|
|
47
50
|
# Frequently used constants when constructing requests or responses. Many times
|
@@ -56,9 +59,7 @@ module Unicorn
|
|
56
59
|
REQUEST_URI='REQUEST_URI'.freeze
|
57
60
|
REQUEST_PATH='REQUEST_PATH'.freeze
|
58
61
|
|
59
|
-
UNICORN_VERSION="0.
|
60
|
-
|
61
|
-
UNICORN_TMP_BASE="unicorn".freeze
|
62
|
+
UNICORN_VERSION="0.6.0".freeze
|
62
63
|
|
63
64
|
DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
|
64
65
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
@@ -82,7 +83,6 @@ module Unicorn
|
|
82
83
|
CONTENT_LENGTH="CONTENT_LENGTH".freeze
|
83
84
|
REMOTE_ADDR="REMOTE_ADDR".freeze
|
84
85
|
HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
|
85
|
-
QUERY_STRING="QUERY_STRING".freeze
|
86
86
|
RACK_INPUT="rack.input".freeze
|
87
87
|
end
|
88
88
|
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'tempfile'
|
2
|
-
require 'uri'
|
3
2
|
require 'stringio'
|
4
3
|
|
5
4
|
# compiled extension
|
@@ -19,27 +18,31 @@ module Unicorn
|
|
19
18
|
"rack.multiprocess" => true,
|
20
19
|
"rack.multithread" => false,
|
21
20
|
"rack.run_once" => false,
|
22
|
-
"rack.version" => [0, 1],
|
23
|
-
"SCRIPT_NAME" => "",
|
21
|
+
"rack.version" => [0, 1].freeze,
|
22
|
+
"SCRIPT_NAME" => "".freeze,
|
24
23
|
|
25
24
|
# this is not in the Rack spec, but some apps may rely on it
|
26
|
-
"SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
|
25
|
+
"SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze
|
27
26
|
}.freeze
|
28
27
|
|
28
|
+
LOCALHOST = '127.0.0.1'.freeze
|
29
|
+
|
30
|
+
# Being explicitly single-threaded, we have certain advantages in
|
31
|
+
# not having to worry about variables being clobbered :)
|
32
|
+
BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
|
33
|
+
PARSER = HttpParser.new
|
34
|
+
PARAMS = Hash.new
|
35
|
+
|
29
36
|
def initialize(logger)
|
30
37
|
@logger = logger
|
31
|
-
|
32
|
-
@buffer = ' ' * Const::CHUNK_SIZE # initial size, may grow
|
33
|
-
@parser = HttpParser.new
|
34
|
-
@params = Hash.new
|
38
|
+
reset
|
35
39
|
end
|
36
40
|
|
37
41
|
def reset
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@body = nil
|
42
|
+
PARAMS[Const::RACK_INPUT].close rescue nil
|
43
|
+
PARAMS[Const::RACK_INPUT].close! rescue nil
|
44
|
+
PARSER.reset
|
45
|
+
PARAMS.clear
|
43
46
|
end
|
44
47
|
|
45
48
|
# Does the majority of the IO processing. It has been written in
|
@@ -56,24 +59,34 @@ module Unicorn
|
|
56
59
|
# This does minimal exception trapping and it is up to the caller
|
57
60
|
# to handle any socket errors (e.g. user aborted upload).
|
58
61
|
def read(socket)
|
62
|
+
# From http://www.ietf.org/rfc/rfc3875:
|
63
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
64
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
65
|
+
# may not identify the ultimate source of the request. They
|
66
|
+
# identify the client for the immediate request to the server;
|
67
|
+
# that client may be a proxy, gateway, or other intermediary
|
68
|
+
# acting on behalf of the actual source client."
|
69
|
+
PARAMS[Const::REMOTE_ADDR] =
|
70
|
+
TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
|
71
|
+
|
59
72
|
# short circuit the common case with small GET requests first
|
60
|
-
|
73
|
+
PARSER.execute(PARAMS, read_socket(socket)) and
|
61
74
|
return handle_body(socket)
|
62
75
|
|
63
|
-
data =
|
76
|
+
data = BUFFER.dup # read_socket will clobber BUFFER
|
64
77
|
|
65
78
|
# Parser is not done, queue up more data to read and continue parsing
|
66
|
-
# an Exception thrown from the
|
67
|
-
|
79
|
+
# an Exception thrown from the PARSER will throw us out of the loop
|
80
|
+
begin
|
68
81
|
data << read_socket(socket)
|
69
|
-
|
70
|
-
end
|
82
|
+
PARSER.execute(PARAMS, data) and return handle_body(socket)
|
83
|
+
end while true
|
71
84
|
rescue HttpParserError => e
|
72
85
|
@logger.error "HTTP parse error, malformed request " \
|
73
|
-
"(#{
|
74
|
-
|
86
|
+
"(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] ||
|
87
|
+
PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}"
|
75
88
|
@logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
|
76
|
-
"PARAMS: #{
|
89
|
+
"PARAMS: #{PARAMS.inspect}\n---\n"
|
77
90
|
raise e
|
78
91
|
end
|
79
92
|
|
@@ -82,75 +95,54 @@ module Unicorn
|
|
82
95
|
# Handles dealing with the rest of the request
|
83
96
|
# returns a Rack environment if successful, raises an exception if not
|
84
97
|
def handle_body(socket)
|
85
|
-
http_body =
|
86
|
-
content_length =
|
87
|
-
|
98
|
+
http_body = PARAMS.delete(:http_body)
|
99
|
+
content_length = PARAMS[Const::CONTENT_LENGTH].to_i
|
100
|
+
|
101
|
+
if content_length == 0 # short circuit the common case
|
102
|
+
PARAMS[Const::RACK_INPUT] = StringIO.new
|
103
|
+
return PARAMS.update(DEF_PARAMS)
|
104
|
+
end
|
88
105
|
|
89
106
|
# must read more data to complete body
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
107
|
+
remain = content_length - http_body.length
|
108
|
+
|
109
|
+
body = PARAMS[Const::RACK_INPUT] = (remain < Const::MAX_BODY) ?
|
110
|
+
StringIO.new : Tempfile.new('unicorn')
|
111
|
+
|
112
|
+
body.binmode
|
113
|
+
body.sync = true
|
114
|
+
body.syswrite(http_body)
|
94
115
|
|
95
116
|
# Some clients (like FF1.0) report 0 for body and then send a body.
|
96
117
|
# This will probably truncate them but at least the request goes through
|
97
118
|
# usually.
|
98
|
-
read_body(socket, remain) if remain > 0
|
99
|
-
|
100
|
-
|
119
|
+
read_body(socket, remain, body) if remain > 0
|
120
|
+
body.rewind
|
121
|
+
body.sysseek(0) if body.respond_to?(:sysseek)
|
101
122
|
|
102
123
|
# in case read_body overread because the client tried to pipeline
|
103
124
|
# another request, we'll truncate it. Again, we don't do pipelining
|
104
125
|
# or keepalive
|
105
|
-
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
# Returns an environment which is rackable:
|
110
|
-
# http://rack.rubyforge.org/doc/files/SPEC.html
|
111
|
-
# Based on Rack's old Mongrel handler.
|
112
|
-
def rack_env(socket)
|
113
|
-
# I'm considering enabling "unicorn.client". It gives
|
114
|
-
# applications some rope to do some "interesting" things like
|
115
|
-
# replacing a worker with another process that has full control
|
116
|
-
# over the HTTP response.
|
117
|
-
# @params["unicorn.client"] = socket
|
118
|
-
|
119
|
-
# From http://www.ietf.org/rfc/rfc3875:
|
120
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
121
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
122
|
-
# may not identify the ultimate source of the request. They
|
123
|
-
# identify the client for the immediate request to the server;
|
124
|
-
# that client may be a proxy, gateway, or other intermediary
|
125
|
-
# acting on behalf of the actual source client."
|
126
|
-
@params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
|
127
|
-
|
128
|
-
# It might be a dumbass full host request header
|
129
|
-
@params[Const::PATH_INFO] = (
|
130
|
-
@params[Const::REQUEST_PATH] ||=
|
131
|
-
URI.parse(@params[Const::REQUEST_URI]).path) or
|
132
|
-
raise "No REQUEST_PATH"
|
133
|
-
|
134
|
-
@params[Const::QUERY_STRING] ||= ''
|
135
|
-
@params[Const::RACK_INPUT] = @body
|
136
|
-
@params.update(DEF_PARAMS)
|
126
|
+
body.truncate(content_length)
|
127
|
+
PARAMS.update(DEF_PARAMS)
|
137
128
|
end
|
138
129
|
|
139
|
-
# Does the heavy lifting of properly reading the larger body
|
140
|
-
# small chunks. It expects
|
141
|
-
#
|
142
|
-
# the
|
143
|
-
|
130
|
+
# Does the heavy lifting of properly reading the larger body
|
131
|
+
# requests in small chunks. It expects PARAMS['rack.input'] to be
|
132
|
+
# an IO object, socket to be valid, It also expects any initial part
|
133
|
+
# of the body that has been read to be in the PARAMS['rack.input']
|
134
|
+
# already. It will return true if successful and false if not.
|
135
|
+
def read_body(socket, remain, body)
|
144
136
|
while remain > 0
|
145
137
|
# writes always write the requested amount on a POSIX filesystem
|
146
|
-
remain -=
|
138
|
+
remain -= body.syswrite(read_socket(socket))
|
147
139
|
end
|
148
140
|
rescue Object => e
|
149
141
|
@logger.error "Error reading HTTP body: #{e.inspect}"
|
150
142
|
|
151
143
|
# Any errors means we should delete the file, including if the file
|
152
144
|
# is dumped. Truncate it ASAP to help avoid page flushes to disk.
|
153
|
-
|
145
|
+
body.truncate(0) rescue nil
|
154
146
|
reset
|
155
147
|
raise e
|
156
148
|
end
|
@@ -158,7 +150,7 @@ module Unicorn
|
|
158
150
|
# read(2) on "slow" devices like sockets can be interrupted by signals
|
159
151
|
def read_socket(socket)
|
160
152
|
begin
|
161
|
-
socket.sysread(Const::CHUNK_SIZE,
|
153
|
+
socket.sysread(Const::CHUNK_SIZE, BUFFER)
|
162
154
|
rescue Errno::EINTR
|
163
155
|
retry
|
164
156
|
end
|
@@ -25,22 +25,23 @@ module Unicorn
|
|
25
25
|
# Connection: and Date: headers no matter what (if anything) our
|
26
26
|
# Rack application sent us.
|
27
27
|
SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
|
28
|
-
|
28
|
+
EMPTY = ''.freeze # :nodoc
|
29
|
+
OUT = [] # :nodoc
|
29
30
|
|
30
31
|
# writes the rack_response to socket as an HTTP response
|
31
32
|
def self.write(socket, rack_response)
|
32
33
|
status, headers, body = rack_response
|
33
|
-
status =
|
34
|
-
|
34
|
+
status = HTTP_STATUS_CODES[status]
|
35
|
+
OUT.clear
|
35
36
|
|
36
37
|
# Don't bother enforcing duplicate supression, it's a Hash most of
|
37
38
|
# the time anyways so just hope our app knows what it's doing
|
38
39
|
headers.each do |key, value|
|
39
40
|
next if SKIP.include?(key.downcase)
|
40
41
|
if value =~ /\n/
|
41
|
-
value.split(/\n/).each { |v|
|
42
|
+
value.split(/\n/).each { |v| OUT << "#{key}: #{v}\r\n" }
|
42
43
|
else
|
43
|
-
|
44
|
+
OUT << "#{key}: #{value}\r\n"
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
@@ -52,7 +53,8 @@ module Unicorn
|
|
52
53
|
"HTTP/1.1 #{status}\r\n" \
|
53
54
|
"Date: #{Time.now.httpdate}\r\n" \
|
54
55
|
"Status: #{status}\r\n" \
|
55
|
-
"
|
56
|
+
"Connection: close\r\n" \
|
57
|
+
"#{OUT.join(EMPTY)}\r\n")
|
56
58
|
body.each { |chunk| socket_write(socket, chunk) }
|
57
59
|
socket.close # uncorks the socket immediately
|
58
60
|
ensure
|
@@ -64,15 +66,12 @@ module Unicorn
|
|
64
66
|
# write(2) can return short on slow devices like sockets as well
|
65
67
|
# as fail with EINTR if a signal was caught.
|
66
68
|
def self.socket_write(socket, buffer)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
retry
|
74
|
-
end
|
75
|
-
end
|
69
|
+
begin
|
70
|
+
written = socket.syswrite(buffer)
|
71
|
+
return written if written == buffer.length
|
72
|
+
buffer = buffer[written..-1]
|
73
|
+
rescue Errno::EINTR
|
74
|
+
end while true
|
76
75
|
end
|
77
76
|
|
78
77
|
end
|
@@ -1,18 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
|
-
class UNIXSocket
|
4
|
-
UNICORN_PEERADDR = '127.0.0.1'.freeze
|
5
|
-
def unicorn_peeraddr
|
6
|
-
UNICORN_PEERADDR
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class TCPSocket
|
11
|
-
def unicorn_peeraddr
|
12
|
-
peeraddr.last
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
3
|
module Unicorn
|
17
4
|
module SocketHelper
|
18
5
|
include Socket::Constants
|
data/test/exec/test_exec.rb
CHANGED
@@ -98,6 +98,28 @@ end
|
|
98
98
|
assert_shutdown(pid)
|
99
99
|
end
|
100
100
|
|
101
|
+
def test_ttin_ttou
|
102
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
103
|
+
pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
|
104
|
+
log = "test_stderr.#{pid}.log"
|
105
|
+
wait_master_ready(log)
|
106
|
+
[ 2, 3].each { |i|
|
107
|
+
assert_nothing_raised { Process.kill(:TTIN, pid) }
|
108
|
+
wait_workers_ready(log, i)
|
109
|
+
}
|
110
|
+
File.truncate(log, 0)
|
111
|
+
reaped = nil
|
112
|
+
[ 2, 1, 0].each { |i|
|
113
|
+
assert_nothing_raised { Process.kill(:TTOU, pid) }
|
114
|
+
DEFAULT_TRIES.times {
|
115
|
+
sleep DEFAULT_RES
|
116
|
+
reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
|
117
|
+
break if reaped.size == 1
|
118
|
+
}
|
119
|
+
assert_equal 1, reaped.size
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
101
123
|
def test_help
|
102
124
|
redirect_test_io do
|
103
125
|
assert(system($unicorn_bin, "-h"), "help text returns true")
|
@@ -459,6 +481,7 @@ end
|
|
459
481
|
results = nil
|
460
482
|
sock = Tempfile.new('unicorn_test_sock')
|
461
483
|
sock_path = sock.path
|
484
|
+
@sockets << sock_path
|
462
485
|
sock.close!
|
463
486
|
ucfg = Tempfile.new('unicorn_test_config')
|
464
487
|
ucfg.syswrite("listen \"#{sock_path}\"\n")
|
@@ -22,7 +22,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
22
22
|
assert_equal '/', req['REQUEST_URI']
|
23
23
|
assert_equal 'GET', req['REQUEST_METHOD']
|
24
24
|
assert_nil req['FRAGMENT']
|
25
|
-
|
25
|
+
assert_equal '', req['QUERY_STRING']
|
26
26
|
|
27
27
|
parser.reset
|
28
28
|
req.clear
|
@@ -40,7 +40,40 @@ class HttpParserTest < Test::Unit::TestCase
|
|
40
40
|
assert_equal '/hello-world', req['REQUEST_URI']
|
41
41
|
assert_equal 'GET', req['REQUEST_METHOD']
|
42
42
|
assert_nil req['FRAGMENT']
|
43
|
-
|
43
|
+
assert_equal '', req['QUERY_STRING']
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_parse_server_host_default_port
|
47
|
+
parser = HttpParser.new
|
48
|
+
req = {}
|
49
|
+
assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
50
|
+
assert_equal 'foo', req['SERVER_NAME']
|
51
|
+
assert_equal '80', req['SERVER_PORT']
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_parse_server_host_alt_port
|
55
|
+
parser = HttpParser.new
|
56
|
+
req = {}
|
57
|
+
assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n")
|
58
|
+
assert_equal 'foo', req['SERVER_NAME']
|
59
|
+
assert_equal '999', req['SERVER_PORT']
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_parse_server_host_empty_port
|
63
|
+
parser = HttpParser.new
|
64
|
+
req = {}
|
65
|
+
assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n")
|
66
|
+
assert_equal 'foo', req['SERVER_NAME']
|
67
|
+
assert_equal '80', req['SERVER_PORT']
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_parse_server_host_xfp_https
|
71
|
+
parser = HttpParser.new
|
72
|
+
req = {}
|
73
|
+
assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:\r\n" \
|
74
|
+
"X-Forwarded-Proto: https\r\n\r\n")
|
75
|
+
assert_equal 'foo', req['SERVER_NAME']
|
76
|
+
assert_equal '443', req['SERVER_PORT']
|
44
77
|
end
|
45
78
|
|
46
79
|
def test_parse_strange_headers
|
@@ -100,7 +133,88 @@ class HttpParserTest < Test::Unit::TestCase
|
|
100
133
|
assert parser.execute(req, http << "\n")
|
101
134
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
102
135
|
assert_nil req['FRAGMENT']
|
103
|
-
|
136
|
+
assert_equal '', req['QUERY_STRING']
|
137
|
+
end
|
138
|
+
|
139
|
+
# not common, but underscores do appear in practice
|
140
|
+
def test_absolute_uri_underscores
|
141
|
+
parser = HttpParser.new
|
142
|
+
req = {}
|
143
|
+
http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
|
144
|
+
assert parser.execute(req, http)
|
145
|
+
assert_equal 'http', req['rack.url_scheme']
|
146
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
147
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
148
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
149
|
+
|
150
|
+
assert_equal 'under_score.example.com', req['HTTP_HOST']
|
151
|
+
assert_equal 'under_score.example.com', req['SERVER_NAME']
|
152
|
+
assert_equal '80', req['SERVER_PORT']
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_absolute_uri
|
156
|
+
parser = HttpParser.new
|
157
|
+
req = {}
|
158
|
+
http = "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
|
159
|
+
assert parser.execute(req, http)
|
160
|
+
assert_equal 'http', req['rack.url_scheme']
|
161
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
162
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
163
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
164
|
+
|
165
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
166
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
167
|
+
assert_equal '80', req['SERVER_PORT']
|
168
|
+
end
|
169
|
+
|
170
|
+
# X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
|
171
|
+
def test_absolute_uri_https
|
172
|
+
parser = HttpParser.new
|
173
|
+
req = {}
|
174
|
+
http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
|
175
|
+
"X-Forwarded-Proto: http\r\n\r\n"
|
176
|
+
assert parser.execute(req, http)
|
177
|
+
assert_equal 'https', req['rack.url_scheme']
|
178
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
179
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
180
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
181
|
+
|
182
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
183
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
184
|
+
assert_equal '443', req['SERVER_PORT']
|
185
|
+
end
|
186
|
+
|
187
|
+
# Host: header should be ignored for absolute URIs
|
188
|
+
def test_absolute_uri_with_port
|
189
|
+
parser = HttpParser.new
|
190
|
+
req = {}
|
191
|
+
http = "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
|
192
|
+
"Host: bad.example.com\r\n\r\n"
|
193
|
+
assert parser.execute(req, http)
|
194
|
+
assert_equal 'http', req['rack.url_scheme']
|
195
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
196
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
197
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
198
|
+
|
199
|
+
assert_equal 'example.com:8080', req['HTTP_HOST']
|
200
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
201
|
+
assert_equal '8080', req['SERVER_PORT']
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_absolute_uri_with_empty_port
|
205
|
+
parser = HttpParser.new
|
206
|
+
req = {}
|
207
|
+
http = "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
|
208
|
+
"Host: bad.example.com\r\n\r\n"
|
209
|
+
assert parser.execute(req, http)
|
210
|
+
assert_equal 'https', req['rack.url_scheme']
|
211
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
212
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
213
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
214
|
+
|
215
|
+
assert_equal 'example.com:', req['HTTP_HOST']
|
216
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
217
|
+
assert_equal '443', req['SERVER_PORT']
|
104
218
|
end
|
105
219
|
|
106
220
|
def test_put_body_oneshot
|