unicorn 4.8.3 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +5 -5
  2. data/.document +0 -1
  3. data/.gitattributes +5 -0
  4. data/.gitignore +2 -2
  5. data/.manifest +15 -19
  6. data/.olddoc.yml +25 -0
  7. data/Application_Timeouts +7 -7
  8. data/CONTRIBUTORS +6 -2
  9. data/DESIGN +2 -4
  10. data/Documentation/.gitignore +1 -3
  11. data/Documentation/unicorn.1 +222 -0
  12. data/Documentation/unicorn_rails.1 +207 -0
  13. data/FAQ +23 -6
  14. data/GIT-VERSION-FILE +1 -1
  15. data/GIT-VERSION-GEN +1 -1
  16. data/GNUmakefile +138 -92
  17. data/HACKING +5 -30
  18. data/ISSUES +73 -36
  19. data/KNOWN_ISSUES +18 -18
  20. data/LATEST +16 -22
  21. data/LICENSE +2 -2
  22. data/Links +24 -22
  23. data/NEWS +771 -0
  24. data/PHILOSOPHY +0 -6
  25. data/README +48 -42
  26. data/Rakefile +0 -44
  27. data/SIGNALS +2 -2
  28. data/Sandbox +11 -10
  29. data/TODO +0 -2
  30. data/TUNING +30 -9
  31. data/archive/slrnpull.conf +1 -1
  32. data/bin/unicorn +4 -2
  33. data/bin/unicorn_rails +3 -3
  34. data/examples/big_app_gc.rb +1 -1
  35. data/examples/init.sh +36 -8
  36. data/examples/logrotate.conf +17 -2
  37. data/examples/nginx.conf +14 -14
  38. data/examples/unicorn.conf.minimal.rb +2 -2
  39. data/examples/unicorn.conf.rb +14 -6
  40. data/examples/unicorn.socket +11 -0
  41. data/examples/unicorn@.service +40 -0
  42. data/ext/unicorn_http/c_util.h +5 -13
  43. data/ext/unicorn_http/common_field_optimization.h +22 -5
  44. data/ext/unicorn_http/epollexclusive.h +124 -0
  45. data/ext/unicorn_http/ext_help.h +0 -44
  46. data/ext/unicorn_http/extconf.rb +32 -5
  47. data/ext/unicorn_http/global_variables.h +2 -2
  48. data/ext/unicorn_http/httpdate.c +3 -2
  49. data/ext/unicorn_http/unicorn_http.c +926 -638
  50. data/ext/unicorn_http/unicorn_http.rl +159 -170
  51. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  52. data/lib/unicorn/configurator.rb +110 -46
  53. data/lib/unicorn/const.rb +2 -25
  54. data/lib/unicorn/http_request.rb +110 -31
  55. data/lib/unicorn/http_response.rb +17 -31
  56. data/lib/unicorn/http_server.rb +259 -179
  57. data/lib/unicorn/launcher.rb +1 -1
  58. data/lib/unicorn/oob_gc.rb +6 -6
  59. data/lib/unicorn/select_waiter.rb +6 -0
  60. data/lib/unicorn/socket_helper.rb +58 -78
  61. data/lib/unicorn/stream_input.rb +8 -7
  62. data/lib/unicorn/tee_input.rb +16 -11
  63. data/lib/unicorn/tmpio.rb +10 -6
  64. data/lib/unicorn/util.rb +5 -4
  65. data/lib/unicorn/version.rb +1 -1
  66. data/lib/unicorn/worker.rb +36 -23
  67. data/lib/unicorn.rb +64 -44
  68. data/man/man1/unicorn.1 +124 -112
  69. data/man/man1/unicorn_rails.1 +106 -107
  70. data/t/GNUmakefile +3 -76
  71. data/t/README +4 -4
  72. data/t/t0002-parser-error.sh +3 -3
  73. data/t/t0011-active-unix-socket.sh +1 -1
  74. data/t/t0012-reload-empty-config.sh +2 -1
  75. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  76. data/t/t0301.ru +13 -0
  77. data/t/test-lib.sh +4 -3
  78. data/test/benchmark/README +14 -4
  79. data/test/benchmark/ddstream.ru +50 -0
  80. data/test/benchmark/readinput.ru +40 -0
  81. data/test/benchmark/uconnect.perl +66 -0
  82. data/test/exec/test_exec.rb +74 -20
  83. data/test/test_helper.rb +42 -33
  84. data/test/unit/test_ccc.rb +91 -0
  85. data/test/unit/test_droplet.rb +1 -1
  86. data/test/unit/test_http_parser.rb +49 -19
  87. data/test/unit/test_http_parser_ng.rb +98 -115
  88. data/test/unit/test_request.rb +11 -11
  89. data/test/unit/test_response.rb +31 -19
  90. data/test/unit/test_server.rb +89 -15
  91. data/test/unit/test_signals.rb +9 -9
  92. data/test/unit/test_socket_helper.rb +16 -12
  93. data/test/unit/test_tee_input.rb +10 -0
  94. data/test/unit/test_upload.rb +10 -15
  95. data/test/unit/test_util.rb +32 -6
  96. data/test/unit/test_waiter.rb +34 -0
  97. data/unicorn.gemspec +28 -23
  98. data/unicorn_1 +1 -0
  99. data/unicorn_rails_1 +1 -0
  100. metadata +37 -58
  101. data/.wrongdoc.yml +0 -11
  102. data/ChangeLog +0 -5027
  103. data/Documentation/GNUmakefile +0 -30
  104. data/Documentation/unicorn.1.txt +0 -178
  105. data/Documentation/unicorn_rails.1.txt +0 -175
  106. data/examples/git.ru +0 -13
  107. data/lib/unicorn/app/exec_cgi.rb +0 -154
  108. data/lib/unicorn/app/inetd.rb +0 -109
  109. data/lib/unicorn/ssl_client.rb +0 -11
  110. data/lib/unicorn/ssl_configurator.rb +0 -104
  111. data/lib/unicorn/ssl_server.rb +0 -42
  112. data/local.mk.sample +0 -59
  113. data/script/isolate_for_tests +0 -31
  114. data/t/hijack.ru +0 -42
  115. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  116. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  117. data/t/t0200-rack-hijack.sh +0 -27
  118. data/test/unit/test_http_parser_xftrust.rb +0 -38
  119. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- require 'test/test_helper'
3
+ require './test/test_helper'
4
4
  require 'digest/md5'
5
5
 
6
6
  include Unicorn
@@ -8,10 +8,29 @@ include Unicorn
8
8
  class HttpParserNgTest < Test::Unit::TestCase
9
9
 
10
10
  def setup
11
- HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT
12
11
  @parser = HttpParser.new
13
12
  end
14
13
 
14
+ # RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
15
+ # but "chunked" must be last if used
16
+ def test_is_chunked
17
+ [ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
18
+ assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
19
+ end
20
+ [ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
21
+ assert HttpParser.is_chunked?(x)
22
+ end
23
+ [ 'gzip', 'xhunked', 'xchunked' ].each do |x|
24
+ assert !HttpParser.is_chunked?(x)
25
+ end
26
+ end
27
+
28
+ def test_parser_max_len
29
+ assert_raises(RangeError) do
30
+ HttpParser.max_header_len = 0xffffffff + 1
31
+ end
32
+ end
33
+
15
34
  def test_next_clear
16
35
  r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
17
36
  @parser.buf << r
@@ -29,23 +48,15 @@ class HttpParserNgTest < Test::Unit::TestCase
29
48
  assert_equal false, @parser.response_start_sent
30
49
  end
31
50
 
32
- def test_keepalive_requests_default_constant
33
- assert_kind_of Integer, HttpParser::KEEPALIVE_REQUESTS_DEFAULT
34
- assert HttpParser::KEEPALIVE_REQUESTS_DEFAULT >= 0
35
- end
36
-
37
- def test_keepalive_requests_setting
38
- HttpParser.keepalive_requests = 0
39
- assert_equal 0, HttpParser.keepalive_requests
40
- HttpParser.keepalive_requests = nil
41
- assert HttpParser.keepalive_requests >= 0xffffffff
42
- HttpParser.keepalive_requests = 1
43
- assert_equal 1, HttpParser.keepalive_requests
44
- HttpParser.keepalive_requests = 666
45
- assert_equal 666, HttpParser.keepalive_requests
46
-
47
- assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
48
- assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
51
+ def test_response_start_sent
52
+ assert_equal false, @parser.response_start_sent, "default is false"
53
+ @parser.response_start_sent = true
54
+ assert_equal true, @parser.response_start_sent
55
+ @parser.response_start_sent = false
56
+ assert_equal false, @parser.response_start_sent
57
+ @parser.response_start_sent = true
58
+ @parser.clear
59
+ assert_equal false, @parser.response_start_sent
49
60
  end
50
61
 
51
62
  def test_connection_TE
@@ -71,41 +82,11 @@ class HttpParserNgTest < Test::Unit::TestCase
71
82
  "REQUEST_METHOD" => "GET",
72
83
  "QUERY_STRING" => ""
73
84
  }.freeze
74
- HttpParser::KEEPALIVE_REQUESTS_DEFAULT.times do |nr|
85
+ 100.times do |nr|
75
86
  @parser.buf << req
76
87
  assert_equal expect, @parser.parse
77
88
  assert @parser.next?
78
89
  end
79
- @parser.buf << req
80
- assert_equal expect, @parser.parse
81
- assert ! @parser.next?
82
- end
83
-
84
- def test_fewer_keepalive_requests_with_next?
85
- HttpParser.keepalive_requests = 5
86
- @parser = HttpParser.new
87
- req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
88
- expect = {
89
- "SERVER_NAME" => "example.com",
90
- "HTTP_HOST" => "example.com",
91
- "rack.url_scheme" => "http",
92
- "REQUEST_PATH" => "/",
93
- "SERVER_PROTOCOL" => "HTTP/1.1",
94
- "PATH_INFO" => "/",
95
- "HTTP_VERSION" => "HTTP/1.1",
96
- "REQUEST_URI" => "/",
97
- "SERVER_PORT" => "80",
98
- "REQUEST_METHOD" => "GET",
99
- "QUERY_STRING" => ""
100
- }.freeze
101
- 5.times do |nr|
102
- @parser.buf << req
103
- assert_equal expect, @parser.parse
104
- assert @parser.next?
105
- end
106
- @parser.buf << req
107
- assert_equal expect, @parser.parse
108
- assert ! @parser.next?
109
90
  end
110
91
 
111
92
  def test_default_keepalive_is_off
@@ -599,6 +580,73 @@ class HttpParserNgTest < Test::Unit::TestCase
599
580
  end
600
581
  end
601
582
 
583
+ def test_duplicate_content_length
584
+ str = "PUT / HTTP/1.1\r\n" \
585
+ "Content-Length: 1\r\n" \
586
+ "Content-Length: 9\r\n" \
587
+ "\r\n"
588
+ assert_raises(HttpParserError) { @parser.headers({}, str) }
589
+ end
590
+
591
+ def test_chunked_overrides_content_length
592
+ order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
593
+ %w(a b).each do |x|
594
+ str = "PUT /#{x} HTTP/1.1\r\n" \
595
+ "#{order.join("\r\n")}" \
596
+ "\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
597
+ order.reverse!
598
+ env = @parser.headers({}, str)
599
+ assert_nil @parser.content_length
600
+ assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
601
+ assert_equal '666', env['CONTENT_LENGTH'],
602
+ 'Content-Length logged so the app can log a possible client bug/attack'
603
+ @parser.filter_body(dst = '', str)
604
+ assert_equal 'helloworld', dst
605
+ @parser.parse # handle the non-existent trailer
606
+ assert @parser.next?
607
+ end
608
+ end
609
+
610
+ def test_chunked_order_good
611
+ str = "PUT /x HTTP/1.1\r\n" \
612
+ "Transfer-Encoding: gzip\r\n" \
613
+ "Transfer-Encoding: chunked\r\n" \
614
+ "\r\n"
615
+ env = @parser.headers({}, str)
616
+ assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
617
+ assert_nil @parser.content_length
618
+
619
+ @parser.clear
620
+ str = "PUT /x HTTP/1.1\r\n" \
621
+ "Transfer-Encoding: gzip, chunked\r\n" \
622
+ "\r\n"
623
+ env = @parser.headers({}, str)
624
+ assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
625
+ assert_nil @parser.content_length
626
+ end
627
+
628
+ def test_chunked_order_bad
629
+ str = "PUT /x HTTP/1.1\r\n" \
630
+ "Transfer-Encoding: chunked\r\n" \
631
+ "Transfer-Encoding: gzip\r\n" \
632
+ "\r\n"
633
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
634
+ end
635
+
636
+ def test_double_chunked
637
+ str = "PUT /x HTTP/1.1\r\n" \
638
+ "Transfer-Encoding: chunked\r\n" \
639
+ "Transfer-Encoding: chunked\r\n" \
640
+ "\r\n"
641
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
642
+
643
+ @parser.clear
644
+ str = "PUT /x HTTP/1.1\r\n" \
645
+ "Transfer-Encoding: chunked,chunked\r\n" \
646
+ "\r\n"
647
+ assert_raise(HttpParserError) { @parser.headers({}, str) }
648
+ end
649
+
602
650
  def test_backtrace_is_empty
603
651
  begin
604
652
  @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
@@ -663,69 +711,4 @@ class HttpParserNgTest < Test::Unit::TestCase
663
711
  assert_equal expect, env2
664
712
  assert_equal "", @parser.buf
665
713
  end
666
-
667
- def test_keepalive_requests_disabled
668
- req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
669
- expect = {
670
- "SERVER_NAME" => "example.com",
671
- "HTTP_HOST" => "example.com",
672
- "rack.url_scheme" => "http",
673
- "REQUEST_PATH" => "/",
674
- "SERVER_PROTOCOL" => "HTTP/1.1",
675
- "PATH_INFO" => "/",
676
- "HTTP_VERSION" => "HTTP/1.1",
677
- "REQUEST_URI" => "/",
678
- "SERVER_PORT" => "80",
679
- "REQUEST_METHOD" => "GET",
680
- "QUERY_STRING" => ""
681
- }.freeze
682
- HttpParser.keepalive_requests = 0
683
- @parser = HttpParser.new
684
- @parser.buf << req
685
- assert_equal expect, @parser.parse
686
- assert ! @parser.next?
687
- end
688
-
689
- def test_chunk_only
690
- tmp = ""
691
- assert_equal @parser, @parser.dechunk!
692
- assert_nil @parser.filter_body(tmp, "6\r\n")
693
- assert_equal "", tmp
694
- assert_nil @parser.filter_body(tmp, "abcdef")
695
- assert_equal "abcdef", tmp
696
- assert_nil @parser.filter_body(tmp, "\r\n")
697
- assert_equal "", tmp
698
- src = "0\r\n\r\n"
699
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
700
- assert_equal "", tmp
701
- end
702
-
703
- def test_chunk_only_bad_align
704
- tmp = ""
705
- assert_equal @parser, @parser.dechunk!
706
- assert_nil @parser.filter_body(tmp, "6\r\na")
707
- assert_equal "a", tmp
708
- assert_nil @parser.filter_body(tmp, "bcde")
709
- assert_equal "bcde", tmp
710
- assert_nil @parser.filter_body(tmp, "f\r")
711
- assert_equal "f", tmp
712
- src = "\n0\r\n\r\n"
713
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
714
- assert_equal "", tmp
715
- end
716
-
717
- def test_chunk_only_reset_ok
718
- tmp = ""
719
- assert_equal @parser, @parser.dechunk!
720
- src = "1\r\na\r\n0\r\n\r\n"
721
- assert_nil @parser.filter_body(tmp, src)
722
- assert_equal "a", tmp
723
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
724
-
725
- assert_equal @parser, @parser.dechunk!
726
- src = "0\r\n\r\n"
727
- assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
728
- assert_equal "", tmp
729
- assert_equal src, @parser.filter_body(tmp, src)
730
- end
731
714
  end
@@ -4,7 +4,7 @@
4
4
  # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
5
  # the GPLv2+ (GPLv3+ preferred)
6
6
 
7
- require 'test/test_helper'
7
+ require './test/test_helper'
8
8
 
9
9
  include Unicorn
10
10
 
@@ -34,7 +34,7 @@ class RequestTest < Test::Unit::TestCase
34
34
  assert_equal '', env['REQUEST_PATH']
35
35
  assert_equal '', env['PATH_INFO']
36
36
  assert_equal '*', env['REQUEST_URI']
37
- res = @lint.call(env)
37
+ assert_kind_of Array, @lint.call(env)
38
38
  end
39
39
 
40
40
  def test_absolute_uri_with_query
@@ -44,7 +44,7 @@ class RequestTest < Test::Unit::TestCase
44
44
  assert_equal '/x', env['REQUEST_PATH']
45
45
  assert_equal '/x', env['PATH_INFO']
46
46
  assert_equal 'y=z', env['QUERY_STRING']
47
- res = @lint.call(env)
47
+ assert_kind_of Array, @lint.call(env)
48
48
  end
49
49
 
50
50
  def test_absolute_uri_with_fragment
@@ -55,7 +55,7 @@ class RequestTest < Test::Unit::TestCase
55
55
  assert_equal '/x', env['PATH_INFO']
56
56
  assert_equal '', env['QUERY_STRING']
57
57
  assert_equal 'frag', env['FRAGMENT']
58
- res = @lint.call(env)
58
+ assert_kind_of Array, @lint.call(env)
59
59
  end
60
60
 
61
61
  def test_absolute_uri_with_query_and_fragment
@@ -66,7 +66,7 @@ class RequestTest < Test::Unit::TestCase
66
66
  assert_equal '/x', env['PATH_INFO']
67
67
  assert_equal 'a=b', env['QUERY_STRING']
68
68
  assert_equal 'frag', env['FRAGMENT']
69
- res = @lint.call(env)
69
+ assert_kind_of Array, @lint.call(env)
70
70
  end
71
71
 
72
72
  def test_absolute_uri_unsupported_schemes
@@ -83,7 +83,7 @@ class RequestTest < Test::Unit::TestCase
83
83
  "Host: foo\r\n\r\n")
84
84
  env = @request.read(client)
85
85
  assert_equal "https", env['rack.url_scheme']
86
- res = @lint.call(env)
86
+ assert_kind_of Array, @lint.call(env)
87
87
  end
88
88
 
89
89
  def test_x_forwarded_proto_http
@@ -92,7 +92,7 @@ class RequestTest < Test::Unit::TestCase
92
92
  "Host: foo\r\n\r\n")
93
93
  env = @request.read(client)
94
94
  assert_equal "http", env['rack.url_scheme']
95
- res = @lint.call(env)
95
+ assert_kind_of Array, @lint.call(env)
96
96
  end
97
97
 
98
98
  def test_x_forwarded_proto_invalid
@@ -101,7 +101,7 @@ class RequestTest < Test::Unit::TestCase
101
101
  "Host: foo\r\n\r\n")
102
102
  env = @request.read(client)
103
103
  assert_equal "http", env['rack.url_scheme']
104
- res = @lint.call(env)
104
+ assert_kind_of Array, @lint.call(env)
105
105
  end
106
106
 
107
107
  def test_rack_lint_get
@@ -109,7 +109,7 @@ class RequestTest < Test::Unit::TestCase
109
109
  env = @request.read(client)
110
110
  assert_equal "http", env['rack.url_scheme']
111
111
  assert_equal '127.0.0.1', env['REMOTE_ADDR']
112
- res = @lint.call(env)
112
+ assert_kind_of Array, @lint.call(env)
113
113
  end
114
114
 
115
115
  def test_no_content_stringio
@@ -143,7 +143,7 @@ class RequestTest < Test::Unit::TestCase
143
143
  "abcde")
144
144
  env = @request.read(client)
145
145
  assert ! env.include?(:http_body)
146
- res = @lint.call(env)
146
+ assert_kind_of Array, @lint.call(env)
147
147
  end
148
148
 
149
149
  def test_rack_lint_big_put
@@ -177,6 +177,6 @@ class RequestTest < Test::Unit::TestCase
177
177
  }
178
178
  assert_nil env['rack.input'].read(bs)
179
179
  env['rack.input'].rewind
180
- res = @lint.call(env)
180
+ assert_kind_of Array, @lint.call(env)
181
181
  end
182
182
  end
@@ -1,13 +1,13 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # Copyright (c) 2005 Zed A. Shaw
3
+ # Copyright (c) 2005 Zed A. Shaw
4
4
  # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
5
  # the GPLv2+ (GPLv3+ preferred)
6
6
  #
7
- # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # Additional work donated by contributors. See git history
8
8
  # for more information.
9
9
 
10
- require 'test/test_helper'
10
+ require './test/test_helper'
11
11
  require 'time'
12
12
 
13
13
  include Unicorn
@@ -33,12 +33,20 @@ class ResponseTest < Test::Unit::TestCase
33
33
  assert out.length > 0, "output didn't have data"
34
34
  end
35
35
 
36
+ # ref: <CAO47=rJa=zRcLn_Xm4v2cHPr6c0UswaFC_omYFEH+baSxHOWKQ@mail.gmail.com>
37
+ def test_response_header_broken_nil
38
+ out = StringIO.new
39
+ http_response_write(out, 200, {"Nil" => nil}, %w(hysterical raisin))
40
+ assert ! out.closed?
41
+
42
+ assert_match %r{^Nil: \r\n}sm, out.string, 'nil accepted'
43
+ end
44
+
36
45
  def test_response_string_status
37
46
  out = StringIO.new
38
47
  http_response_write(out,'200', {}, [])
39
48
  assert ! out.closed?
40
49
  assert out.length > 0, "output didn't have data"
41
- assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
42
50
  end
43
51
 
44
52
  def test_response_200
@@ -71,18 +79,6 @@ class ResponseTest < Test::Unit::TestCase
71
79
  out = StringIO.new
72
80
  http_response_write(out,200, {"X-Whatever" => "stuff"}, [])
73
81
  assert ! out.closed?
74
- assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
75
- end
76
-
77
- def test_body_closed
78
- expect_body = %w(1 2 3 4).join("\n")
79
- body = StringIO.new(expect_body)
80
- body.rewind
81
- out = StringIO.new
82
- http_response_write(out,200, {}, body)
83
- assert ! out.closed?
84
- assert body.closed?
85
- assert_match(expect_body, out.string.split(/\r\n/).last)
86
82
  end
87
83
 
88
84
  def test_unknown_status_pass_through
@@ -91,9 +87,25 @@ class ResponseTest < Test::Unit::TestCase
91
87
  assert ! out.closed?
92
88
  headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
93
89
  assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
94
- status = headers.grep(/\AStatus:/i).first
95
- assert status
96
- assert_equal "Status: 666 I AM THE BEAST", status
97
90
  end
98
91
 
92
+ def test_modified_rack_http_status_codes_late
93
+ r, w = IO.pipe
94
+ pid = fork do
95
+ r.close
96
+ # Users may want to globally override the status text associated
97
+ # with an HTTP status code in their app.
98
+ Rack::Utils::HTTP_STATUS_CODES[200] = "HI"
99
+ http_response_write(w, 200, {}, [])
100
+ w.close
101
+ end
102
+ w.close
103
+ assert_equal "HTTP/1.1 200 HI\r\n", r.gets
104
+ r.read # just drain the pipe
105
+ pid, status = Process.waitpid2(pid)
106
+ assert status.success?, status.inspect
107
+ ensure
108
+ r.close
109
+ w.close unless w.closed?
110
+ end
99
111
  end
@@ -1,13 +1,13 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # Copyright (c) 2005 Zed A. Shaw
3
+ # Copyright (c) 2005 Zed A. Shaw
4
4
  # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
5
  # the GPLv2+ (GPLv3+ preferred)
6
6
  #
7
- # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # Additional work donated by contributors. See git history
8
8
  # for more information.
9
9
 
10
- require 'test/test_helper'
10
+ require './test/test_helper'
11
11
 
12
12
  include Unicorn
13
13
 
@@ -17,12 +17,40 @@ class TestHandler
17
17
  while env['rack.input'].read(4096)
18
18
  end
19
19
  [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
20
- rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
21
- $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
22
- raise e
20
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
21
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
22
+ raise e
23
23
  end
24
24
  end
25
25
 
26
+ class TestEarlyHintsHandler
27
+ def call(env)
28
+ while env['rack.input'].read(4096)
29
+ end
30
+ env['rack.early_hints'].call(
31
+ "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload"
32
+ )
33
+ [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
34
+ end
35
+ end
36
+
37
+ class TestRackAfterReply
38
+ def initialize
39
+ @called = false
40
+ end
41
+
42
+ def call(env)
43
+ while env['rack.input'].read(4096)
44
+ end
45
+
46
+ env["rack.after_reply"] << -> { @called = true }
47
+
48
+ [200, { 'Content-Type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
49
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
50
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
51
+ raise e
52
+ end
53
+ end
26
54
 
27
55
  class WebServerTest < Test::Unit::TestCase
28
56
 
@@ -80,8 +108,54 @@ class WebServerTest < Test::Unit::TestCase
80
108
  loader_pid = tmp.sysread(4096).to_i
81
109
  assert_equal $$, loader_pid
82
110
  assert worker_pid != loader_pid
83
- ensure
84
- tmp.close!
111
+ ensure
112
+ tmp.close!
113
+ end
114
+
115
+ def test_early_hints
116
+ teardown
117
+ redirect_test_io do
118
+ @server = HttpServer.new(TestEarlyHintsHandler.new,
119
+ :listeners => [ "127.0.0.1:#@port"],
120
+ :early_hints => true)
121
+ @server.start
122
+ end
123
+
124
+ sock = tcp_socket('127.0.0.1', @port)
125
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
126
+
127
+ responses = sock.read(4096)
128
+ assert_match %r{\AHTTP/1.[01] 103\b}, responses
129
+ assert_match %r{^Link: </style\.css>}, responses
130
+ assert_match %r{^Link: </script\.js>}, responses
131
+
132
+ assert_match %r{^HTTP/1.[01] 200\b}, responses
133
+ end
134
+
135
+ def test_after_reply
136
+ teardown
137
+
138
+ redirect_test_io do
139
+ @server = HttpServer.new(TestRackAfterReply.new,
140
+ :listeners => [ "127.0.0.1:#@port"])
141
+ @server.start
142
+ end
143
+
144
+ sock = tcp_socket('127.0.0.1', @port)
145
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
146
+
147
+ responses = sock.read(4096)
148
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
149
+ assert_match %r{^after_reply_called: false}, responses
150
+
151
+ sock = tcp_socket('127.0.0.1', @port)
152
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
153
+
154
+ responses = sock.read(4096)
155
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
156
+ assert_match %r{^after_reply_called: true}, responses
157
+
158
+ sock.close
85
159
  end
86
160
 
87
161
  def test_broken_app
@@ -92,7 +166,7 @@ class WebServerTest < Test::Unit::TestCase
92
166
  @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
93
167
  @server.start
94
168
  end
95
- sock = TCPSocket.new('127.0.0.1', @port)
169
+ sock = tcp_socket('127.0.0.1', @port)
96
170
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
97
171
  assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
98
172
  assert_nil sock.close
@@ -105,7 +179,7 @@ class WebServerTest < Test::Unit::TestCase
105
179
 
106
180
  def test_client_shutdown_writes
107
181
  bs = 15609315 * rand
108
- sock = TCPSocket.new('127.0.0.1', @port)
182
+ sock = tcp_socket('127.0.0.1', @port)
109
183
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
110
184
  sock.syswrite("Host: example.com\r\n")
111
185
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -132,7 +206,7 @@ class WebServerTest < Test::Unit::TestCase
132
206
 
133
207
  def test_client_shutdown_write_truncates
134
208
  bs = 15609315 * rand
135
- sock = TCPSocket.new('127.0.0.1', @port)
209
+ sock = tcp_socket('127.0.0.1', @port)
136
210
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
137
211
  sock.syswrite("Host: example.com\r\n")
138
212
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -158,7 +232,7 @@ class WebServerTest < Test::Unit::TestCase
158
232
 
159
233
  def test_client_malformed_body
160
234
  bs = 15653984
161
- sock = TCPSocket.new('127.0.0.1', @port)
235
+ sock = tcp_socket('127.0.0.1', @port)
162
236
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
163
237
  sock.syswrite("Host: example.com\r\n")
164
238
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -180,7 +254,7 @@ class WebServerTest < Test::Unit::TestCase
180
254
 
181
255
  def do_test(string, chunk, close_after=nil, shutdown_delay=0)
182
256
  # Do not use instance variables here, because it needs to be thread safe
183
- socket = TCPSocket.new("127.0.0.1", @port);
257
+ socket = tcp_socket("127.0.0.1", @port);
184
258
  request = StringIO.new(string)
185
259
  chunks_out = 0
186
260
 
@@ -225,14 +299,14 @@ class WebServerTest < Test::Unit::TestCase
225
299
  end
226
300
 
227
301
  def test_bad_client_400
228
- sock = TCPSocket.new('127.0.0.1', @port)
302
+ sock = tcp_socket('127.0.0.1', @port)
229
303
  sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
230
304
  assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
231
305
  assert_nil sock.close
232
306
  end
233
307
 
234
308
  def test_http_0_9
235
- sock = TCPSocket.new('127.0.0.1', @port)
309
+ sock = tcp_socket('127.0.0.1', @port)
236
310
  sock.syswrite("GET /hello\r\n")
237
311
  assert_match 'hello!\n', sock.sysread(4096)
238
312
  assert_nil sock.close
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # Ensure we stay sane in the face of signals being sent to us
8
8
 
9
- require 'test/test_helper'
9
+ require './test/test_helper'
10
10
 
11
11
  include Unicorn
12
12
 
@@ -52,7 +52,7 @@ class SignalsTest < Test::Unit::TestCase
52
52
  redirect_test_io { HttpServer.new(app, opts).start.join }
53
53
  }
54
54
  wait_workers_ready("test_stderr.#{pid}.log", 1)
55
- sock = TCPSocket.new('127.0.0.1', @port)
55
+ sock = tcp_socket('127.0.0.1', @port)
56
56
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
57
57
  buf = sock.readpartial(4096)
58
58
  assert_nil sock.close
@@ -79,7 +79,7 @@ class SignalsTest < Test::Unit::TestCase
79
79
  }
80
80
  wr.close
81
81
  wait_workers_ready("test_stderr.#{pid}.log", 1)
82
- sock = TCPSocket.new('127.0.0.1', @port)
82
+ sock = tcp_socket('127.0.0.1', @port)
83
83
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
84
84
  buf = rd.readpartial(1)
85
85
  wait_master_ready("test_stderr.#{pid}.log")
@@ -102,7 +102,7 @@ class SignalsTest < Test::Unit::TestCase
102
102
  }
103
103
  t0 = Time.now
104
104
  wait_workers_ready("test_stderr.#{pid}.log", 1)
105
- sock = TCPSocket.new('127.0.0.1', @port)
105
+ sock = tcp_socket('127.0.0.1', @port)
106
106
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
107
107
 
108
108
  buf = nil
@@ -114,8 +114,8 @@ class SignalsTest < Test::Unit::TestCase
114
114
  assert_nil buf
115
115
  assert diff > 1.0, "diff was #{diff.inspect}"
116
116
  assert diff < 60.0
117
- ensure
118
- Process.kill(:TERM, pid) rescue nil
117
+ ensure
118
+ Process.kill(:TERM, pid) rescue nil
119
119
  end
120
120
 
121
121
  def test_response_write
@@ -125,7 +125,7 @@ class SignalsTest < Test::Unit::TestCase
125
125
  }
126
126
  redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
127
127
  wait_workers_ready("test_stderr.#{$$}.log", 1)
128
- sock = TCPSocket.new('127.0.0.1', @port)
128
+ sock = tcp_socket('127.0.0.1', @port)
129
129
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
130
130
  buf = ''
131
131
  header_len = pid = nil
@@ -163,13 +163,13 @@ class SignalsTest < Test::Unit::TestCase
163
163
  redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
164
164
 
165
165
  wait_workers_ready("test_stderr.#{$$}.log", 1)
166
- sock = TCPSocket.new('127.0.0.1', @port)
166
+ sock = tcp_socket('127.0.0.1', @port)
167
167
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
168
168
  pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
169
169
  assert_nil sock.close
170
170
 
171
171
  assert pid > 0, "pid not positive: #{pid.inspect}"
172
- sock = TCPSocket.new('127.0.0.1', @port)
172
+ sock = tcp_socket('127.0.0.1', @port)
173
173
  sock.syswrite("PUT / HTTP/1.0\r\n")
174
174
  sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
175
175
  1000.times { Process.kill(:HUP, pid) }