unicorn 0.9.2 → 0.90.0

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