unicorn 3.6.0 → 4.0.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 (58) hide show
  1. data/.document +1 -0
  2. data/.manifest +13 -0
  3. data/ChangeLog +783 -1
  4. data/DESIGN +0 -8
  5. data/Documentation/GNUmakefile +1 -1
  6. data/GIT-VERSION-FILE +1 -1
  7. data/GIT-VERSION-GEN +1 -1
  8. data/GNUmakefile +2 -2
  9. data/HACKING +11 -0
  10. data/KNOWN_ISSUES +2 -2
  11. data/LATEST +24 -24
  12. data/Links +53 -0
  13. data/NEWS +66 -0
  14. data/PHILOSOPHY +49 -49
  15. data/Sandbox +13 -4
  16. data/TODO +0 -2
  17. data/TUNING +31 -9
  18. data/bin/unicorn +2 -1
  19. data/bin/unicorn_rails +2 -1
  20. data/examples/big_app_gc.rb +2 -33
  21. data/examples/nginx.conf +17 -4
  22. data/ext/unicorn_http/ext_help.h +16 -0
  23. data/ext/unicorn_http/extconf.rb +1 -0
  24. data/ext/unicorn_http/global_variables.h +9 -3
  25. data/ext/unicorn_http/unicorn_http.c +357 -259
  26. data/ext/unicorn_http/unicorn_http.rl +148 -50
  27. data/lib/unicorn/configurator.rb +36 -8
  28. data/lib/unicorn/const.rb +5 -3
  29. data/lib/unicorn/http_request.rb +1 -3
  30. data/lib/unicorn/http_server.rb +82 -95
  31. data/lib/unicorn/oob_gc.rb +61 -50
  32. data/lib/unicorn/socket_helper.rb +23 -8
  33. data/lib/unicorn/worker.rb +45 -4
  34. data/lib/unicorn.rb +8 -6
  35. data/script/isolate_for_tests +4 -2
  36. data/t/broken-app.ru +12 -0
  37. data/t/heartbeat-timeout.ru +12 -0
  38. data/t/oob_gc.ru +21 -0
  39. data/t/oob_gc_path.ru +21 -0
  40. data/t/t0001-reload-bad-config.sh +1 -0
  41. data/t/t0002-parser-error.sh +64 -1
  42. data/t/t0004-heartbeat-timeout.sh +69 -0
  43. data/t/t0009-broken-app.sh +56 -0
  44. data/t/t0019-max_header_len.sh +49 -0
  45. data/t/t0020-at_exit-handler.sh +49 -0
  46. data/t/t9001-oob_gc.sh +47 -0
  47. data/t/t9002-oob_gc-path.sh +75 -0
  48. data/test/benchmark/stack.ru +8 -0
  49. data/test/unit/test_droplet.rb +28 -0
  50. data/test/unit/test_http_parser.rb +60 -4
  51. data/test/unit/test_http_parser_ng.rb +54 -0
  52. data/test/unit/test_response.rb +1 -1
  53. data/test/unit/test_server.rb +1 -1
  54. data/test/unit/test_signals.rb +1 -1
  55. data/test/unit/test_socket_helper.rb +8 -0
  56. data/test/unit/test_upload.rb +1 -1
  57. data/unicorn.gemspec +3 -2
  58. metadata +44 -16
@@ -0,0 +1,12 @@
1
+ use Rack::ContentLength
2
+ headers = { 'Content-Type' => 'text/plain' }
3
+ run lambda { |env|
4
+ case env['PATH_INFO']
5
+ when "/block-forever"
6
+ Process.kill(:STOP, $$)
7
+ sleep # in case STOP signal is not received in time
8
+ [ 500, headers, [ "Should never get here\n" ] ]
9
+ else
10
+ [ 200, headers, [ "#$$\n" ] ]
11
+ end
12
+ }
data/t/oob_gc.ru ADDED
@@ -0,0 +1,21 @@
1
+ #\-E none
2
+ require 'unicorn/oob_gc'
3
+ use Rack::ContentLength
4
+ use Rack::ContentType, "text/plain"
5
+ use Unicorn::OobGC
6
+ $gc_started = false
7
+
8
+ # Mock GC.start
9
+ def GC.start
10
+ ObjectSpace.each_object(BasicSocket) do |x|
11
+ next if Unicorn::HttpServer::LISTENERS.include?(x)
12
+ x.closed? or abort "not closed #{x}"
13
+ end
14
+ $gc_started = true
15
+ end
16
+ run lambda { |env|
17
+ if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"]
18
+ $gc_started = false
19
+ end
20
+ [ 200, {}, [ "#$gc_started\n" ] ]
21
+ }
data/t/oob_gc_path.ru ADDED
@@ -0,0 +1,21 @@
1
+ #\-E none
2
+ require 'unicorn/oob_gc'
3
+ use Rack::ContentLength
4
+ use Rack::ContentType, "text/plain"
5
+ use Unicorn::OobGC, 5, /BAD/
6
+ $gc_started = false
7
+
8
+ # Mock GC.start
9
+ def GC.start
10
+ ObjectSpace.each_object(BasicSocket) do |x|
11
+ next if Unicorn::HttpServer::LISTENERS.include?(x)
12
+ x.closed? or abort "not closed #{x}"
13
+ end
14
+ $gc_started = true
15
+ end
16
+ run lambda { |env|
17
+ if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"]
18
+ $gc_started = false
19
+ end
20
+ [ 200, {}, [ "#$gc_started\n" ] ]
21
+ }
@@ -34,6 +34,7 @@ t_begin "reload signal succeeds" && {
34
34
  done
35
35
 
36
36
  grep 'error reloading' $r_err >/dev/null
37
+ > $r_err
37
38
  }
38
39
 
39
40
  t_begin "hit with curl" && {
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
- t_plan 5 "parser error test"
3
+ t_plan 11 "parser error test"
4
4
 
5
5
  t_begin "setup and startup" && {
6
6
  unicorn_setup
@@ -24,6 +24,69 @@ t_begin "response should be a 400" && {
24
24
  grep -F 'HTTP/1.1 400 Bad Request' $tmp
25
25
  }
26
26
 
27
+ t_begin "send a huge Request URI (REQUEST_PATH > (12 * 1024))" && {
28
+ rm -f $tmp
29
+ cat $fifo > $tmp &
30
+ (
31
+ set -e
32
+ trap 'echo ok > $ok' EXIT
33
+ printf 'GET /'
34
+ for i in $(awk </dev/null 'BEGIN{for(i=0;i<1024;i++) print i}')
35
+ do
36
+ printf '0123456789ab'
37
+ done
38
+ printf ' HTTP/1.1\r\nHost: example.com\r\n\r\n'
39
+ ) | socat - TCP:$listen > $fifo || :
40
+ test xok = x$(cat $ok)
41
+ wait
42
+ }
43
+
44
+ t_begin "response should be a 414 (REQUEST_PATH)" && {
45
+ grep -F 'HTTP/1.1 414 Request-URI Too Long' $tmp
46
+ }
47
+
48
+ t_begin "send a huge Request URI (QUERY_STRING > (10 * 1024))" && {
49
+ rm -f $tmp
50
+ cat $fifo > $tmp &
51
+ (
52
+ set -e
53
+ trap 'echo ok > $ok' EXIT
54
+ printf 'GET /hello-world?a'
55
+ for i in $(awk </dev/null 'BEGIN{for(i=0;i<1024;i++) print i}')
56
+ do
57
+ printf '0123456789'
58
+ done
59
+ printf ' HTTP/1.1\r\nHost: example.com\r\n\r\n'
60
+ ) | socat - TCP:$listen > $fifo || :
61
+ test xok = x$(cat $ok)
62
+ wait
63
+ }
64
+
65
+ t_begin "response should be a 414 (QUERY_STRING)" && {
66
+ grep -F 'HTTP/1.1 414 Request-URI Too Long' $tmp
67
+ }
68
+
69
+ t_begin "send a huge Request URI (FRAGMENT > 1024)" && {
70
+ rm -f $tmp
71
+ cat $fifo > $tmp &
72
+ (
73
+ set -e
74
+ trap 'echo ok > $ok' EXIT
75
+ printf 'GET /hello-world#a'
76
+ for i in $(awk </dev/null 'BEGIN{for(i=0;i<64;i++) print i}')
77
+ do
78
+ printf '0123456789abcdef'
79
+ done
80
+ printf ' HTTP/1.1\r\nHost: example.com\r\n\r\n'
81
+ ) | socat - TCP:$listen > $fifo || :
82
+ test xok = x$(cat $ok)
83
+ wait
84
+ }
85
+
86
+ t_begin "response should be a 414 (FRAGMENT)" && {
87
+ grep -F 'HTTP/1.1 414 Request-URI Too Long' $tmp
88
+ }
89
+
27
90
  t_begin "server stderr should be clean" && check_stderr
28
91
 
29
92
  t_begin "term signal sent" && kill $unicorn_pid
@@ -0,0 +1,69 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+
4
+ t_plan 11 "heartbeat/timeout test"
5
+
6
+ t_begin "setup and startup" && {
7
+ unicorn_setup
8
+ echo timeout 3 >> $unicorn_config
9
+ echo preload_app true >> $unicorn_config
10
+ unicorn -D heartbeat-timeout.ru -c $unicorn_config
11
+ unicorn_wait_start
12
+ }
13
+
14
+ t_begin "read worker PID" && {
15
+ worker_pid=$(curl -sSf http://$listen/)
16
+ t_info "worker_pid=$worker_pid"
17
+ }
18
+
19
+ t_begin "sleep for a bit, ensure worker PID does not change" && {
20
+ sleep 4
21
+ test $(curl -sSf http://$listen/) -eq $worker_pid
22
+ }
23
+
24
+ t_begin "block the worker process to force it to die" && {
25
+ rm $ok
26
+ t0=$(date +%s)
27
+ err="$(curl -sSf http://$listen/block-forever 2>&1 || > $ok)"
28
+ t1=$(date +%s)
29
+ elapsed=$(($t1 - $t0))
30
+ t_info "elapsed=$elapsed err=$err"
31
+ test x"$err" != x"Should never get here"
32
+ test x"$err" != x"$worker_pid"
33
+ }
34
+
35
+ t_begin "ensure worker was killed" && {
36
+ test -e $ok
37
+ test 1 -eq $(grep timeout $r_err | grep killing | wc -l)
38
+ }
39
+
40
+ t_begin "ensure timeout took at least 3 seconds" && {
41
+ test $elapsed -ge 3
42
+ }
43
+
44
+ t_begin "we get a fresh new worker process" && {
45
+ new_worker_pid=$(curl -sSf http://$listen/)
46
+ test $new_worker_pid -ne $worker_pid
47
+ }
48
+
49
+ t_begin "truncate the server error log" && {
50
+ > $r_err
51
+ }
52
+
53
+ t_begin "SIGSTOP and SIGCONT on unicorn master does not kill worker" && {
54
+ kill -STOP $unicorn_pid
55
+ sleep 4
56
+ kill -CONT $unicorn_pid
57
+ sleep 2
58
+ test $new_worker_pid -eq $(curl -sSf http://$listen/)
59
+ }
60
+
61
+ t_begin "stop server" && {
62
+ kill -QUIT $unicorn_pid
63
+ }
64
+
65
+ t_begin "check stderr" && check_stderr
66
+
67
+ dbgcat r_err
68
+
69
+ t_done
@@ -0,0 +1,56 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+
4
+ t_plan 9 "graceful handling of broken apps"
5
+
6
+ t_begin "setup and start" && {
7
+ unicorn_setup
8
+ unicorn -E none -D broken-app.ru -c $unicorn_config
9
+ unicorn_wait_start
10
+ }
11
+
12
+ t_begin "normal response is alright" && {
13
+ test xOK = x"$(curl -sSf http://$listen/)"
14
+ }
15
+
16
+ t_begin "app raised exception" && {
17
+ curl -sSf http://$listen/raise 2> $tmp || :
18
+ grep -F 500 $tmp
19
+ > $tmp
20
+ }
21
+
22
+ t_begin "app exception logged and backtrace not swallowed" && {
23
+ grep -F 'app error' $r_err
24
+ grep -A1 -F 'app error' $r_err | tail -1 | grep broken-app.ru:
25
+ dbgcat r_err
26
+ > $r_err
27
+ }
28
+
29
+ t_begin "trigger bad response" && {
30
+ curl -sSf http://$listen/nil 2> $tmp || :
31
+ grep -F 500 $tmp
32
+ > $tmp
33
+ }
34
+
35
+ t_begin "app exception logged" && {
36
+ grep -F 'app error' $r_err
37
+ > $r_err
38
+ }
39
+
40
+ t_begin "normal responses alright afterwards" && {
41
+ > $tmp
42
+ curl -sSf http://$listen/ >> $tmp &
43
+ curl -sSf http://$listen/ >> $tmp &
44
+ curl -sSf http://$listen/ >> $tmp &
45
+ curl -sSf http://$listen/ >> $tmp &
46
+ wait
47
+ test xOK = x$(sort < $tmp | uniq)
48
+ }
49
+
50
+ t_begin "teardown" && {
51
+ kill $unicorn_pid
52
+ }
53
+
54
+ t_begin "check stderr" && check_stderr
55
+
56
+ t_done
@@ -0,0 +1,49 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 5 "max_header_len setting (only intended for Rainbows!)"
4
+
5
+ t_begin "setup and start" && {
6
+ unicorn_setup
7
+ req='GET / HTTP/1.0\r\n\r\n'
8
+ len=$(printf "$req" | wc -c)
9
+ echo Unicorn::HttpParser.max_header_len = $len >> $unicorn_config
10
+ unicorn -D -c $unicorn_config env.ru
11
+ unicorn_wait_start
12
+ }
13
+
14
+ t_begin "minimal request succeeds" && {
15
+ rm -f $tmp
16
+ (
17
+ cat $fifo > $tmp &
18
+ printf "$req"
19
+ wait
20
+ echo ok > $ok
21
+ ) | socat - TCP:$listen > $fifo
22
+ test xok = x$(cat $ok)
23
+
24
+ fgrep "HTTP/1.1 200 OK" $tmp
25
+ }
26
+
27
+ t_begin "big request fails" && {
28
+ rm -f $tmp
29
+ (
30
+ cat $fifo > $tmp &
31
+ printf 'GET /xxxxxx HTTP/1.0\r\n\r\n'
32
+ wait
33
+ echo ok > $ok
34
+ ) | socat - TCP:$listen > $fifo
35
+ test xok = x$(cat $ok)
36
+ fgrep "HTTP/1.1 413" $tmp
37
+ }
38
+
39
+ dbgcat tmp
40
+
41
+ t_begin "killing succeeds" && {
42
+ kill $unicorn_pid
43
+ }
44
+
45
+ t_begin "check stderr" && {
46
+ check_stderr
47
+ }
48
+
49
+ t_done
@@ -0,0 +1,49 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+
4
+ t_plan 5 "at_exit/END handlers work as expected"
5
+
6
+ t_begin "setup and startup" && {
7
+ unicorn_setup
8
+ cat >> $unicorn_config <<EOF
9
+ at_exit { \$stdout.syswrite("#{Process.pid} BOTH\\n") }
10
+ END { \$stdout.syswrite("#{Process.pid} END BOTH\\n") }
11
+ after_fork do |_,_|
12
+ at_exit { \$stdout.syswrite("#{Process.pid} WORKER ONLY\\n") }
13
+ END { \$stdout.syswrite("#{Process.pid} END WORKER ONLY\\n") }
14
+ end
15
+ EOF
16
+
17
+ unicorn -D pid.ru -c $unicorn_config
18
+ unicorn_wait_start
19
+ }
20
+
21
+ t_begin "read worker PID" && {
22
+ worker_pid=$(curl -sSf http://$listen/)
23
+ t_info "worker_pid=$worker_pid"
24
+ }
25
+
26
+ t_begin "issue graceful shutdown (SIGQUIT) and wait for termination" && {
27
+ kill -QUIT $unicorn_pid
28
+
29
+ while kill -0 $unicorn_pid >/dev/null 2>&1
30
+ do
31
+ sleep 1
32
+ done
33
+ }
34
+
35
+ t_begin "check stderr" && check_stderr
36
+
37
+ dbgcat r_err
38
+ dbgcat r_out
39
+
40
+ t_begin "all at_exit handlers ran" && {
41
+ grep "$worker_pid BOTH" $r_out
42
+ grep "$unicorn_pid BOTH" $r_out
43
+ grep "$worker_pid END BOTH" $r_out
44
+ grep "$unicorn_pid END BOTH" $r_out
45
+ grep "$worker_pid WORKER ONLY" $r_out
46
+ grep "$worker_pid END WORKER ONLY" $r_out
47
+ }
48
+
49
+ t_done
data/t/t9001-oob_gc.sh ADDED
@@ -0,0 +1,47 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 9 "OobGC test"
4
+
5
+ t_begin "setup and start" && {
6
+ unicorn_setup
7
+ unicorn -D -c $unicorn_config oob_gc.ru
8
+ unicorn_wait_start
9
+ }
10
+
11
+ t_begin "test default interval (4 requests)" && {
12
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
13
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
14
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
15
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
16
+ }
17
+
18
+ t_begin "GC starting-request returns immediately" && {
19
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
20
+ }
21
+
22
+ t_begin "GC is started after 5 requests" && {
23
+ test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp)
24
+ }
25
+
26
+ t_begin "reset GC" && {
27
+ test xfalse = x$(curl -vsSf -X POST http://$listen/gc_reset 2>> $tmp)
28
+ }
29
+
30
+ t_begin "test default interval again (3 requests)" && {
31
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
32
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
33
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
34
+ }
35
+
36
+ t_begin "GC is started after 5 requests" && {
37
+ test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp)
38
+ }
39
+
40
+ t_begin "killing succeeds" && {
41
+ kill -QUIT $unicorn_pid
42
+ }
43
+
44
+ t_begin "check_stderr" && check_stderr
45
+ dbgcat r_err
46
+
47
+ t_done
@@ -0,0 +1,75 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 12 "OobGC test with limited path"
4
+
5
+ t_begin "setup and start" && {
6
+ unicorn_setup
7
+ unicorn -D -c $unicorn_config oob_gc_path.ru
8
+ unicorn_wait_start
9
+ }
10
+
11
+ t_begin "test default is noop" && {
12
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
13
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
14
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
15
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
16
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
17
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
18
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
19
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
20
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
21
+ }
22
+
23
+ t_begin "4 bad requests to bump counter" && {
24
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
25
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
26
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
27
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
28
+ }
29
+
30
+ t_begin "GC-starting request returns immediately" && {
31
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
32
+ }
33
+
34
+ t_begin "GC was started after 5 requests" && {
35
+ test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp)
36
+ }
37
+
38
+ t_begin "reset GC" && {
39
+ test xfalse = x$(curl -vsSf -X POST http://$listen/gc_reset 2>> $tmp)
40
+ }
41
+
42
+ t_begin "test default is noop" && {
43
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
44
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
45
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
46
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
47
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
48
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
49
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
50
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
51
+ test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp)
52
+ }
53
+
54
+ t_begin "4 bad requests to bump counter" && {
55
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
56
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
57
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
58
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
59
+ }
60
+
61
+ t_begin "GC-starting request returns immediately" && {
62
+ test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp)
63
+ }
64
+
65
+ t_begin "GC was started after 5 requests" && {
66
+ test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp)
67
+ }
68
+
69
+ t_begin "killing succeeds" && {
70
+ kill -QUIT $unicorn_pid
71
+ }
72
+
73
+ t_begin "check_stderr" && check_stderr
74
+
75
+ t_done
@@ -0,0 +1,8 @@
1
+ run(lambda { |env|
2
+ body = "#{caller.size}\n"
3
+ h = {
4
+ "Content-Length" => body.size.to_s,
5
+ "Content-Type" => "text/plain",
6
+ }
7
+ [ 200, h, [ body ] ]
8
+ })
@@ -0,0 +1,28 @@
1
+ require 'test/unit'
2
+ require 'unicorn'
3
+
4
+ class TestDroplet < Test::Unit::TestCase
5
+ def test_create_many_droplets
6
+ now = Time.now.to_i
7
+ tmp = (0..1024).map do |i|
8
+ droplet = Unicorn::Worker.new(i)
9
+ assert droplet.respond_to?(:tick)
10
+ assert_equal 0, droplet.tick
11
+ assert_equal(now, droplet.tick = now)
12
+ assert_equal now, droplet.tick
13
+ assert_equal(0, droplet.tick = 0)
14
+ assert_equal 0, droplet.tick
15
+ end
16
+ end
17
+
18
+ def test_shared_process
19
+ droplet = Unicorn::Worker.new(0)
20
+ _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) })
21
+ assert status.success?, status.inspect
22
+ assert_equal 1, droplet.tick
23
+
24
+ _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) })
25
+ assert status.success?, status.inspect
26
+ assert_equal 2, droplet.tick
27
+ end
28
+ end
@@ -258,6 +258,20 @@ class HttpParserTest < Test::Unit::TestCase
258
258
  assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
259
259
  end
260
260
 
261
+ def test_continuation_eats_trailing_spaces
262
+ parser = HttpParser.new
263
+ header = "GET / HTTP/1.1\r\n" \
264
+ "X-ASDF: \r\n" \
265
+ "\t\r\n" \
266
+ " b \r\n" \
267
+ " ASDF\r\n\r\n"
268
+ parser.buf << header
269
+ req = parser.env
270
+ assert_equal req, parser.parse
271
+ assert_equal '', parser.buf
272
+ assert_equal 'b ASDF', req['HTTP_X_ASDF']
273
+ end
274
+
261
275
  def test_continuation_with_absolute_uri_and_ignored_host_header
262
276
  parser = HttpParser.new
263
277
  header = "GET http://example.com/ HTTP/1.1\r\n" \
@@ -726,7 +740,7 @@ class HttpParserTest < Test::Unit::TestCase
726
740
  # then that large header names are caught
727
741
  10.times do |c|
728
742
  get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
729
- assert_raises Unicorn::HttpParserError do
743
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
730
744
  parser.buf << get
731
745
  parser.parse
732
746
  parser.clear
@@ -736,7 +750,7 @@ class HttpParserTest < Test::Unit::TestCase
736
750
  # then that large mangled field values are caught
737
751
  10.times do |c|
738
752
  get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
739
- assert_raises Unicorn::HttpParserError do
753
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
740
754
  parser.buf << get
741
755
  parser.parse
742
756
  parser.clear
@@ -747,7 +761,7 @@ class HttpParserTest < Test::Unit::TestCase
747
761
  get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
748
762
  get << "X-Test: test\r\n" * (80 * 1024)
749
763
  parser.buf << get
750
- assert_raises Unicorn::HttpParserError do
764
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
751
765
  parser.parse
752
766
  end
753
767
  parser.clear
@@ -755,7 +769,7 @@ class HttpParserTest < Test::Unit::TestCase
755
769
  # finally just that random garbage gets blocked all the time
756
770
  10.times do |c|
757
771
  get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
758
- assert_raises Unicorn::HttpParserError do
772
+ assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
759
773
  parser.buf << get
760
774
  parser.parse
761
775
  parser.clear
@@ -764,6 +778,48 @@ class HttpParserTest < Test::Unit::TestCase
764
778
 
765
779
  end
766
780
 
781
+ def test_leading_tab
782
+ parser = HttpParser.new
783
+ get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
784
+ assert parser.add_parse(get)
785
+ assert_equal 'example.com', parser.env['HTTP_HOST']
786
+ end
787
+
788
+ def test_trailing_whitespace
789
+ parser = HttpParser.new
790
+ get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
791
+ assert parser.add_parse(get)
792
+ assert_equal 'example.com', parser.env['HTTP_HOST']
793
+ end
794
+
795
+ def test_trailing_tab
796
+ parser = HttpParser.new
797
+ get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
798
+ assert parser.add_parse(get)
799
+ assert_equal 'example.com', parser.env['HTTP_HOST']
800
+ end
801
+
802
+ def test_trailing_multiple_linear_whitespace
803
+ parser = HttpParser.new
804
+ get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
805
+ assert parser.add_parse(get)
806
+ assert_equal 'example.com', parser.env['HTTP_HOST']
807
+ end
808
+
809
+ def test_embedded_linear_whitespace_ok
810
+ parser = HttpParser.new
811
+ get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
812
+ assert parser.add_parse(get)
813
+ assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
814
+ end
815
+
816
+ def test_empty_header
817
+ parser = HttpParser.new
818
+ get = "GET / HTTP/1.1\r\nHost: \r\n\r\n"
819
+ assert parser.add_parse(get)
820
+ assert_equal '', parser.env['HTTP_HOST']
821
+ end
822
+
767
823
  # so we don't care about the portability of this test
768
824
  # if it doesn't leak on Linux, it won't leak anywhere else
769
825
  # unless your C compiler or platform is otherwise broken