unicorn 4.8.0 → 6.1.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 (123) hide show
  1. checksums.yaml +7 -0
  2. data/.document +0 -1
  3. data/.gitattributes +5 -0
  4. data/.gitignore +2 -2
  5. data/.manifest +17 -21
  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 +10 -32
  18. data/ISSUES +80 -19
  19. data/KNOWN_ISSUES +18 -18
  20. data/LATEST +23 -27
  21. data/LICENSE +2 -2
  22. data/Links +24 -22
  23. data/NEWS +823 -0
  24. data/PHILOSOPHY +0 -6
  25. data/README +49 -48
  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/.gitignore +3 -0
  32. data/archive/slrnpull.conf +4 -0
  33. data/bin/unicorn +4 -2
  34. data/bin/unicorn_rails +3 -3
  35. data/examples/big_app_gc.rb +1 -1
  36. data/examples/init.sh +36 -8
  37. data/examples/logrotate.conf +17 -2
  38. data/examples/nginx.conf +14 -14
  39. data/examples/unicorn.conf.minimal.rb +2 -2
  40. data/examples/unicorn.conf.rb +14 -6
  41. data/examples/unicorn.socket +11 -0
  42. data/examples/unicorn@.service +40 -0
  43. data/ext/unicorn_http/c_util.h +5 -13
  44. data/ext/unicorn_http/common_field_optimization.h +22 -5
  45. data/ext/unicorn_http/epollexclusive.h +124 -0
  46. data/ext/unicorn_http/ext_help.h +0 -44
  47. data/ext/unicorn_http/extconf.rb +32 -5
  48. data/ext/unicorn_http/global_variables.h +2 -2
  49. data/ext/unicorn_http/httpdate.c +3 -2
  50. data/ext/unicorn_http/unicorn_http.c +926 -638
  51. data/ext/unicorn_http/unicorn_http.rl +159 -170
  52. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  53. data/lib/unicorn/configurator.rb +110 -46
  54. data/lib/unicorn/const.rb +2 -25
  55. data/lib/unicorn/http_request.rb +110 -31
  56. data/lib/unicorn/http_response.rb +17 -31
  57. data/lib/unicorn/http_server.rb +272 -180
  58. data/lib/unicorn/launcher.rb +1 -1
  59. data/lib/unicorn/oob_gc.rb +16 -6
  60. data/lib/unicorn/select_waiter.rb +6 -0
  61. data/lib/unicorn/socket_helper.rb +58 -78
  62. data/lib/unicorn/stream_input.rb +8 -7
  63. data/lib/unicorn/tee_input.rb +16 -11
  64. data/lib/unicorn/tmpio.rb +10 -6
  65. data/lib/unicorn/util.rb +5 -4
  66. data/lib/unicorn/version.rb +1 -1
  67. data/lib/unicorn/worker.rb +36 -23
  68. data/lib/unicorn.rb +64 -44
  69. data/man/man1/unicorn.1 +124 -122
  70. data/man/man1/unicorn_rails.1 +113 -127
  71. data/t/.gitignore +0 -1
  72. data/t/GNUmakefile +3 -80
  73. data/t/README +4 -4
  74. data/t/t0002-parser-error.sh +3 -3
  75. data/t/t0011-active-unix-socket.sh +1 -1
  76. data/t/t0012-reload-empty-config.sh +2 -1
  77. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  78. data/t/t0301.ru +13 -0
  79. data/t/test-lib.sh +4 -3
  80. data/test/benchmark/README +14 -4
  81. data/test/benchmark/ddstream.ru +50 -0
  82. data/test/benchmark/readinput.ru +40 -0
  83. data/test/benchmark/uconnect.perl +66 -0
  84. data/test/exec/test_exec.rb +74 -20
  85. data/test/test_helper.rb +42 -33
  86. data/test/unit/test_ccc.rb +91 -0
  87. data/test/unit/test_droplet.rb +1 -1
  88. data/test/unit/test_http_parser.rb +49 -19
  89. data/test/unit/test_http_parser_ng.rb +98 -115
  90. data/test/unit/test_request.rb +11 -11
  91. data/test/unit/test_response.rb +31 -19
  92. data/test/unit/test_server.rb +89 -15
  93. data/test/unit/test_signals.rb +9 -9
  94. data/test/unit/test_socket_helper.rb +16 -12
  95. data/test/unit/test_tee_input.rb +10 -0
  96. data/test/unit/test_upload.rb +10 -15
  97. data/test/unit/test_util.rb +32 -6
  98. data/test/unit/test_waiter.rb +34 -0
  99. data/unicorn.gemspec +28 -23
  100. data/unicorn_1 +1 -0
  101. data/unicorn_rails_1 +1 -0
  102. metadata +68 -134
  103. data/.wrongdoc.yml +0 -10
  104. data/ChangeLog +0 -4861
  105. data/Documentation/GNUmakefile +0 -30
  106. data/Documentation/unicorn.1.txt +0 -178
  107. data/Documentation/unicorn_rails.1.txt +0 -175
  108. data/examples/git.ru +0 -13
  109. data/lib/unicorn/app/exec_cgi.rb +0 -154
  110. data/lib/unicorn/app/inetd.rb +0 -109
  111. data/lib/unicorn/ssl_client.rb +0 -11
  112. data/lib/unicorn/ssl_configurator.rb +0 -104
  113. data/lib/unicorn/ssl_server.rb +0 -42
  114. data/local.mk.sample +0 -59
  115. data/script/isolate_for_tests +0 -32
  116. data/t/hijack.ru +0 -42
  117. data/t/sslgen.sh +0 -71
  118. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  119. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  120. data/t/t0200-rack-hijack.sh +0 -27
  121. data/t/t0600-https-server-basic.sh +0 -48
  122. data/test/unit/test_http_parser_xftrust.rb +0 -38
  123. 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) }