unicorn 4.9.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +5 -0
  3. data/.manifest +14 -15
  4. data/.olddoc.yml +16 -6
  5. data/Application_Timeouts +7 -7
  6. data/CONTRIBUTORS +6 -2
  7. data/DESIGN +2 -4
  8. data/Documentation/.gitignore +1 -3
  9. data/Documentation/unicorn.1 +222 -0
  10. data/Documentation/unicorn_rails.1 +207 -0
  11. data/FAQ +17 -8
  12. data/GIT-VERSION-FILE +1 -1
  13. data/GIT-VERSION-GEN +1 -1
  14. data/GNUmakefile +121 -56
  15. data/HACKING +2 -10
  16. data/ISSUES +40 -43
  17. data/KNOWN_ISSUES +11 -11
  18. data/LATEST +16 -22
  19. data/LICENSE +2 -2
  20. data/Links +24 -25
  21. data/NEWS +771 -0
  22. data/PHILOSOPHY +0 -6
  23. data/README +46 -40
  24. data/SIGNALS +2 -2
  25. data/Sandbox +11 -10
  26. data/TODO +0 -2
  27. data/TUNING +30 -9
  28. data/archive/slrnpull.conf +1 -1
  29. data/bin/unicorn +4 -2
  30. data/bin/unicorn_rails +3 -3
  31. data/examples/big_app_gc.rb +1 -1
  32. data/examples/init.sh +36 -8
  33. data/examples/logrotate.conf +17 -2
  34. data/examples/nginx.conf +14 -14
  35. data/examples/unicorn.conf.minimal.rb +2 -2
  36. data/examples/unicorn.conf.rb +3 -6
  37. data/examples/unicorn.socket +11 -0
  38. data/examples/unicorn@.service +40 -0
  39. data/ext/unicorn_http/c_util.h +5 -13
  40. data/ext/unicorn_http/common_field_optimization.h +22 -5
  41. data/ext/unicorn_http/epollexclusive.h +124 -0
  42. data/ext/unicorn_http/ext_help.h +0 -44
  43. data/ext/unicorn_http/extconf.rb +32 -5
  44. data/ext/unicorn_http/global_variables.h +2 -2
  45. data/ext/unicorn_http/httpdate.c +3 -2
  46. data/ext/unicorn_http/unicorn_http.c +926 -638
  47. data/ext/unicorn_http/unicorn_http.rl +159 -170
  48. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  49. data/lib/unicorn/configurator.rb +110 -44
  50. data/lib/unicorn/const.rb +2 -25
  51. data/lib/unicorn/http_request.rb +110 -31
  52. data/lib/unicorn/http_response.rb +17 -31
  53. data/lib/unicorn/http_server.rb +255 -179
  54. data/lib/unicorn/launcher.rb +1 -1
  55. data/lib/unicorn/oob_gc.rb +6 -6
  56. data/lib/unicorn/select_waiter.rb +6 -0
  57. data/lib/unicorn/socket_helper.rb +58 -78
  58. data/lib/unicorn/stream_input.rb +8 -7
  59. data/lib/unicorn/tee_input.rb +8 -10
  60. data/lib/unicorn/tmpio.rb +8 -7
  61. data/lib/unicorn/util.rb +5 -4
  62. data/lib/unicorn/version.rb +1 -1
  63. data/lib/unicorn/worker.rb +36 -23
  64. data/lib/unicorn.rb +64 -46
  65. data/man/man1/unicorn.1 +123 -119
  66. data/man/man1/unicorn_rails.1 +106 -107
  67. data/t/GNUmakefile +3 -72
  68. data/t/README +4 -4
  69. data/t/t0011-active-unix-socket.sh +1 -1
  70. data/t/t0012-reload-empty-config.sh +2 -1
  71. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  72. data/t/t0301.ru +13 -0
  73. data/t/test-lib.sh +4 -3
  74. data/test/benchmark/README +14 -4
  75. data/test/benchmark/ddstream.ru +50 -0
  76. data/test/benchmark/readinput.ru +40 -0
  77. data/test/benchmark/uconnect.perl +66 -0
  78. data/test/exec/test_exec.rb +73 -19
  79. data/test/test_helper.rb +40 -31
  80. data/test/unit/test_ccc.rb +91 -0
  81. data/test/unit/test_droplet.rb +1 -1
  82. data/test/unit/test_http_parser.rb +46 -16
  83. data/test/unit/test_http_parser_ng.rb +97 -114
  84. data/test/unit/test_request.rb +10 -10
  85. data/test/unit/test_response.rb +28 -16
  86. data/test/unit/test_server.rb +86 -12
  87. data/test/unit/test_signals.rb +8 -8
  88. data/test/unit/test_socket_helper.rb +14 -10
  89. data/test/unit/test_upload.rb +9 -14
  90. data/test/unit/test_util.rb +31 -5
  91. data/test/unit/test_waiter.rb +34 -0
  92. data/unicorn.gemspec +27 -19
  93. metadata +28 -45
  94. data/Documentation/GNUmakefile +0 -30
  95. data/Documentation/unicorn.1.txt +0 -185
  96. data/Documentation/unicorn_rails.1.txt +0 -175
  97. data/examples/git.ru +0 -13
  98. data/lib/unicorn/app/exec_cgi.rb +0 -154
  99. data/lib/unicorn/app/inetd.rb +0 -109
  100. data/lib/unicorn/ssl_client.rb +0 -11
  101. data/lib/unicorn/ssl_configurator.rb +0 -104
  102. data/lib/unicorn/ssl_server.rb +0 -42
  103. data/t/hijack.ru +0 -42
  104. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  105. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  106. data/t/t0200-rack-hijack.sh +0 -27
  107. data/test/unit/test_http_parser_xftrust.rb +0 -38
  108. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -1,19 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'logger'
3
- require 'unicorn/ssl_configurator'
4
3
 
5
- # Implements a simple DSL for configuring a \Unicorn server.
4
+ # Implements a simple DSL for configuring a unicorn server.
6
5
  #
7
- # See http://unicorn.bogomips.org/examples/unicorn.conf.rb and
8
- # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
6
+ # See https://yhbt.net/unicorn/examples/unicorn.conf.rb and
7
+ # https://yhbt.net/unicorn/examples/unicorn.conf.minimal.rb
9
8
  # example configuration files. An example config file for use with
10
9
  # nginx is also available at
11
- # http://unicorn.bogomips.org/examples/nginx.conf
10
+ # https://yhbt.net/unicorn/examples/nginx.conf
12
11
  #
13
12
  # See the link:/TUNING.html document for more information on tuning unicorn.
14
13
  class Unicorn::Configurator
15
14
  include Unicorn
16
- include Unicorn::SSLConfigurator
17
15
 
18
16
  # :stopdoc:
19
17
  attr_accessor :set, :config_file, :after_reload
@@ -43,12 +41,24 @@ class Unicorn::Configurator
43
41
  :before_exec => lambda { |server|
44
42
  server.logger.info("forked child re-executing...")
45
43
  },
44
+ :after_worker_exit => lambda { |server, worker, status|
45
+ m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
46
+ if status.success?
47
+ server.logger.info(m)
48
+ else
49
+ server.logger.error(m)
50
+ end
51
+ },
52
+ :after_worker_ready => lambda { |server, worker|
53
+ server.logger.info("worker=#{worker.nr} ready")
54
+ },
46
55
  :pid => nil,
56
+ :early_hints => false,
57
+ :worker_exec => false,
47
58
  :preload_app => false,
48
59
  :check_client_connection => false,
49
- :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1),
60
+ :rewindable_input => true,
50
61
  :client_body_buffer_size => Unicorn::Const::MAX_BODY,
51
- :trust_x_forwarded => true,
52
62
  }
53
63
  #:startdoc:
54
64
 
@@ -79,6 +89,9 @@ class Unicorn::Configurator
79
89
  RACKUP[:set_listener] and
80
90
  set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
81
91
 
92
+ RACKUP[:no_default_middleware] and
93
+ set[:default_middleware] = false
94
+
82
95
  # unicorn_rails creates dirs here after working_directory is bound
83
96
  after_reload.call if after_reload
84
97
 
@@ -154,6 +167,38 @@ class Unicorn::Configurator
154
167
  set_hook(:after_fork, block_given? ? block : args[0])
155
168
  end
156
169
 
170
+ # sets after_worker_exit hook to a given block. This block will be called
171
+ # by the master process after a worker exits:
172
+ #
173
+ # after_worker_exit do |server,worker,status|
174
+ # # status is a Process::Status instance for the exited worker process
175
+ # unless status.success?
176
+ # server.logger.error("worker process failure: #{status.inspect}")
177
+ # end
178
+ # end
179
+ #
180
+ # after_worker_exit is only available in unicorn 5.3.0+
181
+ def after_worker_exit(*args, &block)
182
+ set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
183
+ end
184
+
185
+ # sets after_worker_ready hook to a given block. This block will be called
186
+ # by a worker process after it has been fully loaded, directly before it
187
+ # starts responding to requests:
188
+ #
189
+ # after_worker_ready do |server,worker|
190
+ # server.logger.info("worker #{worker.nr} ready, dropping privileges")
191
+ # worker.user('username', 'groupname')
192
+ # end
193
+ #
194
+ # Do not use Configurator#user if you rely on changing users in the
195
+ # after_worker_ready hook.
196
+ #
197
+ # after_worker_ready is only available in unicorn 5.3.0+
198
+ def after_worker_ready(*args, &block)
199
+ set_hook(:after_worker_ready, block_given? ? block : args[0])
200
+ end
201
+
157
202
  # sets before_fork got be a given Proc object. This Proc
158
203
  # object will be called by the master process before forking
159
204
  # each worker.
@@ -184,8 +229,6 @@ class Unicorn::Configurator
184
229
  # to have nginx always retry backends that may have had workers
185
230
  # SIGKILL-ed due to timeouts.
186
231
  #
187
- # # See http://wiki.nginx.org/NginxHttpUpstreamModule for more details
188
- # # on nginx upstream configuration:
189
232
  # upstream unicorn_backend {
190
233
  # # for UNIX domain socket setups:
191
234
  # server unix:/path/to/.unicorn.sock fail_timeout=0;
@@ -195,6 +238,9 @@ class Unicorn::Configurator
195
238
  # server 192.168.0.8:8080 fail_timeout=0;
196
239
  # server 192.168.0.9:8080 fail_timeout=0;
197
240
  # }
241
+ #
242
+ # See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
243
+ # for more details on nginx upstream configuration.
198
244
  def timeout(seconds)
199
245
  set_int(:timeout, seconds, 3)
200
246
  # POSIX says 31 days is the smallest allowed maximum timeout for select()
@@ -202,6 +248,17 @@ class Unicorn::Configurator
202
248
  set[:timeout] = seconds > max ? max : seconds
203
249
  end
204
250
 
251
+ # Whether to exec in each worker process after forking. This changes the
252
+ # memory layout of each worker process, which is a security feature designed
253
+ # to defeat possible address space discovery attacks. Note that using
254
+ # worker_exec only makes sense if you are not preloading the application,
255
+ # and will result in higher memory usage.
256
+ #
257
+ # worker_exec is only available in unicorn 5.3.0+
258
+ def worker_exec(bool)
259
+ set_bool(:worker_exec, bool)
260
+ end
261
+
205
262
  # sets the current number of worker_processes to +nr+. Each worker
206
263
  # process will serve exactly one client at a time. You can
207
264
  # increment or decrement this value at runtime by sending SIGTTIN
@@ -212,6 +269,23 @@ class Unicorn::Configurator
212
269
  set_int(:worker_processes, nr, 1)
213
270
  end
214
271
 
272
+ # sets whether to add default middleware in the development and
273
+ # deployment RACK_ENVs.
274
+ #
275
+ # default_middleware is only available in unicorn 5.5.0+
276
+ def default_middleware(bool)
277
+ set_bool(:default_middleware, bool)
278
+ end
279
+
280
+ # sets whether to enable the proposed early hints Rack API.
281
+ # If enabled, Rails 5.2+ will automatically send a 103 Early Hint
282
+ # for all the `javascript_include_tag` and `stylesheet_link_tag`
283
+ # in your response. See: https://api.rubyonrails.org/v5.2/classes/ActionDispatch/Request.html#method-i-send_early_hints
284
+ # See also https://tools.ietf.org/html/rfc8297
285
+ def early_hints(bool)
286
+ set_bool(:early_hints, bool)
287
+ end
288
+
215
289
  # sets listeners to the given +addresses+, replacing or augmenting the
216
290
  # current set. This is for the global listener pool shared by all
217
291
  # worker processes. For per-worker listeners, see the after_fork example
@@ -257,6 +331,11 @@ class Unicorn::Configurator
257
331
  #
258
332
  # Default: 1024
259
333
  #
334
+ # Note: with the Linux kernel, the net.core.somaxconn sysctl defaults
335
+ # to 128, capping this value to 128. Raising the sysctl allows a
336
+ # larger backlog (which may not be desirable with multiple,
337
+ # load-balanced machines).
338
+ #
260
339
  # [:rcvbuf => bytes, :sndbuf => bytes]
261
340
  #
262
341
  # Maximum receive and send buffer sizes (in bytes) of sockets.
@@ -280,20 +359,19 @@ class Unicorn::Configurator
280
359
  # Setting this to +true+ can make streaming responses in Rails 3.1
281
360
  # appear more quickly at the cost of slightly higher bandwidth usage.
282
361
  # The effect of this option is most visible if nginx is not used,
283
- # but nginx remains highly recommended with \Unicorn.
362
+ # but nginx remains highly recommended with unicorn.
284
363
  #
285
364
  # This has no effect on UNIX sockets.
286
365
  #
287
- # Default: +true+ (Nagle's algorithm disabled) in \Unicorn,
288
- # +true+ in Rainbows! This defaulted to +false+ in \Unicorn
289
- # 3.x
366
+ # Default: +true+ (Nagle's algorithm disabled) in unicorn
367
+ # This defaulted to +false+ in unicorn 3.x
290
368
  #
291
369
  # [:tcp_nopush => true or false]
292
370
  #
293
371
  # Enables/disables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD
294
372
  #
295
373
  # This prevents partial TCP frames from being sent out and reduces
296
- # wakeups in nginx if it is on a different machine. Since \Unicorn
374
+ # wakeups in nginx if it is on a different machine. Since unicorn
297
375
  # is only designed for applications that send the response body
298
376
  # quickly without keepalive, sockets will always be flushed on close
299
377
  # to prevent delays.
@@ -301,7 +379,7 @@ class Unicorn::Configurator
301
379
  # This has no effect on UNIX sockets.
302
380
  #
303
381
  # Default: +false+
304
- # This defaulted to +true+ in \Unicorn 3.4 - 3.7
382
+ # This defaulted to +true+ in unicorn 3.4 - 3.7
305
383
  #
306
384
  # [:ipv6only => true or false]
307
385
  #
@@ -385,12 +463,10 @@ class Unicorn::Configurator
385
463
  # and +false+ or +nil+ is synonymous for a value of zero.
386
464
  #
387
465
  # A value of +1+ is a good optimization for local networks
388
- # and trusted clients. For Rainbows! and Zbatery users, a higher
389
- # value (e.g. +60+) provides more protection against some
390
- # denial-of-service attacks. There is no good reason to ever
391
- # disable this with a +zero+ value when serving HTTP.
466
+ # and trusted clients. There is no good reason to ever
467
+ # disable this with a +zero+ value with unicorn.
392
468
  #
393
- # Default: 1 retransmit for \Unicorn, 60 for Rainbows! 0.95.0\+
469
+ # Default: 1
394
470
  #
395
471
  # [:accept_filter => String]
396
472
  #
@@ -399,8 +475,7 @@ class Unicorn::Configurator
399
475
  # This enables either the "dataready" or (default) "httpready"
400
476
  # accept() filter under FreeBSD. This is intended as an
401
477
  # optimization to reduce context switches with common GET/HEAD
402
- # requests. For Rainbows! and Zbatery users, this provides
403
- # some protection against certain denial-of-service attacks, too.
478
+ # requests.
404
479
  #
405
480
  # There is no good reason to change from the default.
406
481
  #
@@ -467,13 +542,12 @@ class Unicorn::Configurator
467
542
  # Disabling rewindability can improve performance by lowering
468
543
  # I/O and memory usage for applications that accept uploads.
469
544
  # Keep in mind that the Rack 1.x spec requires
470
- # \env[\"rack.input\"] to be rewindable, so this allows
471
- # intentionally violating the current Rack 1.x spec.
545
+ # \env[\"rack.input\"] to be rewindable,
546
+ # but the Rack 2.x spec does not.
472
547
  #
473
- # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
474
- # Rack conformance. When Rack 2.x is finalized, this will most
475
- # likely default to +false+ while still conforming to the newer
476
- # (less demanding) spec.
548
+ # +rewindable_input+ defaults to +true+ for compatibility.
549
+ # Setting it to +false+ may be safe for applications and
550
+ # frameworks developed for Rack 2.x and later.
477
551
  def rewindable_input(bool)
478
552
  set_bool(:rewindable_input, bool)
479
553
  end
@@ -534,7 +608,7 @@ class Unicorn::Configurator
534
608
  # just let chdir raise errors
535
609
  path = File.expand_path(path)
536
610
  if config_file &&
537
- config_file[0] != ?/ &&
611
+ ! config_file.start_with?('/') &&
538
612
  ! File.readable?("#{path}/#{config_file}")
539
613
  raise ArgumentError,
540
614
  "config_file=#{config_file} would not be accessible in" \
@@ -549,6 +623,10 @@ class Unicorn::Configurator
549
623
  # This switch will occur after calling the after_fork hook, and only
550
624
  # if the Worker#user method is not called in the after_fork hook
551
625
  # +group+ is optional and will not change if unspecified.
626
+ #
627
+ # Do not use Configurator#user if you rely on changing users in the
628
+ # after_worker_ready hook. Instead, you need to call Worker#user
629
+ # directly in after_worker_ready.
552
630
  def user(user, group = nil)
553
631
  # raises ArgumentError on invalid user/group
554
632
  Etc.getpwnam(user)
@@ -556,18 +634,6 @@ class Unicorn::Configurator
556
634
  set[:user] = [ user, group ]
557
635
  end
558
636
 
559
- # Sets whether or not the parser will trust X-Forwarded-Proto and
560
- # X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly.
561
- # Rainbows!/Zbatery installations facing untrusted clients directly
562
- # should set this to +false+. This is +true+ by default as Unicorn
563
- # is designed to only sit behind trusted nginx proxies.
564
- #
565
- # This has never been publically documented and is subject to removal
566
- # in future releases.
567
- def trust_x_forwarded(bool) # :nodoc:
568
- set_bool(:trust_x_forwarded, bool)
569
- end
570
-
571
637
  # expands "unix:path/to/foo" to a socket relative to the current path
572
638
  # expands pathnames of sockets if relative to "~" or "~username"
573
639
  # expands "*:port and ":port" to "0.0.0.0:port"
@@ -601,7 +667,7 @@ private
601
667
  def canonicalize_tcp(addr, port)
602
668
  packed = Socket.pack_sockaddr_in(port, addr)
603
669
  port, addr = Socket.unpack_sockaddr_in(packed)
604
- /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
670
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
605
671
  end
606
672
 
607
673
  def set_path(var, path) #:nodoc:
@@ -657,7 +723,7 @@ private
657
723
  raise ArgumentError, "rackup file (#{ru}) not readable"
658
724
 
659
725
  # it could be a .rb file, too, we don't parse those manually
660
- ru =~ /\.ru\z/ or return
726
+ ru.end_with?('.ru') or return
661
727
 
662
728
  /^#\\(.*)/ =~ File.read(ru) or return
663
729
  RACKUP[:optparse].parse!($1.split(/\s+/))
data/lib/unicorn/const.rb CHANGED
@@ -1,12 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # :enddoc:
4
- # Frequently used constants when constructing requests or responses.
5
- # Many times the constant just refers to a string with the same
6
- # contents. Using these constants gave about a 3% to 10% performance
7
- # improvement over using the strings directly. Symbols did not really
8
- # improve things much compared to constants.
9
- module Unicorn::Const
3
+ module Unicorn::Const # :nodoc:
10
4
  # default TCP listen host address (0.0.0.0, all interfaces)
11
5
  DEFAULT_HOST = "0.0.0.0"
12
6
 
@@ -23,22 +17,5 @@ module Unicorn::Const
23
17
  # temporary file for reading (112 kilobytes). This is the default
24
18
  # value of client_body_buffer_size.
25
19
  MAX_BODY = 1024 * 112
26
-
27
- # :stopdoc:
28
- # common errors we'll send back
29
- # (N.B. these are not used by unicorn, but we won't drop them until
30
- # unicorn 5.x to avoid breaking Rainbows!).
31
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
32
- ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n"
33
- ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
34
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
35
-
36
- EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
37
- EXPECT_100_RESPONSE_SUFFIXED = "100 Continue\r\n\r\nHTTP/1.1 "
38
-
39
- HTTP_RESPONSE_START = ['HTTP', '/1.1 ']
40
- HTTP_EXPECT = "HTTP_EXPECT"
41
-
42
- # :startdoc:
43
20
  end
44
- require 'unicorn/version'
21
+ require_relative 'version'
@@ -13,7 +13,8 @@ class Unicorn::HttpParser
13
13
  "rack.multiprocess" => true,
14
14
  "rack.multithread" => false,
15
15
  "rack.run_once" => false,
16
- "rack.version" => [1, 1],
16
+ "rack.version" => [1, 2],
17
+ "rack.hijack?" => true,
17
18
  "SCRIPT_NAME" => "",
18
19
 
19
20
  # this is not in the Rack spec, but some apps may rely on it
@@ -22,14 +23,12 @@ class Unicorn::HttpParser
22
23
 
23
24
  NULL_IO = StringIO.new("")
24
25
 
25
- attr_accessor :response_start_sent
26
-
27
26
  # :stopdoc:
28
- # A frozen format for this is about 15% faster
29
- REMOTE_ADDR = 'REMOTE_ADDR'.freeze
30
- RACK_INPUT = 'rack.input'.freeze
27
+ HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
28
+ EMPTY_ARRAY = [].freeze
31
29
  @@input_class = Unicorn::TeeInput
32
30
  @@check_client_connection = false
31
+ @@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
33
32
 
34
33
  def self.input_class
35
34
  @@input_class
@@ -63,17 +62,16 @@ class Unicorn::HttpParser
63
62
  # This does minimal exception trapping and it is up to the caller
64
63
  # to handle any socket errors (e.g. user aborted upload).
65
64
  def read(socket)
66
- clear
67
65
  e = env
68
66
 
69
- # From http://www.ietf.org/rfc/rfc3875:
67
+ # From https://www.ietf.org/rfc/rfc3875:
70
68
  # "Script authors should be aware that the REMOTE_ADDR and
71
69
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
72
70
  # may not identify the ultimate source of the request. They
73
71
  # identify the client for the immediate request to the server;
74
72
  # that client may be a proxy, gateway, or other intermediary
75
73
  # acting on behalf of the actual source client."
76
- e[REMOTE_ADDR] = socket.kgio_addr
74
+ e['REMOTE_ADDR'] = socket.kgio_addr
77
75
 
78
76
  # short circuit the common case with small GET requests first
79
77
  socket.kgio_read!(16384, buf)
@@ -83,40 +81,121 @@ class Unicorn::HttpParser
83
81
  false until add_parse(socket.kgio_read!(16384))
84
82
  end
85
83
 
86
- # detect if the socket is valid by writing a partial response:
87
- if @@check_client_connection && headers?
88
- @response_start_sent = true
89
- Unicorn::Const::HTTP_RESPONSE_START.each { |c| socket.write(c) }
90
- end
84
+ check_client_connection(socket) if @@check_client_connection
85
+
86
+ e['rack.input'] = 0 == content_length ?
87
+ NULL_IO : @@input_class.new(socket, self)
88
+
89
+ # for Rack hijacking in Rack 1.5 and later
90
+ e['unicorn.socket'] = socket
91
+ e['rack.hijack'] = self
91
92
 
92
- e[RACK_INPUT] = 0 == content_length ?
93
- NULL_IO : @@input_class.new(socket, self)
94
- hijack_setup(e, socket)
95
93
  e.merge!(DEFAULTS)
96
94
  end
97
95
 
98
- # Rack 1.5.0 (protocol version 1.2) adds hijack request support
99
- if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
100
- DEFAULTS["rack.hijack?"] = true
101
- DEFAULTS["rack.version"] = [1, 2]
96
+ # for rack.hijack, we respond to this method so no extra allocation
97
+ # of a proc object
98
+ def call
99
+ hijacked!
100
+ env['rack.hijack_io'] = env['unicorn.socket']
101
+ end
102
102
 
103
- RACK_HIJACK = "rack.hijack".freeze
104
- RACK_HIJACK_IO = "rack.hijack_io".freeze
103
+ def hijacked?
104
+ env.include?('rack.hijack_io'.freeze)
105
+ end
105
106
 
106
- def hijacked?
107
- env.include?(RACK_HIJACK_IO)
107
+ if Raindrops.const_defined?(:TCP_Info)
108
+ TCPI = Raindrops::TCP_Info.allocate
109
+
110
+ def check_client_connection(socket) # :nodoc:
111
+ if Unicorn::TCPClient === socket
112
+ # Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
113
+ raise Errno::EPIPE, "client closed connection".freeze,
114
+ EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
115
+ else
116
+ write_http_header(socket)
117
+ end
108
118
  end
109
119
 
110
- def hijack_setup(e, socket)
111
- e[RACK_HIJACK] = proc { e[RACK_HIJACK_IO] = socket }
120
+ if Raindrops.const_defined?(:TCP)
121
+ # raindrops 0.18.0+ supports FreeBSD + Linux using the same names
122
+ # Evaluate these hash lookups at load time so we can
123
+ # generate an opt_case_dispatch instruction
124
+ eval <<-EOS
125
+ def closed_state?(state) # :nodoc:
126
+ case state
127
+ when #{Raindrops::TCP[:ESTABLISHED]}
128
+ false
129
+ when #{Raindrops::TCP.values_at(
130
+ :CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
131
+ true
132
+ else
133
+ false
134
+ end
135
+ end
136
+ EOS
137
+ else
138
+ # raindrops before 0.18 only supported TCP_INFO under Linux
139
+ def closed_state?(state) # :nodoc:
140
+ case state
141
+ when 1 # ESTABLISHED
142
+ false
143
+ when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
144
+ true
145
+ else
146
+ false
147
+ end
148
+ end
112
149
  end
113
150
  else
114
- # old Rack, do nothing.
115
- def hijack_setup(e, _)
151
+
152
+ # Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
153
+ # Not that efficient, but probably still better than doing unnecessary
154
+ # work after a client gives up.
155
+ def check_client_connection(socket) # :nodoc:
156
+ if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
157
+ opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
158
+ if opt =~ /\bstate=(\S+)/
159
+ raise Errno::EPIPE, "client closed connection".freeze,
160
+ EMPTY_ARRAY if closed_state_str?($1)
161
+ else
162
+ @@tcpi_inspect_ok = false
163
+ write_http_header(socket)
164
+ end
165
+ opt.clear
166
+ else
167
+ write_http_header(socket)
168
+ end
116
169
  end
117
170
 
118
- def hijacked?
119
- false
171
+ def closed_state_str?(state)
172
+ case state
173
+ when 'ESTABLISHED'
174
+ false
175
+ # not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
176
+ when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
177
+ true
178
+ else
179
+ false
180
+ end
181
+ end
182
+ end
183
+
184
+ def write_http_header(socket) # :nodoc:
185
+ if headers?
186
+ self.response_start_sent = true
187
+ HTTP_RESPONSE_START.each { |c| socket.write(c) }
188
+ end
189
+ end
190
+
191
+ # called by ext/unicorn_http/unicorn_http.rl via rb_funcall
192
+ def self.is_chunked?(v) # :nodoc:
193
+ vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
194
+ if vals.pop == 'chunked'.freeze
195
+ return true unless vals.include?('chunked'.freeze)
196
+ raise Unicorn::HttpParserError, 'double chunked', []
120
197
  end
198
+ return false unless vals.include?('chunked'.freeze)
199
+ raise Unicorn::HttpParserError, 'chunked not last', []
121
200
  end
122
201
  end
@@ -10,66 +10,52 @@
10
10
  # is the job of Rack, with the exception of the "Date" and "Status" header.
11
11
  module Unicorn::HttpResponse
12
12
 
13
- # Every standard HTTP code mapped to the appropriate message.
14
- CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
15
- hash[code] = "#{code} #{msg}"
16
- hash
17
- }
18
- CRLF = "\r\n"
13
+ STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
14
+ Rack::Utils::HTTP_STATUS_CODES : {}
19
15
 
16
+ # internal API, code will always be common-enough-for-even-old-Rack
20
17
  def err_response(code, response_start_sent)
21
- "#{response_start_sent ? '' : 'HTTP/1.1 '}#{CODES[code]}\r\n\r\n"
18
+ "#{response_start_sent ? '' : 'HTTP/1.1 '}" \
19
+ "#{code} #{STATUS_CODES[code]}\r\n\r\n"
22
20
  end
23
21
 
24
22
  # writes the rack_response to socket as an HTTP response
25
23
  def http_response_write(socket, status, headers, body,
26
- response_start_sent=false)
27
- status = CODES[status.to_i] || status
24
+ req = Unicorn::HttpRequest.new)
28
25
  hijack = nil
29
26
 
30
- http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
31
27
  if headers
32
- buf = "#{http_response_start}#{status}\r\n" \
28
+ code = status.to_i
29
+ msg = STATUS_CODES[code]
30
+ start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
31
+ buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
33
32
  "Date: #{httpdate}\r\n" \
34
- "Status: #{status}\r\n" \
35
33
  "Connection: close\r\n"
36
34
  headers.each do |key, value|
37
35
  case key
38
- when %r{\A(?:Date\z|Connection\z)}i
36
+ when %r{\A(?:Date|Connection)\z}i
39
37
  next
40
38
  when "rack.hijack"
41
- # this was an illegal key in Rack < 1.5, so it should be
42
- # OK to silently discard it for those older versions
43
- hijack = hijack_prepare(value)
39
+ # This should only be hit under Rack >= 1.5, as this was an illegal
40
+ # key in Rack < 1.5
41
+ hijack = value
44
42
  else
45
43
  if value =~ /\n/
46
44
  # avoiding blank, key-only cookies with /\n+/
47
- buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
45
+ value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
48
46
  else
49
47
  buf << "#{key}: #{value}\r\n"
50
48
  end
51
49
  end
52
50
  end
53
- socket.write(buf << CRLF)
51
+ socket.write(buf << "\r\n".freeze)
54
52
  end
55
53
 
56
54
  if hijack
57
- body = nil # ensure we do not close body
55
+ req.hijacked!
58
56
  hijack.call(socket)
59
57
  else
60
58
  body.each { |chunk| socket.write(chunk) }
61
59
  end
62
- ensure
63
- body.respond_to?(:close) and body.close
64
- end
65
-
66
- # Rack 1.5.0 (protocol version 1.2) adds response hijacking support
67
- if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
68
- def hijack_prepare(value)
69
- value
70
- end
71
- else
72
- def hijack_prepare(_)
73
- end
74
60
  end
75
61
  end