giraffesoft-unicorn 0.93.5

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 (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -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 +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,74 @@
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
+ out.concat(value.split(/\n/).map! { |v| "#{key}: #{v}\r\n" })
51
+ else
52
+ out << "#{key}: #{value}\r\n"
53
+ end
54
+ end
55
+
56
+ # Rack should enforce Content-Length or chunked transfer encoding,
57
+ # so don't worry or care about them.
58
+ # Date is required by HTTP/1.1 as long as our clock can be trusted.
59
+ # Some broken clients require a "Status" header so we accomodate them
60
+ socket.write("HTTP/1.1 #{status}\r\n" \
61
+ "Date: #{Time.now.httpdate}\r\n" \
62
+ "Status: #{status}\r\n" \
63
+ "Connection: close\r\n" \
64
+ "#{out.join('')}\r\n")
65
+ end
66
+
67
+ body.each { |chunk| socket.write(chunk) }
68
+ socket.close # flushes and uncorks the socket immediately
69
+ ensure
70
+ body.respond_to?(:close) and body.close
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ $stdin.sync = $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!
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
+ exit if fork
29
+ Process.setsid
30
+ exit if fork
31
+
32
+ # $stderr/$stderr can/will be redirected separately in the Unicorn config
33
+ Unicorn::Configurator::DEFAULTS[:stderr_path] = "/dev/null"
34
+ Unicorn::Configurator::DEFAULTS[:stdout_path] = "/dev/null"
35
+ end
36
+ $stdin.sync = $stdout.sync = $stderr.sync = true
37
+ end
38
+
39
+ end
@@ -0,0 +1,138 @@
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
+ TCP_CORK = 3 unless defined?(TCP_CORK)
15
+ when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
16
+ # Do nothing for httpready, just closing a bug when freebsd <= 5.4
17
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
18
+ when /freebsd/
19
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
20
+ # Use the HTTP accept filter if available.
21
+ # The struct made by pack() is defined in /usr/include/sys/socket.h
22
+ # as accept_filter_arg
23
+ unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
24
+ FILTER_ARG = ['httpready', nil].pack('a16a240')
25
+ end
26
+ end
27
+
28
+ def set_tcp_sockopt(sock, opt)
29
+
30
+ # highly portable, but off by default because we don't do keepalive
31
+ if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
32
+ sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0) rescue nil
33
+ end
34
+
35
+ unless (val = opt[:tcp_nopush]).nil?
36
+ val = val ? 1 : 0
37
+ if defined?(TCP_CORK) # Linux
38
+ sock.setsockopt(IPPROTO_TCP, TCP_CORK, val) rescue nil
39
+ elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
40
+ sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val) rescue nil
41
+ end
42
+ end
43
+
44
+ # No good reason to ever have deferred accepts off
45
+ if defined?(TCP_DEFER_ACCEPT)
46
+ sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
47
+ elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
48
+ sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG) rescue nil
49
+ end
50
+ end
51
+
52
+ def set_server_sockopt(sock, opt)
53
+ opt ||= {}
54
+
55
+ TCPSocket === sock and set_tcp_sockopt(sock, opt)
56
+
57
+ if opt[:rcvbuf] || opt[:sndbuf]
58
+ log_buffer_sizes(sock, "before: ")
59
+ sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
60
+ sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
61
+ log_buffer_sizes(sock, " after: ")
62
+ end
63
+ sock.listen(opt[:backlog] || 1024)
64
+ end
65
+
66
+ def log_buffer_sizes(sock, pfx = '')
67
+ respond_to?(:logger) or return
68
+ rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
69
+ sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
70
+ logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
71
+ end
72
+
73
+ # creates a new server, socket. address may be a HOST:PORT or
74
+ # an absolute path to a UNIX socket. address can even be a Socket
75
+ # object in which case it is immediately returned
76
+ def bind_listen(address = '0.0.0.0:8080', opt = {})
77
+ return address unless String === address
78
+
79
+ sock = if address[0] == ?/
80
+ if File.exist?(address)
81
+ if File.socket?(address)
82
+ if self.respond_to?(:logger)
83
+ logger.info "unlinking existing socket=#{address}"
84
+ end
85
+ File.unlink(address)
86
+ else
87
+ raise ArgumentError,
88
+ "socket=#{address} specified but it is not a socket!"
89
+ end
90
+ end
91
+ old_umask = File.umask(0)
92
+ begin
93
+ UNIXServer.new(address)
94
+ ensure
95
+ File.umask(old_umask)
96
+ end
97
+ elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
98
+ TCPServer.new($1, $2.to_i)
99
+ else
100
+ raise ArgumentError, "Don't know how to bind: #{address}"
101
+ end
102
+ set_server_sockopt(sock, opt)
103
+ sock
104
+ end
105
+
106
+ # Returns the configuration name of a socket as a string. sock may
107
+ # be a string value, in which case it is returned as-is
108
+ # Warning: TCP sockets may not always return the name given to it.
109
+ def sock_name(sock)
110
+ case sock
111
+ when String then sock
112
+ when UNIXServer
113
+ Socket.unpack_sockaddr_un(sock.getsockname)
114
+ when TCPServer
115
+ Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
116
+ when Socket
117
+ begin
118
+ Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
119
+ rescue ArgumentError
120
+ Socket.unpack_sockaddr_un(sock.getsockname)
121
+ end
122
+ else
123
+ raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
124
+ end
125
+ end
126
+
127
+ # casts a given Socket to be a TCPServer or UNIXServer
128
+ def server_cast(sock)
129
+ begin
130
+ Socket.unpack_sockaddr_in(sock.getsockname)
131
+ TCPServer.for_fd(sock.fileno)
132
+ rescue ArgumentError
133
+ UNIXServer.for_fd(sock.fileno)
134
+ end
135
+ end
136
+
137
+ end # module SocketHelper
138
+ end # module Unicorn
@@ -0,0 +1,174 @@
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
7
+ # backing store. On the first pass, the input is only read on demand
8
+ # so your Rack application can use input notification (upload progress
9
+ # and like). This should fully conform to the Rack::InputWrapper
10
+ # specification on the public API. This class is intended to be a
11
+ # strict interpretation of Rack::InputWrapper functionality and will
12
+ # not support any deviations from it.
13
+ class TeeInput < Struct.new(:socket, :req, :parser, :buf)
14
+
15
+ def initialize(*args)
16
+ super(*args)
17
+ @size = parser.content_length
18
+ @tmp = @size && @size < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
19
+ @buf2 = buf.dup
20
+ if buf.size > 0
21
+ parser.filter_body(@buf2, buf) and finalize_input
22
+ @tmp.write(@buf2)
23
+ @tmp.seek(0)
24
+ end
25
+ end
26
+
27
+ # returns the size of the input. This is what the Content-Length
28
+ # header value should be, and how large our input is expected to be.
29
+ # For TE:chunked, this requires consuming all of the input stream
30
+ # before returning since there's no other way
31
+ def size
32
+ @size and return @size
33
+
34
+ if socket
35
+ pos = @tmp.pos
36
+ while tee(Const::CHUNK_SIZE, @buf2)
37
+ end
38
+ @tmp.seek(pos)
39
+ end
40
+
41
+ @size = tmp_size
42
+ end
43
+
44
+ # call-seq:
45
+ # ios = env['rack.input']
46
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
47
+ #
48
+ # Reads at most length bytes from the I/O stream, or to the end of
49
+ # file if length is omitted or is nil. length must be a non-negative
50
+ # integer or nil. If the optional buffer argument is present, it
51
+ # must reference a String, which will receive the data.
52
+ #
53
+ # At end of file, it returns nil or "" depend on length.
54
+ # ios.read() and ios.read(nil) returns "".
55
+ # ios.read(length [, buffer]) returns nil.
56
+ #
57
+ # If the Content-Length of the HTTP request is known (as is the common
58
+ # case for POST requests), then ios.read(length [, buffer]) will block
59
+ # until the specified length is read (or it is the last chunk).
60
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
61
+ # ios.read(length [, buffer]) will return immediately if there is
62
+ # any data and only block when nothing is available (providing
63
+ # IO#readpartial semantics).
64
+ def read(*args)
65
+ socket or return @tmp.read(*args)
66
+
67
+ length = args.shift
68
+ if nil == length
69
+ rv = @tmp.read || ""
70
+ while tee(Const::CHUNK_SIZE, @buf2)
71
+ rv << @buf2
72
+ end
73
+ rv
74
+ else
75
+ rv = args.shift || @buf2.dup
76
+ diff = tmp_size - @tmp.pos
77
+ if 0 == diff
78
+ ensure_length(tee(length, rv), length)
79
+ else
80
+ ensure_length(@tmp.read(diff > length ? length : diff, rv), length)
81
+ end
82
+ end
83
+ end
84
+
85
+ # takes zero arguments for strict Rack::Lint compatibility, unlike IO#gets
86
+ def gets
87
+ socket or return @tmp.gets
88
+ nil == $/ and return read
89
+
90
+ orig_size = tmp_size
91
+ if @tmp.pos == orig_size
92
+ tee(Const::CHUNK_SIZE, @buf2) or return nil
93
+ @tmp.seek(orig_size)
94
+ end
95
+
96
+ line = @tmp.gets # cannot be nil here since size > pos
97
+ $/ == line[-$/.size, $/.size] and return line
98
+
99
+ # unlikely, if we got here, then @tmp is at EOF
100
+ begin
101
+ orig_size = @tmp.pos
102
+ tee(Const::CHUNK_SIZE, @buf2) or break
103
+ @tmp.seek(orig_size)
104
+ line << @tmp.gets
105
+ $/ == line[-$/.size, $/.size] and return line
106
+ # @tmp is at EOF again here, retry the loop
107
+ end while true
108
+
109
+ line
110
+ end
111
+
112
+ def each(&block)
113
+ while line = gets
114
+ yield line
115
+ end
116
+
117
+ self # Rack does not specify what the return value is here
118
+ end
119
+
120
+ def rewind
121
+ @tmp.rewind # Rack does not specify what the return value is here
122
+ end
123
+
124
+ private
125
+
126
+ # tees off a +length+ chunk of data from the input into the IO
127
+ # backing store as well as returning it. +buf+ must be specified.
128
+ # returns nil if reading from the input returns nil
129
+ def tee(length, dst)
130
+ unless parser.body_eof?
131
+ begin
132
+ if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
133
+ @tmp.write(dst)
134
+ @tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
135
+ return dst
136
+ end
137
+ rescue EOFError
138
+ end
139
+ end
140
+ finalize_input
141
+ end
142
+
143
+ def finalize_input
144
+ while parser.trailers(req, buf).nil?
145
+ buf << socket.readpartial(Const::CHUNK_SIZE, @buf2)
146
+ end
147
+ self.socket = nil
148
+ end
149
+
150
+ def tmp_size
151
+ StringIO === @tmp ? @tmp.size : @tmp.stat.size
152
+ end
153
+
154
+ # tee()s into +buf+ until it is of +length+ bytes (or until
155
+ # we've reached the Content-Length of the request body).
156
+ # Returns +buf+ (the exact object, not a duplicate)
157
+ # To continue supporting applications that need near-real-time
158
+ # streaming input bodies, this is a no-op for
159
+ # "Transfer-Encoding: chunked" requests.
160
+ def ensure_length(buf, length)
161
+ # @size is nil for chunked bodies, so we can't ensure length for those
162
+ # since they could be streaming bidirectionally and we don't want to
163
+ # block the caller in that case.
164
+ return buf if buf.nil? || @size.nil?
165
+
166
+ while buf.size < length && @size != @tmp.pos
167
+ buf << tee(length - buf.size, @buf2)
168
+ end
169
+
170
+ buf
171
+ end
172
+
173
+ end
174
+ end
@@ -0,0 +1,64 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'fcntl'
4
+ require 'tmpdir'
5
+
6
+ module Unicorn
7
+ class Util
8
+ class << self
9
+
10
+ # This reopens ALL logfiles in the process that have been rotated
11
+ # using logrotate(8) (without copytruncate) or similar tools.
12
+ # A +File+ object is considered for reopening if it is:
13
+ # 1) opened with the O_APPEND and O_WRONLY flags
14
+ # 2) opened with an absolute path (starts with "/")
15
+ # 3) the current open file handle does not match its original open path
16
+ # 4) unbuffered (as far as userspace buffering goes, not O_SYNC)
17
+ # Returns the number of files reopened
18
+ def reopen_logs
19
+ nr = 0
20
+ append_flags = File::WRONLY | File::APPEND
21
+
22
+ ObjectSpace.each_object(File) do |fp|
23
+ next if fp.closed?
24
+ next unless (fp.sync && fp.path[0..0] == "/")
25
+ next unless (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
26
+
27
+ begin
28
+ a, b = fp.stat, File.stat(fp.path)
29
+ next if a.ino == b.ino && a.dev == b.dev
30
+ rescue Errno::ENOENT
31
+ end
32
+
33
+ open_arg = 'a'
34
+ if fp.respond_to?(:external_encoding) && enc = fp.external_encoding
35
+ open_arg << ":#{enc.to_s}"
36
+ enc = fp.internal_encoding and open_arg << ":#{enc.to_s}"
37
+ end
38
+ fp.reopen(fp.path, open_arg)
39
+ fp.sync = true
40
+ nr += 1
41
+ end # each_object
42
+ nr
43
+ end
44
+
45
+ # creates and returns a new File object. The File is unlinked
46
+ # immediately, switched to binary mode, and userspace output
47
+ # buffering is disabled
48
+ def tmpio
49
+ fp = begin
50
+ File.open("#{Dir::tmpdir}/#{rand}",
51
+ File::RDWR|File::CREAT|File::EXCL, 0600)
52
+ rescue Errno::EEXIST
53
+ retry
54
+ end
55
+ File.unlink(fp.path)
56
+ fp.binmode
57
+ fp.sync = true
58
+ fp
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end