unicorn-fotopedia 0.99.1

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 (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