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.
Files changed (60) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG +3 -0
  3. data/GNUmakefile +7 -7
  4. data/Manifest +20 -23
  5. data/README +16 -13
  6. data/TODO +5 -3
  7. data/bin/unicorn +1 -1
  8. data/bin/unicorn_rails +1 -1
  9. data/ext/unicorn_http/c_util.h +107 -0
  10. data/ext/unicorn_http/common_field_optimization.h +110 -0
  11. data/ext/unicorn_http/ext_help.h +41 -5
  12. data/ext/unicorn_http/extconf.rb +3 -1
  13. data/ext/unicorn_http/global_variables.h +93 -0
  14. data/ext/unicorn_http/unicorn_http.c +2123 -326
  15. data/ext/unicorn_http/unicorn_http.rl +488 -87
  16. data/ext/unicorn_http/unicorn_http_common.rl +12 -1
  17. data/lib/unicorn.rb +0 -2
  18. data/lib/unicorn/app/exec_cgi.rb +0 -1
  19. data/lib/unicorn/app/inetd.rb +2 -0
  20. data/lib/unicorn/app/old_rails/static.rb +0 -2
  21. data/lib/unicorn/const.rb +1 -5
  22. data/lib/unicorn/http_request.rb +13 -29
  23. data/lib/unicorn/http_response.rb +1 -1
  24. data/lib/unicorn/tee_input.rb +48 -46
  25. data/lib/unicorn/util.rb +3 -3
  26. data/test/benchmark/README +0 -5
  27. data/test/exec/test_exec.rb +4 -0
  28. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/.gitignore +0 -0
  29. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/Rakefile +0 -0
  30. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/application_controller.rb +0 -0
  31. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/foo_controller.rb +0 -0
  32. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/helpers/application_helper.rb +0 -0
  33. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/boot.rb +0 -0
  34. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/database.yml +0 -0
  35. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environment.rb +0 -0
  36. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/development.rb +0 -0
  37. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/production.rb +0 -0
  38. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/routes.rb +0 -0
  39. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/db/.gitignore +0 -0
  40. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/404.html +0 -0
  41. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/500.html +0 -0
  42. data/test/unit/test_http_parser.rb +112 -47
  43. data/test/unit/test_http_parser_ng.rb +284 -0
  44. data/test/unit/test_request.rb +25 -7
  45. data/test/unit/test_response.rb +11 -0
  46. data/test/unit/test_server.rb +7 -2
  47. data/test/unit/test_signals.rb +2 -0
  48. data/test/unit/test_tee_input.rb +118 -2
  49. data/test/unit/test_upload.rb +1 -1
  50. data/test/unit/test_util.rb +5 -0
  51. data/unicorn.gemspec +6 -6
  52. metadata +33 -37
  53. data/ext/unicorn_http/unicorn_http.h +0 -1289
  54. data/lib/unicorn/chunked_reader.rb +0 -77
  55. data/lib/unicorn/trailer_parser.rb +0 -52
  56. data/test/benchmark/big_request.rb +0 -44
  57. data/test/benchmark/request.rb +0 -56
  58. data/test/benchmark/response.rb +0 -30
  59. data/test/unit/test_chunked_reader.rb +0 -123
  60. 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 ( message_header )* ( CRLF @done );
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...
@@ -1,5 +1,4 @@
1
1
  require 'unicorn'
2
- require 'rack'
3
2
 
4
3
  module Unicorn::App
5
4
 
@@ -85,6 +85,8 @@ module Unicorn::App
85
85
  errors.write("Failed to reap #{str} (PID:#{pid})\n")
86
86
  end
87
87
  }
88
+ out_rd.close
89
+ err_rd.close
88
90
  end
89
91
 
90
92
  end
@@ -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.9.2".freeze
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
 
@@ -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
- BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
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
- PARAMS = Hash.new
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
- PARAMS.clear
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
- PARAMS[Const::REMOTE_ADDR] =
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.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and
58
+ PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)) and
64
59
  return handle_body(socket)
65
60
 
66
- data = BUFFER.dup # socket.readpartial will clobber BUFFER
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
- data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
72
- PARSER.execute(PARAMS, data) and return handle_body(socket)
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
- PARAMS[Const::RACK_INPUT] = if (body = PARAMS.delete(:http_body))
82
- length = PARAMS[Const::CONTENT_LENGTH].to_i
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
@@ -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(input, size, body)
17
- @tmp = Unicorn::Util.tmpio
18
-
19
- if body
20
- @tmp.write(body)
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 @input
35
- buf = Z.dup
36
- while tee(Const::CHUNK_SIZE, buf)
35
+ if socket
36
+ pos = @tmp.pos
37
+ while tee(Const::CHUNK_SIZE, @buf2)
37
38
  end
38
- @tmp.rewind
39
+ @tmp.seek(pos)
39
40
  end
40
41
 
41
- @size = @tmp.stat.size
42
+ @size = tmp_size
42
43
  end
43
44
 
44
45
  def read(*args)
45
- @input or return @tmp.read(*args)
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
- tmp = Z.dup
51
- while tee(Const::CHUNK_SIZE, tmp)
52
- rv << tmp
51
+ while tee(Const::CHUNK_SIZE, @buf2)
52
+ rv << @buf2
53
53
  end
54
54
  rv
55
55
  else
56
- buf = args.shift || Z.dup
57
- diff = @tmp.stat.size - @tmp.pos
56
+ rv = args.shift || @buf2.dup
57
+ diff = tmp_size - @tmp.pos
58
58
  if 0 == diff
59
- tee(length, buf)
59
+ tee(length, rv)
60
60
  else
61
- @tmp.read(diff > length ? length : diff, buf)
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
- @input or return @tmp.gets
68
+ socket or return @tmp.gets
69
69
  nil == $/ and return read
70
70
 
71
- orig_size = @tmp.stat.size
71
+ orig_size = tmp_size
72
72
  if @tmp.pos == orig_size
73
- tee(Const::CHUNK_SIZE, Z.dup) or return nil
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.stat.size
83
- tee(Const::CHUNK_SIZE, Z.dup) or break
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, buf)
111
- begin
112
- if @size
113
- left = @size - @tmp.stat.size
114
- 0 == left and return nil
115
- if length >= left
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
- else # ChunkedReader#readpartial just raises EOFError when done
123
- @input.readpartial(length, buf)
117
+ rescue EOFError
124
118
  end
125
- rescue EOFError
126
- return @input = nil
127
119
  end
128
- @tmp.write(buf)
129
- buf
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
- # this reopens logs that have been rotated (using logrotate(8) or
11
- # similar). It is recommended that you install
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
@@ -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
@@ -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
@@ -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
- assert parser.execute(req, http)
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
- assert ! parser.execute(req, "G")
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.execute(req, http)
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n")
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n")
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, "GET / HTTP/1.1\r\nHost: foo:\r\n" \
79
- "X-Forwarded-Proto: https\r\n\r\n")
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, should_be_good)
90
- assert_nil req[:http_body]
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
- assert parser.execute(req, sorta_safe)
114
- assert_nil req[:http_body]
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.execute(req, bad_http) }
171
+ assert_raises(HttpParserError) { parser.headers(req, bad_http) }
172
+
173
+ # make sure we can recover
124
174
  parser.reset
125
- assert(parser.execute({}, "GET / HTTP/1.0\r\n\r\n"))
126
- assert_nil req[:http_body]
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
- assert ! parser.execute(req, http)
134
- assert_raises(HttpParserError) { parser.execute(req, http) }
135
- assert ! parser.execute(req, http << " / HTTP/1.0")
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
- assert ! parser.execute(req, http << "\r\n")
190
+ assert_nil parser.headers(req, http << "\r\n")
140
191
  assert_equal 'HTTP/1.0', req['HTTP_VERSION']
141
- assert ! parser.execute(req, http << "\r")
142
- assert parser.execute(req, http << "\n")
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, http)
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
- assert_nil req[:http_body]
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
- assert parser.execute(req, http)
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
- assert parser.execute(req, http)
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
- assert parser.execute(req, http)
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
- assert parser.execute(req, http)
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
- assert parser.execute(req, http)
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", req[:http_body]
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
- assert parser.execute(req, http)
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 "", req[:http_body]
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.execute(req, s)
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 "", req[:http_body]
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.execute(req, get)
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
- assert_nil req[:http_body]
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.execute({}, get)
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.execute({}, get)
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.execute({}, get)
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.execute({}, get)
403
+ parser.headers({}, get)
339
404
  parser.reset
340
405
  end
341
406
  end