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
@@ -1,77 +0,0 @@
1
- # Copyright (c) 2009 Eric Wong
2
- # You can redistribute it and/or modify it under the same terms as Ruby.
3
-
4
- require 'unicorn'
5
- require 'unicorn_http'
6
-
7
- module Unicorn
8
- class ChunkedReader
9
-
10
- def initialize(env, input, buf)
11
- @env, @input, @buf = env, input, buf
12
- @chunk_left = 0
13
- parse_chunk_header
14
- end
15
-
16
- def readpartial(max, buf = Z.dup)
17
- while @input && @chunk_left <= 0 && ! parse_chunk_header
18
- @buf << @input.readpartial(Const::CHUNK_SIZE, buf)
19
- end
20
-
21
- if @input
22
- begin
23
- @buf << @input.read_nonblock(Const::CHUNK_SIZE, buf)
24
- rescue Errno::EAGAIN, Errno::EINTR
25
- end
26
- end
27
-
28
- max = @chunk_left if max > @chunk_left
29
- buf.replace(last_block(max) || Z)
30
- @chunk_left -= buf.size
31
- (0 == buf.size && @input.nil?) and raise EOFError
32
- buf
33
- end
34
-
35
- private
36
-
37
- def last_block(max = nil)
38
- rv = @buf
39
- if max && rv && max < rv.size
40
- @buf = rv[max - rv.size, rv.size - max]
41
- return rv[0, max]
42
- end
43
- @buf = Z.dup
44
- rv
45
- end
46
-
47
- def parse_chunk_header
48
- buf = @buf
49
- # ignoring chunk-extension info for now, I haven't seen any use for it
50
- # (or any users, and TE:chunked sent by clients is rare already)
51
- # if there was not enough data in buffer to parse length of the chunk
52
- # then just return
53
- if buf.sub!(/\A(?:\r\n)?([a-fA-F0-9]{1,8})[^\r]*?\r\n/, Z)
54
- @chunk_left = $1.to_i(16)
55
- if 0 == @chunk_left # EOF
56
- buf.sub!(/\A\r\n(?:\r\n)?/, Z) # cleanup for future requests
57
- if trailer = @env[Const::HTTP_TRAILER]
58
- tp = TrailerParser.new(trailer)
59
- while ! tp.execute!(@env, buf)
60
- buf << @input.readpartial(Const::CHUNK_SIZE)
61
- end
62
- end
63
- @input = nil
64
- end
65
- return @chunk_left
66
- end
67
-
68
- buf.size > 256 and
69
- raise HttpParserError,
70
- "malformed chunk, chunk-length not found in buffer: " \
71
- "#{buf.inspect}"
72
- nil
73
- end
74
-
75
- end
76
-
77
- end
@@ -1,52 +0,0 @@
1
- # Copyright (c) 2009 Eric Wong
2
- # You can redistribute it and/or modify it under the same terms as Ruby.
3
- require 'unicorn'
4
- require 'unicorn_http'
5
-
6
- # Eventually I should integrate this into HttpParser...
7
- module Unicorn
8
- class TrailerParser
9
-
10
- TR_FR = 'a-z-'.freeze
11
- TR_TO = 'A-Z_'.freeze
12
-
13
- # initializes HTTP trailer parser with acceptable +trailer+
14
- def initialize(http_trailer)
15
- @trailers = http_trailer.split(/\s*,\s*/).inject({}) { |hash, key|
16
- hash[key.tr(TR_FR, TR_TO)] = true
17
- hash
18
- }
19
- end
20
-
21
- # Executes our TrailerParser on +data+ and modifies +env+ This will
22
- # shrink +data+ as it is being consumed. Returns true if it has
23
- # parsed all trailers, false if not. It raises HttpParserError on
24
- # parse failure or unknown headers. It has slightly smaller limits
25
- # than the C-based HTTP parser but should not be an issue in practice
26
- # since Content-MD5 is probably the only legitimate use for it.
27
- def execute!(env, data)
28
- data.size > 0xffff and
29
- raise HttpParserError, "trailer buffer too large: #{data.size} bytes"
30
-
31
- begin
32
- data.sub!(/\A([^\r]+)\r\n/, Z) or return false # need more data
33
-
34
- key, val = $1.split(/:\s*/, 2)
35
-
36
- key.size > 256 and
37
- raise HttpParserError, "trailer key #{key.inspect} is too long"
38
- val.size > 8192 and
39
- raise HttpParserError, "trailer value #{val.inspect} is too long"
40
-
41
- key.tr!(TR_FR, TR_TO)
42
-
43
- @trailers.delete(key) or
44
- raise HttpParserError, "unknown trailer: #{key.inspect}"
45
- env["HTTP_#{key}"] = val
46
-
47
- @trailers.empty? and return true
48
- end while true
49
- end
50
-
51
- end
52
- end
@@ -1,44 +0,0 @@
1
- require 'benchmark'
2
- require 'tempfile'
3
- require 'unicorn'
4
- nr = ENV['nr'] ? ENV['nr'].to_i : 100
5
- bs = ENV['bs'] ? ENV['bs'].to_i : (1024 * 1024)
6
- count = ENV['count'] ? ENV['count'].to_i : 4
7
- length = bs * count
8
- slice = (' ' * bs).freeze
9
-
10
- big = Tempfile.new('')
11
-
12
- def big.unicorn_peeraddr # old versions of Unicorn used this
13
- '127.0.0.1'
14
- end
15
-
16
- big.syswrite(
17
- "PUT /hello/world/puturl?abcd=efg&hi#anchor HTTP/1.0\r\n" \
18
- "Host: localhost\r\n" \
19
- "Accept: */*\r\n" \
20
- "Content-Length: #{length}\r\n" \
21
- "User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda\r\n" \
22
- "\r\n")
23
- count.times { big.syswrite(slice) }
24
- big.sysseek(0)
25
- big.fsync
26
-
27
- include Unicorn
28
- request = HttpRequest.new(Logger.new($stderr))
29
- unless request.respond_to?(:reset)
30
- def request.reset
31
- # no-op
32
- end
33
- end
34
-
35
- Benchmark.bmbm do |x|
36
- x.report("big") do
37
- for i in 1..nr
38
- request.read(big)
39
- request.reset
40
- big.sysseek(0)
41
- end
42
- end
43
- end
44
-
@@ -1,56 +0,0 @@
1
- require 'benchmark'
2
- require 'unicorn'
3
- nr = ENV['nr'] ? ENV['nr'].to_i : 100000
4
-
5
- class TestClient
6
- def initialize(response)
7
- @response = (response.join("\r\n") << "\r\n\r\n").freeze
8
- end
9
- def sysread(len, buf)
10
- buf.replace(@response)
11
- end
12
-
13
- alias readpartial sysread
14
-
15
- # old versions of Unicorn used this
16
- def unicorn_peeraddr
17
- '127.0.0.1'
18
- end
19
- end
20
-
21
- small = TestClient.new([
22
- 'GET / HTTP/1.0',
23
- 'Host: localhost',
24
- 'Accept: */*',
25
- 'User-Agent: test-user-agent 0.1.0'
26
- ])
27
-
28
- medium = TestClient.new([
29
- 'GET /hello/world/geturl?abcd=efg&hi#anchor HTTP/1.0',
30
- 'Host: localhost',
31
- 'Accept: */*',
32
- 'User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda'
33
- ])
34
-
35
- include Unicorn
36
- request = HttpRequest.new(Logger.new($stderr))
37
- unless request.respond_to?(:reset)
38
- def request.reset
39
- # no-op
40
- end
41
- end
42
-
43
- Benchmark.bmbm do |x|
44
- x.report("small") do
45
- for i in 1..nr
46
- request.read(small)
47
- request.reset
48
- end
49
- end
50
- x.report("medium") do
51
- for i in 1..nr
52
- request.read(medium)
53
- request.reset
54
- end
55
- end
56
- end
@@ -1,30 +0,0 @@
1
- require 'benchmark'
2
- require 'unicorn'
3
-
4
- class NullWriter
5
- def syswrite(buf); buf.size; end
6
- alias write syswrite
7
- def close; end
8
- end
9
-
10
- include Unicorn
11
-
12
- socket = NullWriter.new
13
- bs = ENV['bs'] ? ENV['bs'].to_i : 4096
14
- count = ENV['count'] ? ENV['count'].to_i : 1
15
- slice = (' ' * bs).freeze
16
- body = (1..count).map { slice }.freeze
17
- hdr = {
18
- 'Content-Length' => (bs * count).to_s.freeze,
19
- 'Content-Type' => 'text/plain'.freeze
20
- }.freeze
21
- response = [ 200, hdr, body ].freeze
22
-
23
- nr = ENV['nr'] ? ENV['nr'].to_i : 100000
24
- Benchmark.bmbm do |x|
25
- x.report do
26
- for i in 1..nr
27
- HttpResponse.write(socket.dup, response)
28
- end
29
- end
30
- end
@@ -1,123 +0,0 @@
1
- require 'test/unit'
2
- require 'unicorn'
3
- require 'unicorn_http'
4
- require 'tempfile'
5
- require 'io/nonblock'
6
- require 'digest/sha1'
7
-
8
- class TestChunkedReader < Test::Unit::TestCase
9
-
10
- def setup
11
- @env = {}
12
- @rd, @wr = IO.pipe
13
- @rd.binmode
14
- @wr.binmode
15
- @rd.sync = @wr.sync = true
16
- @start_pid = $$
17
- end
18
-
19
- def teardown
20
- return if $$ != @start_pid
21
- @rd.close rescue nil
22
- @wr.close rescue nil
23
- begin
24
- Process.wait
25
- rescue Errno::ECHILD
26
- break
27
- end while true
28
- end
29
-
30
- def test_error
31
- cr = bin_reader("8\r\nasdfasdf\r\n8\r\nasdfasdfa#{'a' * 1024}")
32
- a = nil
33
- assert_nothing_raised { a = cr.readpartial(8192) }
34
- assert_equal 'asdfasdf', a
35
- assert_nothing_raised { a = cr.readpartial(8192) }
36
- assert_equal 'asdfasdf', a
37
- assert_raises(Unicorn::HttpParserError) { cr.readpartial(8192) }
38
- end
39
-
40
- def test_eof1
41
- cr = bin_reader("0\r\n")
42
- assert_raises(EOFError) { cr.readpartial(8192) }
43
- end
44
-
45
- def test_eof2
46
- cr = bin_reader("0\r\n\r\n")
47
- assert_raises(EOFError) { cr.readpartial(8192) }
48
- end
49
-
50
- def test_readpartial1
51
- cr = bin_reader("4\r\nasdf\r\n0\r\n")
52
- assert_equal 'asdf', cr.readpartial(8192)
53
- assert_raises(EOFError) { cr.readpartial(8192) }
54
- end
55
-
56
- def test_dd
57
- cr = bin_reader("6\r\nhello\n\r\n")
58
- tmp = Tempfile.new('test_dd')
59
- tmp.sync = true
60
-
61
- pid = fork {
62
- crd, cwr = IO.pipe
63
- crd.binmode
64
- cwr.binmode
65
- crd.sync = cwr.sync = true
66
-
67
- pid = fork {
68
- STDOUT.reopen(cwr)
69
- crd.close
70
- cwr.close
71
- exec('dd', 'if=/dev/urandom', 'bs=93390', 'count=16')
72
- }
73
- cwr.close
74
- begin
75
- buf = crd.readpartial(16384)
76
- tmp.write(buf)
77
- @wr.write("#{'%x' % buf.size}\r\n#{buf}\r\n")
78
- rescue EOFError
79
- @wr.write("0\r\n\r\n")
80
- Process.waitpid(pid)
81
- exit 0
82
- end while true
83
- }
84
- assert_equal "hello\n", cr.readpartial(6)
85
- sha1 = Digest::SHA1.new
86
- buf = Unicorn::Z.dup
87
- begin
88
- cr.readpartial(16384, buf)
89
- sha1.update(buf)
90
- rescue EOFError
91
- break
92
- end while true
93
-
94
- assert_nothing_raised { Process.waitpid(pid) }
95
- sha1_file = Digest::SHA1.new
96
- File.open(tmp.path, 'rb') { |fp|
97
- while fp.read(16384, buf)
98
- sha1_file.update(buf)
99
- end
100
- }
101
- assert_equal sha1_file.hexdigest, sha1.hexdigest
102
- end
103
-
104
- def test_trailer
105
- @env['HTTP_TRAILER'] = 'Content-MD5'
106
- pid = fork { @wr.syswrite("Content-MD5: asdf\r\n") }
107
- cr = bin_reader("8\r\nasdfasdf\r\n8\r\nasdfasdf\r\n0\r\n")
108
- assert_equal 'asdfasdf', cr.readpartial(4096)
109
- assert_equal 'asdfasdf', cr.readpartial(4096)
110
- assert_raises(EOFError) { cr.readpartial(4096) }
111
- pid, status = Process.waitpid2(pid)
112
- assert status.success?
113
- assert_equal 'asdf', @env['HTTP_CONTENT_MD5']
114
- end
115
-
116
- private
117
-
118
- def bin_reader(buf)
119
- buf.force_encoding(Encoding::BINARY) if buf.respond_to?(:force_encoding)
120
- Unicorn::ChunkedReader.new(@env, @rd, buf)
121
- end
122
-
123
- end
@@ -1,52 +0,0 @@
1
- require 'test/unit'
2
- require 'unicorn'
3
- require 'unicorn_http'
4
- require 'unicorn/trailer_parser'
5
-
6
- class TestTrailerParser < Test::Unit::TestCase
7
-
8
- def test_basic
9
- tp = Unicorn::TrailerParser.new('Content-MD5')
10
- env = {}
11
- assert ! tp.execute!(env, "Content-MD5: asdf")
12
- assert env.empty?
13
- assert tp.execute!(env, "Content-MD5: asdf\r\n")
14
- assert_equal 'asdf', env['HTTP_CONTENT_MD5']
15
- assert_equal 1, env.size
16
- end
17
-
18
- def test_invalid_trailer
19
- tp = Unicorn::TrailerParser.new('Content-MD5')
20
- env = {}
21
- assert_raises(Unicorn::HttpParserError) {
22
- tp.execute!(env, "Content-MD: asdf\r\n")
23
- }
24
- assert env.empty?
25
- end
26
-
27
- def test_multiple_trailer
28
- tp = Unicorn::TrailerParser.new('Foo,Bar')
29
- env = {}
30
- buf = "Bar: a\r\nFoo: b\r\n"
31
- assert tp.execute!(env, buf)
32
- assert_equal 'a', env['HTTP_BAR']
33
- assert_equal 'b', env['HTTP_FOO']
34
- end
35
-
36
- def test_too_big_key
37
- tp = Unicorn::TrailerParser.new('Foo,Bar')
38
- env = {}
39
- buf = "Bar#{'a' * 1024}: a\r\nFoo: b\r\n"
40
- assert_raises(Unicorn::HttpParserError) { tp.execute!(env, buf) }
41
- assert env.empty?
42
- end
43
-
44
- def test_too_big_value
45
- tp = Unicorn::TrailerParser.new('Foo,Bar')
46
- env = {}
47
- buf = "Bar: #{'a' * (1024 * 1024)}: a\r\nFoo: b\r\n"
48
- assert_raises(Unicorn::HttpParserError) { tp.execute!(env, buf) }
49
- assert env.empty?
50
- end
51
-
52
- end