unicorn-fotopedia 0.99.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +19 -0
  3. data/.gitignore +21 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +32 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +171 -0
  11. data/Documentation/unicorn_rails.1.txt +172 -0
  12. data/FAQ +52 -0
  13. data/GIT-VERSION-GEN +40 -0
  14. data/GNUmakefile +292 -0
  15. data/HACKING +116 -0
  16. data/ISSUES +36 -0
  17. data/KNOWN_ISSUES +50 -0
  18. data/LICENSE +55 -0
  19. data/PHILOSOPHY +145 -0
  20. data/README +149 -0
  21. data/Rakefile +191 -0
  22. data/SIGNALS +109 -0
  23. data/Sandbox +78 -0
  24. data/TODO +5 -0
  25. data/TUNING +70 -0
  26. data/bin/unicorn +126 -0
  27. data/bin/unicorn_rails +203 -0
  28. data/examples/big_app_gc.rb +33 -0
  29. data/examples/echo.ru +27 -0
  30. data/examples/git.ru +13 -0
  31. data/examples/init.sh +58 -0
  32. data/examples/logger_mp_safe.rb +25 -0
  33. data/examples/nginx.conf +139 -0
  34. data/examples/unicorn.conf.rb +78 -0
  35. data/ext/unicorn_http/CFLAGS +13 -0
  36. data/ext/unicorn_http/c_util.h +124 -0
  37. data/ext/unicorn_http/common_field_optimization.h +111 -0
  38. data/ext/unicorn_http/ext_help.h +77 -0
  39. data/ext/unicorn_http/extconf.rb +14 -0
  40. data/ext/unicorn_http/global_variables.h +89 -0
  41. data/ext/unicorn_http/unicorn_http.rl +714 -0
  42. data/ext/unicorn_http/unicorn_http_common.rl +75 -0
  43. data/lib/unicorn.rb +847 -0
  44. data/lib/unicorn/app/exec_cgi.rb +150 -0
  45. data/lib/unicorn/app/inetd.rb +109 -0
  46. data/lib/unicorn/app/old_rails.rb +33 -0
  47. data/lib/unicorn/app/old_rails/static.rb +58 -0
  48. data/lib/unicorn/cgi_wrapper.rb +145 -0
  49. data/lib/unicorn/configurator.rb +421 -0
  50. data/lib/unicorn/const.rb +34 -0
  51. data/lib/unicorn/http_request.rb +72 -0
  52. data/lib/unicorn/http_response.rb +75 -0
  53. data/lib/unicorn/launcher.rb +65 -0
  54. data/lib/unicorn/oob_gc.rb +58 -0
  55. data/lib/unicorn/socket_helper.rb +152 -0
  56. data/lib/unicorn/tee_input.rb +217 -0
  57. data/lib/unicorn/util.rb +90 -0
  58. data/local.mk.sample +62 -0
  59. data/setup.rb +1586 -0
  60. data/t/.gitignore +2 -0
  61. data/t/GNUmakefile +67 -0
  62. data/t/README +42 -0
  63. data/t/bin/content-md5-put +36 -0
  64. data/t/bin/sha1sum.rb +23 -0
  65. data/t/bin/unused_listen +40 -0
  66. data/t/bin/utee +12 -0
  67. data/t/env.ru +3 -0
  68. data/t/my-tap-lib.sh +200 -0
  69. data/t/t0000-http-basic.sh +50 -0
  70. data/t/t0001-reload-bad-config.sh +52 -0
  71. data/t/t0002-config-conflict.sh +49 -0
  72. data/t/test-lib.sh +100 -0
  73. data/test/aggregate.rb +15 -0
  74. data/test/benchmark/README +50 -0
  75. data/test/benchmark/dd.ru +18 -0
  76. data/test/exec/README +5 -0
  77. data/test/exec/test_exec.rb +1038 -0
  78. data/test/rails/app-1.2.3/.gitignore +2 -0
  79. data/test/rails/app-1.2.3/Rakefile +7 -0
  80. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  81. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  82. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  83. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  84. data/test/rails/app-1.2.3/config/database.yml +12 -0
  85. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  86. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  87. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  88. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  89. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  90. data/test/rails/app-1.2.3/public/404.html +1 -0
  91. data/test/rails/app-1.2.3/public/500.html +1 -0
  92. data/test/rails/app-2.0.2/.gitignore +2 -0
  93. data/test/rails/app-2.0.2/Rakefile +7 -0
  94. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  95. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  96. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  97. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  98. data/test/rails/app-2.0.2/config/database.yml +12 -0
  99. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  100. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  101. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  102. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  103. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  104. data/test/rails/app-2.0.2/public/404.html +1 -0
  105. data/test/rails/app-2.0.2/public/500.html +1 -0
  106. data/test/rails/app-2.1.2/.gitignore +2 -0
  107. data/test/rails/app-2.1.2/Rakefile +7 -0
  108. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  109. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  110. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  111. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  112. data/test/rails/app-2.1.2/config/database.yml +12 -0
  113. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  114. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  115. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  116. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  117. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  118. data/test/rails/app-2.1.2/public/404.html +1 -0
  119. data/test/rails/app-2.1.2/public/500.html +1 -0
  120. data/test/rails/app-2.2.2/.gitignore +2 -0
  121. data/test/rails/app-2.2.2/Rakefile +7 -0
  122. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  123. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  124. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  125. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  126. data/test/rails/app-2.2.2/config/database.yml +12 -0
  127. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  128. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  129. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  130. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  131. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  132. data/test/rails/app-2.2.2/public/404.html +1 -0
  133. data/test/rails/app-2.2.2/public/500.html +1 -0
  134. data/test/rails/app-2.3.5/.gitignore +2 -0
  135. data/test/rails/app-2.3.5/Rakefile +7 -0
  136. data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
  137. data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
  138. data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
  139. data/test/rails/app-2.3.5/config/boot.rb +109 -0
  140. data/test/rails/app-2.3.5/config/database.yml +12 -0
  141. data/test/rails/app-2.3.5/config/environment.rb +17 -0
  142. data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
  143. data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
  144. data/test/rails/app-2.3.5/config/routes.rb +6 -0
  145. data/test/rails/app-2.3.5/db/.gitignore +0 -0
  146. data/test/rails/app-2.3.5/public/404.html +1 -0
  147. data/test/rails/app-2.3.5/public/500.html +1 -0
  148. data/test/rails/app-2.3.5/public/x.txt +1 -0
  149. data/test/rails/test_rails.rb +280 -0
  150. data/test/test_helper.rb +301 -0
  151. data/test/unit/test_configurator.rb +150 -0
  152. data/test/unit/test_http_parser.rb +555 -0
  153. data/test/unit/test_http_parser_ng.rb +443 -0
  154. data/test/unit/test_request.rb +184 -0
  155. data/test/unit/test_response.rb +110 -0
  156. data/test/unit/test_server.rb +291 -0
  157. data/test/unit/test_signals.rb +206 -0
  158. data/test/unit/test_socket_helper.rb +147 -0
  159. data/test/unit/test_tee_input.rb +257 -0
  160. data/test/unit/test_upload.rb +298 -0
  161. data/test/unit/test_util.rb +96 -0
  162. data/unicorn.gemspec +52 -0
  163. metadata +283 -0
@@ -0,0 +1,72 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'stringio'
4
+ require 'unicorn_http'
5
+
6
+ module Unicorn
7
+ class HttpRequest
8
+
9
+ # default parameters we merge into the request env for Rack handlers
10
+ DEFAULTS = {
11
+ "rack.errors" => $stderr,
12
+ "rack.multiprocess" => true,
13
+ "rack.multithread" => false,
14
+ "rack.run_once" => false,
15
+ "rack.version" => [1, 1],
16
+ "SCRIPT_NAME" => "",
17
+
18
+ # this is not in the Rack spec, but some apps may rely on it
19
+ "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
20
+ }
21
+
22
+ NULL_IO = StringIO.new("")
23
+ LOCALHOST = '127.0.0.1'
24
+
25
+ # Being explicitly single-threaded, we have certain advantages in
26
+ # not having to worry about variables being clobbered :)
27
+ BUF = ""
28
+ PARSER = HttpParser.new
29
+ REQ = {}
30
+
31
+ # Does the majority of the IO processing. It has been written in
32
+ # Ruby using about 8 different IO processing strategies.
33
+ #
34
+ # It is currently carefully constructed to make sure that it gets
35
+ # the best possible performance for the common case: GET requests
36
+ # that are fully complete after a single read(2)
37
+ #
38
+ # Anyone who thinks they can make it faster is more than welcome to
39
+ # take a crack at it.
40
+ #
41
+ # returns an environment hash suitable for Rack if successful
42
+ # This does minimal exception trapping and it is up to the caller
43
+ # to handle any socket errors (e.g. user aborted upload).
44
+ def read(socket)
45
+ REQ.clear
46
+ PARSER.reset
47
+
48
+ # From http://www.ietf.org/rfc/rfc3875:
49
+ # "Script authors should be aware that the REMOTE_ADDR and
50
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
51
+ # may not identify the ultimate source of the request. They
52
+ # identify the client for the immediate request to the server;
53
+ # that client may be a proxy, gateway, or other intermediary
54
+ # acting on behalf of the actual source client."
55
+ REQ[Const::REMOTE_ADDR] =
56
+ TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
57
+
58
+ # short circuit the common case with small GET requests first
59
+ if PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)).nil?
60
+ # Parser is not done, queue up more data to read and continue parsing
61
+ # an Exception thrown from the PARSER will throw us out of the loop
62
+ begin
63
+ BUF << socket.readpartial(Const::CHUNK_SIZE)
64
+ end while PARSER.headers(REQ, BUF).nil?
65
+ end
66
+ REQ[Const::RACK_INPUT] = 0 == PARSER.content_length ?
67
+ NULL_IO : Unicorn::TeeInput.new(socket, REQ, PARSER, BUF)
68
+ REQ.update(DEFAULTS)
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,75 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'time'
4
+
5
+ module Unicorn
6
+ # Writes a Rack response to your client using the HTTP/1.1 specification.
7
+ # You use it by simply doing:
8
+ #
9
+ # status, headers, body = rack_app.call(env)
10
+ # HttpResponse.write(socket, [ status, headers, body ])
11
+ #
12
+ # Most header correctness (including Content-Length and Content-Type)
13
+ # is the job of Rack, with the exception of the "Connection: close"
14
+ # and "Date" headers.
15
+ #
16
+ # A design decision was made to force the client to not pipeline or
17
+ # keepalive requests. HTTP/1.1 pipelining really kills the
18
+ # performance due to how it has to be handled and how unclear the
19
+ # standard is. To fix this the HttpResponse always gives a
20
+ # "Connection: close" header which forces the client to close right
21
+ # away. The bonus for this is that it gives a pretty nice speed boost
22
+ # to most clients since they can close their connection immediately.
23
+
24
+ class HttpResponse
25
+
26
+ # Every standard HTTP code mapped to the appropriate message.
27
+ CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
28
+ hash[code] = "#{code} #{msg}"
29
+ hash
30
+ }
31
+
32
+ # Rack does not set/require a Date: header. We always override the
33
+ # Connection: and Date: headers no matter what (if anything) our
34
+ # Rack application sent us.
35
+ SKIP = { 'connection' => true, 'date' => true, 'status' => true }
36
+
37
+ # writes the rack_response to socket as an HTTP response
38
+ def self.write(socket, rack_response, have_header = true)
39
+ status, headers, body = rack_response
40
+
41
+ if have_header
42
+ status = CODES[status.to_i] || status
43
+ out = []
44
+
45
+ # Don't bother enforcing duplicate supression, it's a Hash most of
46
+ # the time anyways so just hope our app knows what it's doing
47
+ headers.each do |key, value|
48
+ next if SKIP.include?(key.downcase)
49
+ if value =~ /\n/
50
+ # avoiding blank, key-only cookies with /\n+/
51
+ out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
52
+ else
53
+ out << "#{key}: #{value}\r\n"
54
+ end
55
+ end
56
+
57
+ # Rack should enforce Content-Length or chunked transfer encoding,
58
+ # so don't worry or care about them.
59
+ # Date is required by HTTP/1.1 as long as our clock can be trusted.
60
+ # Some broken clients require a "Status" header so we accomodate them
61
+ socket.write("HTTP/1.1 #{status}\r\n" \
62
+ "Date: #{Time.now.httpdate}\r\n" \
63
+ "Status: #{status}\r\n" \
64
+ "Connection: close\r\n" \
65
+ "#{out.join('')}\r\n")
66
+ end
67
+
68
+ body.each { |chunk| socket.write(chunk) }
69
+ socket.close # flushes and uncorks the socket immediately
70
+ ensure
71
+ body.respond_to?(:close) and body.close
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ $stdout.sync = $stderr.sync = true
4
+ $stdin.binmode
5
+ $stdout.binmode
6
+ $stderr.binmode
7
+
8
+ require 'unicorn'
9
+
10
+ class Unicorn::Launcher
11
+
12
+ # We don't do a lot of standard daemonization stuff:
13
+ # * umask is whatever was set by the parent process at startup
14
+ # and can be set in config.ru and config_file, so making it
15
+ # 0000 and potentially exposing sensitive log data can be bad
16
+ # policy.
17
+ # * don't bother to chdir("/") here since unicorn is designed to
18
+ # run inside APP_ROOT. Unicorn will also re-chdir() to
19
+ # the directory it was started in when being re-executed
20
+ # to pickup code changes if the original deployment directory
21
+ # is a symlink or otherwise got replaced.
22
+ def self.daemonize!(options = nil)
23
+ $stdin.reopen("/dev/null")
24
+
25
+ # We only start a new process group if we're not being reexecuted
26
+ # and inheriting file descriptors from our parent
27
+ unless ENV['UNICORN_FD']
28
+ if options
29
+ # grandparent - reads pipe, exits when master is ready
30
+ # \_ parent - exits immediately ASAP
31
+ # \_ unicorn master - writes to pipe when ready
32
+
33
+ rd, wr = IO.pipe
34
+ grandparent = $$
35
+ if fork
36
+ wr.close # grandparent does not write
37
+ else
38
+ rd.close # unicorn master does not read
39
+ Process.setsid
40
+ exit if fork # parent dies now
41
+ end
42
+
43
+ if grandparent == $$
44
+ # this will block until HttpServer#join runs (or it dies)
45
+ master_pid = (rd.readpartial(16) rescue nil).to_i
46
+ unless master_pid > 1
47
+ warn "master failed to start, check stderr log for details"
48
+ exit!(1)
49
+ end
50
+ exit 0
51
+ else # unicorn master process
52
+ options[:ready_pipe] = wr
53
+ end
54
+ else # backwards compat
55
+ exit if fork
56
+ Process.setsid
57
+ exit if fork
58
+ end
59
+ # $stderr/$stderr can/will be redirected separately in the Unicorn config
60
+ Unicorn::Configurator::DEFAULTS[:stderr_path] = "/dev/null"
61
+ Unicorn::Configurator::DEFAULTS[:stdout_path] = "/dev/null"
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,58 @@
1
+ # -*- encoding: binary -*-
2
+ module Unicorn
3
+
4
+ # Run GC after every request, after closing the client socket and
5
+ # before attempting to accept more connections.
6
+ #
7
+ # This shouldn't hurt overall performance as long as the server cluster
8
+ # is at <50% CPU capacity, and improves the performance of most memory
9
+ # intensive requests. This serves to improve _client-visible_
10
+ # performance (possibly at the cost of overall performance).
11
+ #
12
+ # We'll call GC after each request is been written out to the socket, so
13
+ # the client never sees the extra GC hit it.
14
+ #
15
+ # This middleware is _only_ effective for applications that use a lot
16
+ # of memory, and will hurt simpler apps/endpoints that can process
17
+ # multiple requests before incurring GC.
18
+ #
19
+ # This middleware is only designed to work with Unicorn, as it harms
20
+ # keepalive performance.
21
+ #
22
+ # Example (in config.ru):
23
+ #
24
+ # require 'unicorn/oob_gc'
25
+ #
26
+ # # GC ever two requests that hit /expensive/foo or /more_expensive/foo
27
+ # # in your app. By default, this will GC once every 5 requests
28
+ # # for all endpoints in your app
29
+ # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
30
+ class OobGC < Struct.new(:app, :interval, :path, :nr, :env, :body)
31
+
32
+ def initialize(app, interval = 5, path = %r{\A/})
33
+ super(app, interval, path, interval)
34
+ end
35
+
36
+ def call(env)
37
+ status, headers, self.body = app.call(self.env = env)
38
+ [ status, headers, self ]
39
+ end
40
+
41
+ def each(&block)
42
+ body.each(&block)
43
+ end
44
+
45
+ # in Unicorn, this is closed _after_ the client socket
46
+ def close
47
+ body.close if body.respond_to?(:close)
48
+
49
+ if path =~ env['PATH_INFO'] && ((self.nr -= 1) <= 0)
50
+ self.nr = interval
51
+ self.body = nil
52
+ env.clear
53
+ GC.start
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,152 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'socket'
4
+
5
+ module Unicorn
6
+ module SocketHelper
7
+ include Socket::Constants
8
+
9
+ # configure platform-specific options (only tested on Linux 2.6 so far)
10
+ case RUBY_PLATFORM
11
+ when /linux/
12
+ # from /usr/include/linux/tcp.h
13
+ TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
14
+
15
+ # do not send out partial frames (Linux)
16
+ TCP_CORK = 3 unless defined?(TCP_CORK)
17
+ when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
18
+ # Do nothing for httpready, just closing a bug when freebsd <= 5.4
19
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) # :nodoc:
20
+ when /freebsd/
21
+ # do not send out partial frames (FreeBSD)
22
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
23
+
24
+ # Use the HTTP accept filter if available.
25
+ # The struct made by pack() is defined in /usr/include/sys/socket.h
26
+ # as accept_filter_arg
27
+ unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
28
+ # set set the "httpready" accept filter in FreeBSD if available
29
+ # if other protocols are to be supported, this may be
30
+ # String#replace-d with "dataready" arguments instead
31
+ FILTER_ARG = ['httpready', nil].pack('a16a240')
32
+ end
33
+ end
34
+
35
+ def set_tcp_sockopt(sock, opt)
36
+
37
+ # highly portable, but off by default because we don't do keepalive
38
+ if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
39
+ sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
40
+ end
41
+
42
+ unless (val = opt[:tcp_nopush]).nil?
43
+ val = val ? 1 : 0
44
+ if defined?(TCP_CORK) # Linux
45
+ sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
46
+ elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
47
+ sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
48
+ end
49
+ end
50
+
51
+ # No good reason to ever have deferred accepts off
52
+ if defined?(TCP_DEFER_ACCEPT)
53
+ sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
54
+ elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
55
+ sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG)
56
+ end
57
+ end
58
+
59
+ def set_server_sockopt(sock, opt)
60
+ opt ||= {}
61
+
62
+ TCPSocket === sock and set_tcp_sockopt(sock, opt)
63
+
64
+ if opt[:rcvbuf] || opt[:sndbuf]
65
+ log_buffer_sizes(sock, "before: ")
66
+ sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
67
+ sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
68
+ log_buffer_sizes(sock, " after: ")
69
+ end
70
+ sock.listen(opt[:backlog] || 1024)
71
+ rescue => e
72
+ if respond_to?(:logger)
73
+ logger.error "error setting socket options: #{e.inspect}"
74
+ logger.error e.backtrace.join("\n")
75
+ end
76
+ end
77
+
78
+ def log_buffer_sizes(sock, pfx = '')
79
+ respond_to?(:logger) or return
80
+ rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
81
+ sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
82
+ logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
83
+ end
84
+
85
+ # creates a new server, socket. address may be a HOST:PORT or
86
+ # an absolute path to a UNIX socket. address can even be a Socket
87
+ # object in which case it is immediately returned
88
+ def bind_listen(address = '0.0.0.0:8080', opt = {})
89
+ return address unless String === address
90
+
91
+ sock = if address[0] == ?/
92
+ if File.exist?(address)
93
+ if File.socket?(address)
94
+ if self.respond_to?(:logger)
95
+ logger.info "unlinking existing socket=#{address}"
96
+ end
97
+ File.unlink(address)
98
+ else
99
+ raise ArgumentError,
100
+ "socket=#{address} specified but it is not a socket!"
101
+ end
102
+ end
103
+ old_umask = File.umask(opt[:umask] || 0)
104
+ begin
105
+ UNIXServer.new(address)
106
+ ensure
107
+ File.umask(old_umask)
108
+ end
109
+ elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
110
+ TCPServer.new($1, $2.to_i)
111
+ else
112
+ raise ArgumentError, "Don't know how to bind: #{address}"
113
+ end
114
+ set_server_sockopt(sock, opt)
115
+ sock
116
+ end
117
+
118
+ # Returns the configuration name of a socket as a string. sock may
119
+ # be a string value, in which case it is returned as-is
120
+ # Warning: TCP sockets may not always return the name given to it.
121
+ def sock_name(sock)
122
+ case sock
123
+ when String then sock
124
+ when UNIXServer
125
+ Socket.unpack_sockaddr_un(sock.getsockname)
126
+ when TCPServer
127
+ Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
128
+ when Socket
129
+ begin
130
+ Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
131
+ rescue ArgumentError
132
+ Socket.unpack_sockaddr_un(sock.getsockname)
133
+ end
134
+ else
135
+ raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
136
+ end
137
+ end
138
+
139
+ module_function :sock_name
140
+
141
+ # casts a given Socket to be a TCPServer or UNIXServer
142
+ def server_cast(sock)
143
+ begin
144
+ Socket.unpack_sockaddr_in(sock.getsockname)
145
+ TCPServer.for_fd(sock.fileno)
146
+ rescue ArgumentError
147
+ UNIXServer.for_fd(sock.fileno)
148
+ end
149
+ end
150
+
151
+ end # module SocketHelper
152
+ end # module Unicorn
@@ -0,0 +1,217 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module Unicorn
4
+
5
+ # acts like tee(1) on an input input to provide a input-like stream
6
+ # while providing rewindable semantics through a File/StringIO backing
7
+ # store. On the first pass, the input is only read on demand so your
8
+ # Rack application can use input notification (upload progress and
9
+ # like). This should fully conform to the Rack::Lint::InputWrapper
10
+ # specification on the public API. This class is intended to be a
11
+ # strict interpretation of Rack::Lint::InputWrapper functionality and
12
+ # will not support any deviations from it.
13
+ #
14
+ # When processing uploads, Unicorn exposes a TeeInput object under
15
+ # "rack.input" of the Rack environment.
16
+ class TeeInput < Struct.new(:socket, :req, :parser, :buf, :len, :tmp, :buf2)
17
+
18
+ # Initializes a new TeeInput object. You normally do not have to call
19
+ # this unless you are writing an HTTP server.
20
+ def initialize(*args)
21
+ super(*args)
22
+ self.len = parser.content_length
23
+ self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
24
+ self.buf2 = ""
25
+ if buf.size > 0
26
+ parser.filter_body(buf2, buf) and finalize_input
27
+ tmp.write(buf2)
28
+ tmp.seek(0)
29
+ end
30
+ end
31
+
32
+ # :call-seq:
33
+ # ios.size => Integer
34
+ #
35
+ # Returns the size of the input. For requests with a Content-Length
36
+ # header value, this will not read data off the socket and just return
37
+ # the value of the Content-Length header as an Integer.
38
+ #
39
+ # For Transfer-Encoding:chunked requests, this requires consuming
40
+ # all of the input stream before returning since there's no other
41
+ # way to determine the size of the request body beforehand.
42
+ def size
43
+ len and return len
44
+
45
+ if socket
46
+ pos = tmp.pos
47
+ while tee(Const::CHUNK_SIZE, buf2)
48
+ end
49
+ tmp.seek(pos)
50
+ end
51
+
52
+ self.len = tmp.size
53
+ end
54
+
55
+ # :call-seq:
56
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
57
+ #
58
+ # Reads at most length bytes from the I/O stream, or to the end of
59
+ # file if length is omitted or is nil. length must be a non-negative
60
+ # integer or nil. If the optional buffer argument is present, it
61
+ # must reference a String, which will receive the data.
62
+ #
63
+ # At end of file, it returns nil or "" depend on length.
64
+ # ios.read() and ios.read(nil) returns "".
65
+ # ios.read(length [, buffer]) returns nil.
66
+ #
67
+ # If the Content-Length of the HTTP request is known (as is the common
68
+ # case for POST requests), then ios.read(length [, buffer]) will block
69
+ # until the specified length is read (or it is the last chunk).
70
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
71
+ # ios.read(length [, buffer]) will return immediately if there is
72
+ # any data and only block when nothing is available (providing
73
+ # IO#readpartial semantics).
74
+ def read(*args)
75
+ socket or return tmp.read(*args)
76
+
77
+ length = args.shift
78
+ if nil == length
79
+ rv = tmp.read || ""
80
+ while tee(Const::CHUNK_SIZE, buf2)
81
+ rv << buf2
82
+ end
83
+ rv
84
+ else
85
+ rv = args.shift || ""
86
+ diff = tmp.size - tmp.pos
87
+ if 0 == diff
88
+ ensure_length(tee(length, rv), length)
89
+ else
90
+ ensure_length(tmp.read(diff > length ? length : diff, rv), length)
91
+ end
92
+ end
93
+ end
94
+
95
+ # :call-seq:
96
+ # ios.gets => string or nil
97
+ #
98
+ # Reads the next ``line'' from the I/O stream; lines are separated
99
+ # by the global record separator ($/, typically "\n"). A global
100
+ # record separator of nil reads the entire unread contents of ios.
101
+ # Returns nil if called at the end of file.
102
+ # This takes zero arguments for strict Rack::Lint compatibility,
103
+ # unlike IO#gets.
104
+ def gets
105
+ socket or return tmp.gets
106
+ nil == $/ and return read
107
+
108
+ orig_size = tmp.size
109
+ if tmp.pos == orig_size
110
+ tee(Const::CHUNK_SIZE, buf2) or return nil
111
+ tmp.seek(orig_size)
112
+ end
113
+
114
+ line = tmp.gets # cannot be nil here since size > pos
115
+ $/ == line[-$/.size, $/.size] and return line
116
+
117
+ # unlikely, if we got here, then tmp is at EOF
118
+ begin
119
+ orig_size = tmp.pos
120
+ tee(Const::CHUNK_SIZE, buf2) or break
121
+ tmp.seek(orig_size)
122
+ line << tmp.gets
123
+ $/ == line[-$/.size, $/.size] and return line
124
+ # tmp is at EOF again here, retry the loop
125
+ end while true
126
+
127
+ line
128
+ end
129
+
130
+ # :call-seq:
131
+ # ios.each { |line| block } => ios
132
+ #
133
+ # Executes the block for every ``line'' in *ios*, where lines are
134
+ # separated by the global record separator ($/, typically "\n").
135
+ def each(&block)
136
+ while line = gets
137
+ yield line
138
+ end
139
+
140
+ self # Rack does not specify what the return value is here
141
+ end
142
+
143
+ # :call-seq:
144
+ # ios.rewind => 0
145
+ #
146
+ # Positions the *ios* pointer to the beginning of input, returns
147
+ # the offset (zero) of the +ios+ pointer. Subsequent reads will
148
+ # start from the beginning of the previously-buffered input.
149
+ def rewind
150
+ tmp.rewind # Rack does not specify what the return value is here
151
+ end
152
+
153
+ private
154
+
155
+ def client_error(e)
156
+ case e
157
+ when EOFError
158
+ # in case client only did a premature shutdown(SHUT_WR)
159
+ # we do support clients that shutdown(SHUT_WR) after the
160
+ # _entire_ request has been sent, and those will not have
161
+ # raised EOFError on us.
162
+ socket.close if socket
163
+ raise ClientShutdown, "bytes_read=#{tmp.size}", []
164
+ when HttpParserError
165
+ e.set_backtrace([])
166
+ end
167
+ raise e
168
+ end
169
+
170
+ # tees off a +length+ chunk of data from the input into the IO
171
+ # backing store as well as returning it. +dst+ must be specified.
172
+ # returns nil if reading from the input returns nil
173
+ def tee(length, dst)
174
+ unless parser.body_eof?
175
+ if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
176
+ tmp.write(dst)
177
+ tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
178
+ return dst
179
+ end
180
+ end
181
+ finalize_input
182
+ rescue => e
183
+ client_error(e)
184
+ end
185
+
186
+ def finalize_input
187
+ while parser.trailers(req, buf).nil?
188
+ # Don't worry about raising ClientShutdown here on EOFError, tee()
189
+ # will catch EOFError when app is processing it, otherwise in
190
+ # initialize we never get any chance to enter the app so the
191
+ # EOFError will just get trapped by Unicorn and not the Rack app
192
+ buf << socket.readpartial(Const::CHUNK_SIZE)
193
+ end
194
+ self.socket = nil
195
+ end
196
+
197
+ # tee()s into +dst+ until it is of +length+ bytes (or until
198
+ # we've reached the Content-Length of the request body).
199
+ # Returns +dst+ (the exact object, not a duplicate)
200
+ # To continue supporting applications that need near-real-time
201
+ # streaming input bodies, this is a no-op for
202
+ # "Transfer-Encoding: chunked" requests.
203
+ def ensure_length(dst, length)
204
+ # len is nil for chunked bodies, so we can't ensure length for those
205
+ # since they could be streaming bidirectionally and we don't want to
206
+ # block the caller in that case.
207
+ return dst if dst.nil? || len.nil?
208
+
209
+ while dst.size < length && tee(length - dst.size, buf2)
210
+ dst << buf2
211
+ end
212
+
213
+ dst
214
+ end
215
+
216
+ end
217
+ end