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,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
  #
@@ -309,8 +387,6 @@ class Unicorn::Configurator
309
387
  # to receive IPv4 queries on dual-stack systems. A separate IPv4-only
310
388
  # listener is required if this is true.
311
389
  #
312
- # This option is only available for Ruby 1.9.2 and later.
313
- #
314
390
  # Enabling this option for the IPv6-only listener and having a
315
391
  # separate IPv4 listener is recommended if you wish to support IPv6
316
392
  # on the same TCP port. Otherwise, the value of \env[\"REMOTE_ADDR\"]
@@ -387,12 +463,10 @@ class Unicorn::Configurator
387
463
  # and +false+ or +nil+ is synonymous for a value of zero.
388
464
  #
389
465
  # A value of +1+ is a good optimization for local networks
390
- # and trusted clients. For Rainbows! and Zbatery users, a higher
391
- # value (e.g. +60+) provides more protection against some
392
- # denial-of-service attacks. There is no good reason to ever
393
- # 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.
394
468
  #
395
- # Default: 1 retransmit for \Unicorn, 60 for Rainbows! 0.95.0\+
469
+ # Default: 1
396
470
  #
397
471
  # [:accept_filter => String]
398
472
  #
@@ -401,8 +475,7 @@ class Unicorn::Configurator
401
475
  # This enables either the "dataready" or (default) "httpready"
402
476
  # accept() filter under FreeBSD. This is intended as an
403
477
  # optimization to reduce context switches with common GET/HEAD
404
- # requests. For Rainbows! and Zbatery users, this provides
405
- # some protection against certain denial-of-service attacks, too.
478
+ # requests.
406
479
  #
407
480
  # There is no good reason to change from the default.
408
481
  #
@@ -469,13 +542,12 @@ class Unicorn::Configurator
469
542
  # Disabling rewindability can improve performance by lowering
470
543
  # I/O and memory usage for applications that accept uploads.
471
544
  # Keep in mind that the Rack 1.x spec requires
472
- # \env[\"rack.input\"] to be rewindable, so this allows
473
- # intentionally violating the current Rack 1.x spec.
545
+ # \env[\"rack.input\"] to be rewindable,
546
+ # but the Rack 2.x spec does not.
474
547
  #
475
- # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
476
- # Rack conformance. When Rack 2.x is finalized, this will most
477
- # likely default to +false+ while still conforming to the newer
478
- # (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.
479
551
  def rewindable_input(bool)
480
552
  set_bool(:rewindable_input, bool)
481
553
  end
@@ -536,7 +608,7 @@ class Unicorn::Configurator
536
608
  # just let chdir raise errors
537
609
  path = File.expand_path(path)
538
610
  if config_file &&
539
- config_file[0] != ?/ &&
611
+ ! config_file.start_with?('/') &&
540
612
  ! File.readable?("#{path}/#{config_file}")
541
613
  raise ArgumentError,
542
614
  "config_file=#{config_file} would not be accessible in" \
@@ -551,6 +623,10 @@ class Unicorn::Configurator
551
623
  # This switch will occur after calling the after_fork hook, and only
552
624
  # if the Worker#user method is not called in the after_fork hook
553
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.
554
630
  def user(user, group = nil)
555
631
  # raises ArgumentError on invalid user/group
556
632
  Etc.getpwnam(user)
@@ -558,18 +634,6 @@ class Unicorn::Configurator
558
634
  set[:user] = [ user, group ]
559
635
  end
560
636
 
561
- # Sets whether or not the parser will trust X-Forwarded-Proto and
562
- # X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly.
563
- # Rainbows!/Zbatery installations facing untrusted clients directly
564
- # should set this to +false+. This is +true+ by default as Unicorn
565
- # is designed to only sit behind trusted nginx proxies.
566
- #
567
- # This has never been publically documented and is subject to removal
568
- # in future releases.
569
- def trust_x_forwarded(bool) # :nodoc:
570
- set_bool(:trust_x_forwarded, bool)
571
- end
572
-
573
637
  # expands "unix:path/to/foo" to a socket relative to the current path
574
638
  # expands pathnames of sockets if relative to "~" or "~username"
575
639
  # expands "*:port and ":port" to "0.0.0.0:port"
@@ -603,7 +667,7 @@ private
603
667
  def canonicalize_tcp(addr, port)
604
668
  packed = Socket.pack_sockaddr_in(port, addr)
605
669
  port, addr = Socket.unpack_sockaddr_in(packed)
606
- /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
670
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
607
671
  end
608
672
 
609
673
  def set_path(var, path) #:nodoc:
@@ -659,7 +723,7 @@ private
659
723
  raise ArgumentError, "rackup file (#{ru}) not readable"
660
724
 
661
725
  # it could be a .rb file, too, we don't parse those manually
662
- ru =~ /\.ru\z/ or return
726
+ ru.end_with?('.ru') or return
663
727
 
664
728
  /^#\\(.*)/ =~ File.read(ru) or return
665
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