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
@@ -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