unicorn 0.9.2 → 0.90.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|