giraffesoft-unicorn 0.93.5

Sign up to get free protection for your applications and to get access to all the features.
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