unicorn 0.9.2 → 0.90.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/.gitignore +1 -0
- data/CHANGELOG +3 -0
- data/GNUmakefile +7 -7
- data/Manifest +20 -23
- data/README +16 -13
- data/TODO +5 -3
- data/bin/unicorn +1 -1
- data/bin/unicorn_rails +1 -1
- data/ext/unicorn_http/c_util.h +107 -0
- data/ext/unicorn_http/common_field_optimization.h +110 -0
- data/ext/unicorn_http/ext_help.h +41 -5
- data/ext/unicorn_http/extconf.rb +3 -1
- data/ext/unicorn_http/global_variables.h +93 -0
- data/ext/unicorn_http/unicorn_http.c +2123 -326
- data/ext/unicorn_http/unicorn_http.rl +488 -87
- data/ext/unicorn_http/unicorn_http_common.rl +12 -1
- data/lib/unicorn.rb +0 -2
- data/lib/unicorn/app/exec_cgi.rb +0 -1
- data/lib/unicorn/app/inetd.rb +2 -0
- data/lib/unicorn/app/old_rails/static.rb +0 -2
- data/lib/unicorn/const.rb +1 -5
- data/lib/unicorn/http_request.rb +13 -29
- data/lib/unicorn/http_response.rb +1 -1
- data/lib/unicorn/tee_input.rb +48 -46
- data/lib/unicorn/util.rb +3 -3
- data/test/benchmark/README +0 -5
- data/test/exec/test_exec.rb +4 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/.gitignore +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/Rakefile +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/application_controller.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/foo_controller.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/helpers/application_helper.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/boot.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/database.yml +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environment.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/development.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/production.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/routes.rb +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/db/.gitignore +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/404.html +0 -0
- data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/500.html +0 -0
- data/test/unit/test_http_parser.rb +112 -47
- data/test/unit/test_http_parser_ng.rb +284 -0
- data/test/unit/test_request.rb +25 -7
- data/test/unit/test_response.rb +11 -0
- data/test/unit/test_server.rb +7 -2
- data/test/unit/test_signals.rb +2 -0
- data/test/unit/test_tee_input.rb +118 -2
- data/test/unit/test_upload.rb +1 -1
- data/test/unit/test_util.rb +5 -0
- data/unicorn.gemspec +6 -6
- metadata +33 -37
- data/ext/unicorn_http/unicorn_http.h +0 -1289
- data/lib/unicorn/chunked_reader.rb +0 -77
- data/lib/unicorn/trailer_parser.rb +0 -52
- data/test/benchmark/big_request.rb +0 -44
- data/test/benchmark/request.rb +0 -56
- data/test/benchmark/response.rb +0 -30
- data/test/unit/test_chunked_reader.rb +0 -123
- data/test/unit/test_trailer_parser.rb +0 -52
@@ -50,8 +50,19 @@
|
|
50
50
|
field_value = any* >start_value %write_value;
|
51
51
|
|
52
52
|
message_header = field_name ":" " "* field_value :> CRLF;
|
53
|
+
chunk_ext_val = token*;
|
54
|
+
chunk_ext_name = token*;
|
55
|
+
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
|
56
|
+
last_chunk = "0"+ chunk_extension CRLF;
|
57
|
+
chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
|
58
|
+
chunk_end = CRLF;
|
59
|
+
chunk_body = any >skip_chunk_data;
|
60
|
+
chunk_begin = chunk_size chunk_extension CRLF;
|
61
|
+
chunk = chunk_begin chunk_body chunk_end;
|
62
|
+
ChunkedBody := chunk* last_chunk @end_chunked_body;
|
63
|
+
Trailers := (message_header)* CRLF @end_trailers;
|
53
64
|
|
54
|
-
Request = Request_Line (
|
65
|
+
Request = Request_Line (message_header)* CRLF @header_done;
|
55
66
|
|
56
67
|
main := Request;
|
57
68
|
|
data/lib/unicorn.rb
CHANGED
@@ -11,8 +11,6 @@ module Unicorn
|
|
11
11
|
autoload :HttpResponse, 'unicorn/http_response'
|
12
12
|
autoload :Configurator, 'unicorn/configurator'
|
13
13
|
autoload :TeeInput, 'unicorn/tee_input'
|
14
|
-
autoload :ChunkedReader, 'unicorn/chunked_reader'
|
15
|
-
autoload :TrailerParser, 'unicorn/trailer_parser'
|
16
14
|
autoload :Util, 'unicorn/util'
|
17
15
|
|
18
16
|
Z = '' # the stock empty string we use everywhere...
|
data/lib/unicorn/app/exec_cgi.rb
CHANGED
data/lib/unicorn/app/inetd.rb
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
# Copyright (c) 2009 Eric Wong
|
4
4
|
# You can redistribute it and/or modify it under the same terms as Ruby.
|
5
5
|
|
6
|
-
require 'rack/file'
|
7
|
-
|
8
6
|
# Static file handler for Rails < 2.3. This handler is only provided
|
9
7
|
# as a convenience for developers. Performance-minded deployments should
|
10
8
|
# use nginx (or similar) for serving static files.
|
data/lib/unicorn/const.rb
CHANGED
@@ -5,7 +5,7 @@ module Unicorn
|
|
5
5
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
6
6
|
# Symbols did not really improve things much compared to constants.
|
7
7
|
module Const
|
8
|
-
UNICORN_VERSION="0.
|
8
|
+
UNICORN_VERSION="0.90.0".freeze
|
9
9
|
|
10
10
|
DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
|
11
11
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
@@ -27,12 +27,8 @@ module Unicorn
|
|
27
27
|
EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
|
28
28
|
|
29
29
|
# A frozen format for this is about 15% faster
|
30
|
-
HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'.freeze
|
31
|
-
CONTENT_LENGTH="CONTENT_LENGTH".freeze
|
32
30
|
REMOTE_ADDR="REMOTE_ADDR".freeze
|
33
|
-
HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
|
34
31
|
HTTP_EXPECT="HTTP_EXPECT".freeze
|
35
|
-
HTTP_TRAILER="HTTP_TRAILER".freeze
|
36
32
|
RACK_INPUT="rack.input".freeze
|
37
33
|
end
|
38
34
|
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
+
# coding:binary
|
1
2
|
require 'stringio'
|
2
|
-
|
3
|
-
# compiled extension
|
4
3
|
require 'unicorn_http'
|
5
4
|
|
6
5
|
module Unicorn
|
@@ -22,15 +21,11 @@ module Unicorn
|
|
22
21
|
NULL_IO = StringIO.new(Z)
|
23
22
|
LOCALHOST = '127.0.0.1'.freeze
|
24
23
|
|
25
|
-
def initialize
|
26
|
-
end
|
27
|
-
|
28
24
|
# Being explicitly single-threaded, we have certain advantages in
|
29
25
|
# not having to worry about variables being clobbered :)
|
30
|
-
|
31
|
-
BUFFER.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding)
|
26
|
+
BUF = ' ' * Const::CHUNK_SIZE # initial size, may grow
|
32
27
|
PARSER = HttpParser.new
|
33
|
-
|
28
|
+
REQ = {}
|
34
29
|
|
35
30
|
# Does the majority of the IO processing. It has been written in
|
36
31
|
# Ruby using about 8 different IO processing strategies.
|
@@ -46,7 +41,7 @@ module Unicorn
|
|
46
41
|
# This does minimal exception trapping and it is up to the caller
|
47
42
|
# to handle any socket errors (e.g. user aborted upload).
|
48
43
|
def read(socket)
|
49
|
-
|
44
|
+
REQ.clear
|
50
45
|
PARSER.reset
|
51
46
|
|
52
47
|
# From http://www.ietf.org/rfc/rfc3875:
|
@@ -56,42 +51,31 @@ module Unicorn
|
|
56
51
|
# identify the client for the immediate request to the server;
|
57
52
|
# that client may be a proxy, gateway, or other intermediary
|
58
53
|
# acting on behalf of the actual source client."
|
59
|
-
|
54
|
+
REQ[Const::REMOTE_ADDR] =
|
60
55
|
TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
|
61
56
|
|
62
57
|
# short circuit the common case with small GET requests first
|
63
|
-
PARSER.
|
58
|
+
PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)) and
|
64
59
|
return handle_body(socket)
|
65
60
|
|
66
|
-
data =
|
61
|
+
data = BUF.dup # socket.readpartial will clobber data
|
67
62
|
|
68
63
|
# Parser is not done, queue up more data to read and continue parsing
|
69
64
|
# an Exception thrown from the PARSER will throw us out of the loop
|
70
65
|
begin
|
71
|
-
|
72
|
-
PARSER.
|
66
|
+
BUF << socket.readpartial(Const::CHUNK_SIZE, data)
|
67
|
+
PARSER.headers(REQ, BUF) and return handle_body(socket)
|
73
68
|
end while true
|
74
69
|
end
|
75
70
|
|
76
71
|
private
|
77
72
|
|
78
73
|
# Handles dealing with the rest of the request
|
79
|
-
# returns a Rack environment if successful
|
74
|
+
# returns a # Rack environment if successful
|
80
75
|
def handle_body(socket)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
if /\Achunked\z/i =~ PARAMS[Const::HTTP_TRANSFER_ENCODING]
|
85
|
-
socket = ChunkedReader.new(PARAMS, socket, body)
|
86
|
-
length = body = nil
|
87
|
-
end
|
88
|
-
|
89
|
-
TeeInput.new(socket, length, body)
|
90
|
-
else
|
91
|
-
NULL_IO
|
92
|
-
end
|
93
|
-
|
94
|
-
PARAMS.update(DEFAULTS)
|
76
|
+
REQ[Const::RACK_INPUT] = 0 == PARSER.content_length ?
|
77
|
+
NULL_IO : Unicorn::TeeInput.new(socket, REQ, PARSER, BUF)
|
78
|
+
REQ.update(DEFAULTS)
|
95
79
|
end
|
96
80
|
|
97
81
|
end
|
@@ -36,7 +36,7 @@ module Unicorn
|
|
36
36
|
# writes the rack_response to socket as an HTTP response
|
37
37
|
def self.write(socket, rack_response)
|
38
38
|
status, headers, body = rack_response
|
39
|
-
status = CODES[status.to_i]
|
39
|
+
status = CODES[status.to_i] || status
|
40
40
|
OUT.clear
|
41
41
|
|
42
42
|
# Don't bother enforcing duplicate supression, it's a Hash most of
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -11,17 +11,18 @@
|
|
11
11
|
# not support any deviations from it.
|
12
12
|
|
13
13
|
module Unicorn
|
14
|
-
class TeeInput
|
15
|
-
|
16
|
-
def initialize(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
class TeeInput < Struct.new(:socket, :req, :parser, :buf)
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
super(*args)
|
18
|
+
@size = parser.content_length
|
19
|
+
@tmp = @size && @size < Const::MAX_BODY ? StringIO.new(Z.dup) : Util.tmpio
|
20
|
+
@buf2 = buf.dup
|
21
|
+
if buf.size > 0
|
22
|
+
parser.filter_body(@buf2, buf) and finalize_input
|
23
|
+
@tmp.write(@buf2)
|
21
24
|
@tmp.seek(0)
|
22
25
|
end
|
23
|
-
@input = input
|
24
|
-
@size = size # nil if chunked
|
25
26
|
end
|
26
27
|
|
27
28
|
# returns the size of the input. This is what the Content-Length
|
@@ -31,46 +32,45 @@ module Unicorn
|
|
31
32
|
def size
|
32
33
|
@size and return @size
|
33
34
|
|
34
|
-
if
|
35
|
-
|
36
|
-
while tee(Const::CHUNK_SIZE,
|
35
|
+
if socket
|
36
|
+
pos = @tmp.pos
|
37
|
+
while tee(Const::CHUNK_SIZE, @buf2)
|
37
38
|
end
|
38
|
-
@tmp.
|
39
|
+
@tmp.seek(pos)
|
39
40
|
end
|
40
41
|
|
41
|
-
@size =
|
42
|
+
@size = tmp_size
|
42
43
|
end
|
43
44
|
|
44
45
|
def read(*args)
|
45
|
-
|
46
|
+
socket or return @tmp.read(*args)
|
46
47
|
|
47
48
|
length = args.shift
|
48
49
|
if nil == length
|
49
50
|
rv = @tmp.read || Z.dup
|
50
|
-
|
51
|
-
|
52
|
-
rv << tmp
|
51
|
+
while tee(Const::CHUNK_SIZE, @buf2)
|
52
|
+
rv << @buf2
|
53
53
|
end
|
54
54
|
rv
|
55
55
|
else
|
56
|
-
|
57
|
-
diff =
|
56
|
+
rv = args.shift || @buf2.dup
|
57
|
+
diff = tmp_size - @tmp.pos
|
58
58
|
if 0 == diff
|
59
|
-
tee(length,
|
59
|
+
tee(length, rv)
|
60
60
|
else
|
61
|
-
@tmp.read(diff > length ? length : diff,
|
61
|
+
@tmp.read(diff > length ? length : diff, rv)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
66
|
# takes zero arguments for strict Rack::Lint compatibility, unlike IO#gets
|
67
67
|
def gets
|
68
|
-
|
68
|
+
socket or return @tmp.gets
|
69
69
|
nil == $/ and return read
|
70
70
|
|
71
|
-
orig_size =
|
71
|
+
orig_size = tmp_size
|
72
72
|
if @tmp.pos == orig_size
|
73
|
-
tee(Const::CHUNK_SIZE,
|
73
|
+
tee(Const::CHUNK_SIZE, @buf2) or return nil
|
74
74
|
@tmp.seek(orig_size)
|
75
75
|
end
|
76
76
|
|
@@ -79,8 +79,8 @@ module Unicorn
|
|
79
79
|
|
80
80
|
# unlikely, if we got here, then @tmp is at EOF
|
81
81
|
begin
|
82
|
-
orig_size = @tmp.
|
83
|
-
tee(Const::CHUNK_SIZE,
|
82
|
+
orig_size = @tmp.pos
|
83
|
+
tee(Const::CHUNK_SIZE, @buf2) or break
|
84
84
|
@tmp.seek(orig_size)
|
85
85
|
line << @tmp.gets
|
86
86
|
$/ == line[-$/.size, $/.size] and return line
|
@@ -95,11 +95,11 @@ module Unicorn
|
|
95
95
|
yield line
|
96
96
|
end
|
97
97
|
|
98
|
-
self # Rack does not specify what the return value here
|
98
|
+
self # Rack does not specify what the return value is here
|
99
99
|
end
|
100
100
|
|
101
101
|
def rewind
|
102
|
-
@tmp.rewind # Rack does not specify what the return value here
|
102
|
+
@tmp.rewind # Rack does not specify what the return value is here
|
103
103
|
end
|
104
104
|
|
105
105
|
private
|
@@ -107,26 +107,28 @@ module Unicorn
|
|
107
107
|
# tees off a +length+ chunk of data from the input into the IO
|
108
108
|
# backing store as well as returning it. +buf+ must be specified.
|
109
109
|
# returns nil if reading from the input returns nil
|
110
|
-
def tee(length,
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
@input.readpartial(left, buf) == left and @input = nil
|
117
|
-
elsif @input.nil?
|
118
|
-
return nil
|
119
|
-
else
|
120
|
-
@input.readpartial(length, buf)
|
110
|
+
def tee(length, dst)
|
111
|
+
unless parser.body_eof?
|
112
|
+
begin
|
113
|
+
if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
|
114
|
+
@tmp.write(dst)
|
115
|
+
return dst
|
121
116
|
end
|
122
|
-
|
123
|
-
@input.readpartial(length, buf)
|
117
|
+
rescue EOFError
|
124
118
|
end
|
125
|
-
rescue EOFError
|
126
|
-
return @input = nil
|
127
119
|
end
|
128
|
-
|
129
|
-
|
120
|
+
finalize_input
|
121
|
+
end
|
122
|
+
|
123
|
+
def finalize_input
|
124
|
+
while parser.trailers(req, buf).nil?
|
125
|
+
buf << socket.readpartial(Const::CHUNK_SIZE, @buf2)
|
126
|
+
end
|
127
|
+
self.socket = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def tmp_size
|
131
|
+
StringIO === @tmp ? @tmp.size : @tmp.stat.size
|
130
132
|
end
|
131
133
|
|
132
134
|
end
|
data/lib/unicorn/util.rb
CHANGED
@@ -7,13 +7,13 @@ module Unicorn
|
|
7
7
|
|
8
8
|
APPEND_FLAGS = File::WRONLY | File::APPEND
|
9
9
|
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# This reopens ALL logfiles in the process that have been rotated
|
11
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
12
12
|
# A +File+ object is considered for reopening if it is:
|
13
13
|
# 1) opened with the O_APPEND and O_WRONLY flags
|
14
14
|
# 2) opened with an absolute path (starts with "/")
|
15
15
|
# 3) the current open file handle does not match its original open path
|
16
|
-
# 4) unbuffered (as far as userspace buffering goes)
|
16
|
+
# 4) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
17
17
|
# Returns the number of files reopened
|
18
18
|
def reopen_logs
|
19
19
|
nr = 0
|
data/test/benchmark/README
CHANGED
@@ -42,11 +42,6 @@ The benchmark client is usually httperf.
|
|
42
42
|
Another gentle reminder: performance with slow networks/clients
|
43
43
|
is NOT our problem. That is the job of nginx (or similar).
|
44
44
|
|
45
|
-
== request.rb, response.rb, big_request.rb
|
46
|
-
|
47
|
-
These are micro-benchmarks designed to test internal components
|
48
|
-
of Unicorn. It assumes the internal Unicorn API is mostly stable.
|
49
|
-
|
50
45
|
== Contributors
|
51
46
|
|
52
47
|
This directory is maintained independently in the "benchmark" branch
|
data/test/exec/test_exec.rb
CHANGED
@@ -77,6 +77,7 @@ end
|
|
77
77
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
78
78
|
pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
|
79
79
|
wait_master_ready("test_stderr.#{pid}.log")
|
80
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
80
81
|
status = nil
|
81
82
|
assert_nothing_raised do
|
82
83
|
Process.kill(sig, pid)
|
@@ -626,6 +627,7 @@ end
|
|
626
627
|
end
|
627
628
|
|
628
629
|
wait_master_ready(log.path)
|
630
|
+
wait_workers_ready(log.path, 1)
|
629
631
|
File.truncate(log.path, 0)
|
630
632
|
wait_for_file(pid_file)
|
631
633
|
orig_pid = pid = File.read(pid_file).to_i
|
@@ -641,6 +643,7 @@ end
|
|
641
643
|
wait_for_death(pid)
|
642
644
|
|
643
645
|
wait_master_ready(log.path)
|
646
|
+
wait_workers_ready(log.path, 1)
|
644
647
|
File.truncate(log.path, 0)
|
645
648
|
wait_for_file(pid_file)
|
646
649
|
pid = File.read(pid_file).to_i
|
@@ -660,6 +663,7 @@ end
|
|
660
663
|
wait_for_death(pid)
|
661
664
|
|
662
665
|
wait_master_ready(log.path)
|
666
|
+
wait_workers_ready(log.path, 1)
|
663
667
|
File.truncate(log.path, 0)
|
664
668
|
wait_for_file(pid_file)
|
665
669
|
pid = File.read(pid_file).to_i
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -9,12 +9,13 @@ require 'test/test_helper'
|
|
9
9
|
include Unicorn
|
10
10
|
|
11
11
|
class HttpParserTest < Test::Unit::TestCase
|
12
|
-
|
12
|
+
|
13
13
|
def test_parse_simple
|
14
14
|
parser = HttpParser.new
|
15
15
|
req = {}
|
16
16
|
http = "GET / HTTP/1.1\r\n\r\n"
|
17
|
-
|
17
|
+
assert_equal req, parser.headers(req, http)
|
18
|
+
assert_equal '', http
|
18
19
|
|
19
20
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
20
21
|
assert_equal '/', req['REQUEST_PATH']
|
@@ -23,17 +24,19 @@ class HttpParserTest < Test::Unit::TestCase
|
|
23
24
|
assert_equal 'GET', req['REQUEST_METHOD']
|
24
25
|
assert_nil req['FRAGMENT']
|
25
26
|
assert_equal '', req['QUERY_STRING']
|
26
|
-
assert_nil req[:http_body]
|
27
27
|
|
28
|
+
assert parser.keepalive?
|
28
29
|
parser.reset
|
29
30
|
req.clear
|
30
31
|
|
31
|
-
|
32
|
+
http = "G"
|
33
|
+
assert_nil parser.headers(req, http)
|
34
|
+
assert_equal "G", http
|
32
35
|
assert req.empty?
|
33
36
|
|
34
37
|
# try parsing again to ensure we were reset correctly
|
35
38
|
http = "GET /hello-world HTTP/1.1\r\n\r\n"
|
36
|
-
assert parser.
|
39
|
+
assert parser.headers(req, http)
|
37
40
|
|
38
41
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
39
42
|
assert_equal '/hello-world', req['REQUEST_PATH']
|
@@ -42,52 +45,95 @@ class HttpParserTest < Test::Unit::TestCase
|
|
42
45
|
assert_equal 'GET', req['REQUEST_METHOD']
|
43
46
|
assert_nil req['FRAGMENT']
|
44
47
|
assert_equal '', req['QUERY_STRING']
|
45
|
-
|
48
|
+
assert_equal '', http
|
49
|
+
assert parser.keepalive?
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_connection_close_no_ka
|
53
|
+
parser = HttpParser.new
|
54
|
+
req = {}
|
55
|
+
tmp = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
56
|
+
assert_equal req.object_id, parser.headers(req, tmp).object_id
|
57
|
+
assert_equal "GET", req['REQUEST_METHOD']
|
58
|
+
assert ! parser.keepalive?
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_connection_keep_alive_ka
|
62
|
+
parser = HttpParser.new
|
63
|
+
req = {}
|
64
|
+
tmp = "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
|
65
|
+
assert_equal req.object_id, parser.headers(req, tmp).object_id
|
66
|
+
assert parser.keepalive?
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_connection_keep_alive_ka_bad_method
|
70
|
+
parser = HttpParser.new
|
71
|
+
req = {}
|
72
|
+
tmp = "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
|
73
|
+
assert_equal req.object_id, parser.headers(req, tmp).object_id
|
74
|
+
assert ! parser.keepalive?
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_connection_keep_alive_ka_bad_version
|
78
|
+
parser = HttpParser.new
|
79
|
+
req = {}
|
80
|
+
tmp = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
|
81
|
+
assert_equal req.object_id, parser.headers(req, tmp).object_id
|
82
|
+
assert parser.keepalive?
|
46
83
|
end
|
47
84
|
|
48
85
|
def test_parse_server_host_default_port
|
49
86
|
parser = HttpParser.new
|
50
87
|
req = {}
|
51
|
-
|
88
|
+
tmp = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
|
89
|
+
assert_equal req, parser.headers(req, tmp)
|
52
90
|
assert_equal 'foo', req['SERVER_NAME']
|
53
91
|
assert_equal '80', req['SERVER_PORT']
|
54
|
-
|
92
|
+
assert_equal '', tmp
|
93
|
+
assert parser.keepalive?
|
55
94
|
end
|
56
95
|
|
57
96
|
def test_parse_server_host_alt_port
|
58
97
|
parser = HttpParser.new
|
59
98
|
req = {}
|
60
|
-
|
99
|
+
tmp = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
|
100
|
+
assert_equal req, parser.headers(req, tmp)
|
61
101
|
assert_equal 'foo', req['SERVER_NAME']
|
62
102
|
assert_equal '999', req['SERVER_PORT']
|
63
|
-
|
103
|
+
assert_equal '', tmp
|
104
|
+
assert parser.keepalive?
|
64
105
|
end
|
65
106
|
|
66
107
|
def test_parse_server_host_empty_port
|
67
108
|
parser = HttpParser.new
|
68
109
|
req = {}
|
69
|
-
|
110
|
+
tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n"
|
111
|
+
assert_equal req, parser.headers(req, tmp)
|
70
112
|
assert_equal 'foo', req['SERVER_NAME']
|
71
113
|
assert_equal '80', req['SERVER_PORT']
|
72
|
-
|
114
|
+
assert_equal '', tmp
|
115
|
+
assert parser.keepalive?
|
73
116
|
end
|
74
117
|
|
75
118
|
def test_parse_server_host_xfp_https
|
76
119
|
parser = HttpParser.new
|
77
120
|
req = {}
|
78
|
-
|
79
|
-
|
121
|
+
tmp = "GET / HTTP/1.1\r\nHost: foo:\r\n" \
|
122
|
+
"X-Forwarded-Proto: https\r\n\r\n"
|
123
|
+
assert_equal req, parser.headers(req, tmp)
|
80
124
|
assert_equal 'foo', req['SERVER_NAME']
|
81
125
|
assert_equal '443', req['SERVER_PORT']
|
82
|
-
|
126
|
+
assert_equal '', tmp
|
127
|
+
assert parser.keepalive?
|
83
128
|
end
|
84
129
|
|
85
130
|
def test_parse_strange_headers
|
86
131
|
parser = HttpParser.new
|
87
132
|
req = {}
|
88
133
|
should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
|
89
|
-
|
90
|
-
|
134
|
+
assert_equal req, parser.headers(req, should_be_good)
|
135
|
+
assert_equal '', should_be_good
|
136
|
+
assert parser.keepalive?
|
91
137
|
|
92
138
|
# ref: http://thread.gmane.org/gmane.comp.lang.ruby.mongrel.devel/37/focus=45
|
93
139
|
# (note we got 'pen' mixed up with 'pound' in that thread,
|
@@ -110,8 +156,10 @@ class HttpParserTest < Test::Unit::TestCase
|
|
110
156
|
parser = HttpParser.new
|
111
157
|
req = {}
|
112
158
|
sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
|
113
|
-
|
114
|
-
|
159
|
+
assert_equal req, parser.headers(req, sorta_safe)
|
160
|
+
assert_equal path, req['REQUEST_URI']
|
161
|
+
assert_equal '', sorta_safe
|
162
|
+
assert parser.keepalive?
|
115
163
|
end
|
116
164
|
end
|
117
165
|
|
@@ -120,30 +168,34 @@ class HttpParserTest < Test::Unit::TestCase
|
|
120
168
|
req = {}
|
121
169
|
bad_http = "GET / SsUTF/1.1"
|
122
170
|
|
123
|
-
assert_raises(HttpParserError) { parser.
|
171
|
+
assert_raises(HttpParserError) { parser.headers(req, bad_http) }
|
172
|
+
|
173
|
+
# make sure we can recover
|
124
174
|
parser.reset
|
125
|
-
|
126
|
-
|
175
|
+
req.clear
|
176
|
+
assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
|
177
|
+
assert ! parser.keepalive?
|
127
178
|
end
|
128
179
|
|
129
180
|
def test_piecemeal
|
130
181
|
parser = HttpParser.new
|
131
182
|
req = {}
|
132
183
|
http = "GET"
|
133
|
-
|
134
|
-
|
135
|
-
|
184
|
+
assert_nil parser.headers(req, http)
|
185
|
+
assert_nil parser.headers(req, http)
|
186
|
+
assert_nil parser.headers(req, http << " / HTTP/1.0")
|
136
187
|
assert_equal '/', req['REQUEST_PATH']
|
137
188
|
assert_equal '/', req['REQUEST_URI']
|
138
189
|
assert_equal 'GET', req['REQUEST_METHOD']
|
139
|
-
|
190
|
+
assert_nil parser.headers(req, http << "\r\n")
|
140
191
|
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
141
|
-
|
142
|
-
|
192
|
+
assert_nil parser.headers(req, http << "\r")
|
193
|
+
assert_equal req, parser.headers(req, http << "\n")
|
143
194
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
144
195
|
assert_nil req['FRAGMENT']
|
145
196
|
assert_equal '', req['QUERY_STRING']
|
146
|
-
|
197
|
+
assert_equal "", http
|
198
|
+
assert ! parser.keepalive?
|
147
199
|
end
|
148
200
|
|
149
201
|
# not common, but underscores do appear in practice
|
@@ -151,7 +203,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
151
203
|
parser = HttpParser.new
|
152
204
|
req = {}
|
153
205
|
http = "GET http://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
|
154
|
-
|
206
|
+
assert_equal req, parser.headers(req, http)
|
155
207
|
assert_equal 'http', req['rack.url_scheme']
|
156
208
|
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
157
209
|
assert_equal '/foo', req['REQUEST_PATH']
|
@@ -160,14 +212,15 @@ class HttpParserTest < Test::Unit::TestCase
|
|
160
212
|
assert_equal 'under_score.example.com', req['HTTP_HOST']
|
161
213
|
assert_equal 'under_score.example.com', req['SERVER_NAME']
|
162
214
|
assert_equal '80', req['SERVER_PORT']
|
163
|
-
|
215
|
+
assert_equal "", http
|
216
|
+
assert ! parser.keepalive?
|
164
217
|
end
|
165
218
|
|
166
219
|
def test_absolute_uri
|
167
220
|
parser = HttpParser.new
|
168
221
|
req = {}
|
169
222
|
http = "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n"
|
170
|
-
|
223
|
+
assert_equal req, parser.headers(req, http)
|
171
224
|
assert_equal 'http', req['rack.url_scheme']
|
172
225
|
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
173
226
|
assert_equal '/foo', req['REQUEST_PATH']
|
@@ -176,6 +229,8 @@ class HttpParserTest < Test::Unit::TestCase
|
|
176
229
|
assert_equal 'example.com', req['HTTP_HOST']
|
177
230
|
assert_equal 'example.com', req['SERVER_NAME']
|
178
231
|
assert_equal '80', req['SERVER_PORT']
|
232
|
+
assert_equal "", http
|
233
|
+
assert ! parser.keepalive?
|
179
234
|
end
|
180
235
|
|
181
236
|
# X-Forwarded-Proto is not in rfc2616, absolute URIs are, however...
|
@@ -184,7 +239,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
184
239
|
req = {}
|
185
240
|
http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
|
186
241
|
"X-Forwarded-Proto: http\r\n\r\n"
|
187
|
-
|
242
|
+
assert_equal req, parser.headers(req, http)
|
188
243
|
assert_equal 'https', req['rack.url_scheme']
|
189
244
|
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
190
245
|
assert_equal '/foo', req['REQUEST_PATH']
|
@@ -193,6 +248,8 @@ class HttpParserTest < Test::Unit::TestCase
|
|
193
248
|
assert_equal 'example.com', req['HTTP_HOST']
|
194
249
|
assert_equal 'example.com', req['SERVER_NAME']
|
195
250
|
assert_equal '443', req['SERVER_PORT']
|
251
|
+
assert_equal "", http
|
252
|
+
assert parser.keepalive?
|
196
253
|
end
|
197
254
|
|
198
255
|
# Host: header should be ignored for absolute URIs
|
@@ -201,7 +258,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
201
258
|
req = {}
|
202
259
|
http = "GET http://example.com:8080/foo?q=bar HTTP/1.2\r\n" \
|
203
260
|
"Host: bad.example.com\r\n\r\n"
|
204
|
-
|
261
|
+
assert_equal req, parser.headers(req, http)
|
205
262
|
assert_equal 'http', req['rack.url_scheme']
|
206
263
|
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
207
264
|
assert_equal '/foo', req['REQUEST_PATH']
|
@@ -210,6 +267,8 @@ class HttpParserTest < Test::Unit::TestCase
|
|
210
267
|
assert_equal 'example.com:8080', req['HTTP_HOST']
|
211
268
|
assert_equal 'example.com', req['SERVER_NAME']
|
212
269
|
assert_equal '8080', req['SERVER_PORT']
|
270
|
+
assert_equal "", http
|
271
|
+
assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
213
272
|
end
|
214
273
|
|
215
274
|
def test_absolute_uri_with_empty_port
|
@@ -217,7 +276,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
217
276
|
req = {}
|
218
277
|
http = "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
|
219
278
|
"Host: bad.example.com\r\n\r\n"
|
220
|
-
|
279
|
+
assert_equal req, parser.headers(req, http)
|
221
280
|
assert_equal 'https', req['rack.url_scheme']
|
222
281
|
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
223
282
|
assert_equal '/foo', req['REQUEST_PATH']
|
@@ -226,32 +285,36 @@ class HttpParserTest < Test::Unit::TestCase
|
|
226
285
|
assert_equal 'example.com:', req['HTTP_HOST']
|
227
286
|
assert_equal 'example.com', req['SERVER_NAME']
|
228
287
|
assert_equal '443', req['SERVER_PORT']
|
288
|
+
assert_equal "", http
|
289
|
+
assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
229
290
|
end
|
230
291
|
|
231
292
|
def test_put_body_oneshot
|
232
293
|
parser = HttpParser.new
|
233
294
|
req = {}
|
234
295
|
http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
|
235
|
-
|
296
|
+
assert_equal req, parser.headers(req, http)
|
236
297
|
assert_equal '/', req['REQUEST_PATH']
|
237
298
|
assert_equal '/', req['REQUEST_URI']
|
238
299
|
assert_equal 'PUT', req['REQUEST_METHOD']
|
239
300
|
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
240
301
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
241
|
-
assert_equal "abcde",
|
302
|
+
assert_equal "abcde", http
|
303
|
+
assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
242
304
|
end
|
243
305
|
|
244
306
|
def test_put_body_later
|
245
307
|
parser = HttpParser.new
|
246
308
|
req = {}
|
247
309
|
http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
|
248
|
-
|
310
|
+
assert_equal req, parser.headers(req, http)
|
249
311
|
assert_equal '/l', req['REQUEST_PATH']
|
250
312
|
assert_equal '/l', req['REQUEST_URI']
|
251
313
|
assert_equal 'PUT', req['REQUEST_METHOD']
|
252
314
|
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
253
315
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
254
|
-
assert_equal "",
|
316
|
+
assert_equal "", http
|
317
|
+
assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
255
318
|
end
|
256
319
|
|
257
320
|
def test_unknown_methods
|
@@ -261,14 +324,15 @@ class HttpParserTest < Test::Unit::TestCase
|
|
261
324
|
s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
262
325
|
ok = false
|
263
326
|
assert_nothing_raised do
|
264
|
-
ok = parser.
|
327
|
+
ok = parser.headers(req, s)
|
265
328
|
end
|
266
329
|
assert ok
|
267
330
|
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
268
331
|
assert_equal 'posts-17408', req['FRAGMENT']
|
269
332
|
assert_equal 'page=1', req['QUERY_STRING']
|
270
|
-
assert_equal "",
|
333
|
+
assert_equal "", s
|
271
334
|
assert_equal m, req['REQUEST_METHOD']
|
335
|
+
assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
272
336
|
}
|
273
337
|
end
|
274
338
|
|
@@ -278,13 +342,14 @@ class HttpParserTest < Test::Unit::TestCase
|
|
278
342
|
get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
279
343
|
ok = false
|
280
344
|
assert_nothing_raised do
|
281
|
-
ok = parser.
|
345
|
+
ok = parser.headers(req, get)
|
282
346
|
end
|
283
347
|
assert ok
|
284
348
|
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
285
349
|
assert_equal 'posts-17408', req['FRAGMENT']
|
286
350
|
assert_equal 'page=1', req['QUERY_STRING']
|
287
|
-
|
351
|
+
assert_equal '', get
|
352
|
+
assert parser.keepalive?
|
288
353
|
end
|
289
354
|
|
290
355
|
# lame random garbage maker
|
@@ -309,7 +374,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
309
374
|
10.times do |c|
|
310
375
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
311
376
|
assert_raises Unicorn::HttpParserError do
|
312
|
-
parser.
|
377
|
+
parser.headers({}, get)
|
313
378
|
parser.reset
|
314
379
|
end
|
315
380
|
end
|
@@ -318,7 +383,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
318
383
|
10.times do |c|
|
319
384
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
320
385
|
assert_raises Unicorn::HttpParserError do
|
321
|
-
parser.
|
386
|
+
parser.headers({}, get)
|
322
387
|
parser.reset
|
323
388
|
end
|
324
389
|
end
|
@@ -327,7 +392,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
327
392
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
328
393
|
get << "X-Test: test\r\n" * (80 * 1024)
|
329
394
|
assert_raises Unicorn::HttpParserError do
|
330
|
-
parser.
|
395
|
+
parser.headers({}, get)
|
331
396
|
parser.reset
|
332
397
|
end
|
333
398
|
|
@@ -335,7 +400,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
335
400
|
10.times do |c|
|
336
401
|
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
337
402
|
assert_raises Unicorn::HttpParserError do
|
338
|
-
parser.
|
403
|
+
parser.headers({}, get)
|
339
404
|
parser.reset
|
340
405
|
end
|
341
406
|
end
|