unicorn-rupcio 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +144 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +318 -0
  20. data/HACKING +117 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LICENSE +67 -0
  24. data/Links +58 -0
  25. data/PHILOSOPHY +139 -0
  26. data/README +165 -0
  27. data/Rakefile +17 -0
  28. data/SIGNALS +123 -0
  29. data/Sandbox +104 -0
  30. data/TODO +1 -0
  31. data/TUNING +119 -0
  32. data/archive/.gitignore +3 -0
  33. data/archive/slrnpull.conf +4 -0
  34. data/bin/unicorn +129 -0
  35. data/bin/unicorn_rails +210 -0
  36. data/examples/big_app_gc.rb +3 -0
  37. data/examples/echo.ru +27 -0
  38. data/examples/init.sh +102 -0
  39. data/examples/logger_mp_safe.rb +26 -0
  40. data/examples/logrotate.conf +44 -0
  41. data/examples/nginx.conf +156 -0
  42. data/examples/unicorn.conf.minimal.rb +14 -0
  43. data/examples/unicorn.conf.rb +111 -0
  44. data/examples/unicorn.socket +11 -0
  45. data/examples/unicorn@.service +40 -0
  46. data/ext/unicorn_http/CFLAGS +13 -0
  47. data/ext/unicorn_http/c_util.h +115 -0
  48. data/ext/unicorn_http/common_field_optimization.h +128 -0
  49. data/ext/unicorn_http/epollexclusive.h +128 -0
  50. data/ext/unicorn_http/ext_help.h +38 -0
  51. data/ext/unicorn_http/extconf.rb +40 -0
  52. data/ext/unicorn_http/global_variables.h +97 -0
  53. data/ext/unicorn_http/httpdate.c +91 -0
  54. data/ext/unicorn_http/unicorn_http.c +4348 -0
  55. data/ext/unicorn_http/unicorn_http.rl +1054 -0
  56. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  57. data/lib/unicorn/app/old_rails/static.rb +60 -0
  58. data/lib/unicorn/app/old_rails.rb +36 -0
  59. data/lib/unicorn/cgi_wrapper.rb +148 -0
  60. data/lib/unicorn/configurator.rb +749 -0
  61. data/lib/unicorn/const.rb +22 -0
  62. data/lib/unicorn/http_request.rb +180 -0
  63. data/lib/unicorn/http_response.rb +95 -0
  64. data/lib/unicorn/http_server.rb +860 -0
  65. data/lib/unicorn/launcher.rb +63 -0
  66. data/lib/unicorn/oob_gc.rb +82 -0
  67. data/lib/unicorn/preread_input.rb +34 -0
  68. data/lib/unicorn/select_waiter.rb +7 -0
  69. data/lib/unicorn/socket_helper.rb +186 -0
  70. data/lib/unicorn/stream_input.rb +152 -0
  71. data/lib/unicorn/tee_input.rb +132 -0
  72. data/lib/unicorn/tmpio.rb +34 -0
  73. data/lib/unicorn/util.rb +91 -0
  74. data/lib/unicorn/version.rb +1 -0
  75. data/lib/unicorn/worker.rb +166 -0
  76. data/lib/unicorn.rb +137 -0
  77. data/man/man1/unicorn.1 +222 -0
  78. data/man/man1/unicorn_rails.1 +207 -0
  79. data/setup.rb +1587 -0
  80. data/t/.gitignore +4 -0
  81. data/t/GNUmakefile +5 -0
  82. data/t/README +49 -0
  83. data/t/active-unix-socket.t +110 -0
  84. data/t/back-out-of-upgrade.t +44 -0
  85. data/t/bin/unused_listen +40 -0
  86. data/t/client_body_buffer_size.ru +15 -0
  87. data/t/client_body_buffer_size.t +79 -0
  88. data/t/detach.ru +12 -0
  89. data/t/env.ru +4 -0
  90. data/t/fails-rack-lint.ru +6 -0
  91. data/t/heartbeat-timeout.ru +13 -0
  92. data/t/heartbeat-timeout.t +60 -0
  93. data/t/integration.ru +129 -0
  94. data/t/integration.t +509 -0
  95. data/t/lib.perl +309 -0
  96. data/t/listener_names.ru +5 -0
  97. data/t/my-tap-lib.sh +201 -0
  98. data/t/oob_gc.ru +18 -0
  99. data/t/oob_gc_path.ru +18 -0
  100. data/t/pid.ru +4 -0
  101. data/t/preread_input.ru +23 -0
  102. data/t/reload-bad-config.t +49 -0
  103. data/t/reopen-logs.ru +14 -0
  104. data/t/reopen-logs.t +36 -0
  105. data/t/t0010-reap-logging.sh +55 -0
  106. data/t/t0012-reload-empty-config.sh +86 -0
  107. data/t/t0013-rewindable-input-false.sh +24 -0
  108. data/t/t0013.ru +13 -0
  109. data/t/t0014-rewindable-input-true.sh +24 -0
  110. data/t/t0014.ru +13 -0
  111. data/t/t0015-configurator-internals.sh +25 -0
  112. data/t/t0020-at_exit-handler.sh +49 -0
  113. data/t/t0021-process_detach.sh +29 -0
  114. data/t/t0022-listener_names-preload_app.sh +32 -0
  115. data/t/t0300-no-default-middleware.sh +20 -0
  116. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  117. data/t/t0301.ru +14 -0
  118. data/t/t9001-oob_gc.sh +47 -0
  119. data/t/t9002-oob_gc-path.sh +75 -0
  120. data/t/test-lib.sh +125 -0
  121. data/t/winch_ttin.t +64 -0
  122. data/t/working_directory.t +86 -0
  123. data/test/aggregate.rb +16 -0
  124. data/test/benchmark/README +60 -0
  125. data/test/benchmark/dd.ru +19 -0
  126. data/test/benchmark/ddstream.ru +51 -0
  127. data/test/benchmark/readinput.ru +41 -0
  128. data/test/benchmark/stack.ru +9 -0
  129. data/test/benchmark/uconnect.perl +66 -0
  130. data/test/exec/README +5 -0
  131. data/test/exec/test_exec.rb +1030 -0
  132. data/test/test_helper.rb +307 -0
  133. data/test/unit/test_configurator.rb +176 -0
  134. data/test/unit/test_droplet.rb +29 -0
  135. data/test/unit/test_http_parser.rb +885 -0
  136. data/test/unit/test_http_parser_ng.rb +715 -0
  137. data/test/unit/test_server.rb +245 -0
  138. data/test/unit/test_signals.rb +189 -0
  139. data/test/unit/test_socket_helper.rb +160 -0
  140. data/test/unit/test_stream_input.rb +211 -0
  141. data/test/unit/test_tee_input.rb +304 -0
  142. data/test/unit/test_util.rb +132 -0
  143. data/test/unit/test_waiter.rb +35 -0
  144. data/unicorn.gemspec +49 -0
  145. metadata +266 -0
@@ -0,0 +1,63 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ # :enddoc:
5
+ $stdout.sync = $stderr.sync = true
6
+ $stdin.binmode
7
+ $stdout.binmode
8
+ $stderr.binmode
9
+
10
+ require 'unicorn'
11
+
12
+ module Unicorn::Launcher
13
+
14
+ # We don't do a lot of standard daemonization stuff:
15
+ # * umask is whatever was set by the parent process at startup
16
+ # and can be set in config.ru and config_file, so making it
17
+ # 0000 and potentially exposing sensitive log data can be bad
18
+ # policy.
19
+ # * don't bother to chdir("/") here since unicorn is designed to
20
+ # run inside APP_ROOT. Unicorn will also re-chdir() to
21
+ # the directory it was started in when being re-executed
22
+ # to pickup code changes if the original deployment directory
23
+ # is a symlink or otherwise got replaced.
24
+ def self.daemonize!(options)
25
+ cfg = Unicorn::Configurator
26
+ $stdin.reopen("/dev/null")
27
+
28
+ # We only start a new process group if we're not being reexecuted
29
+ # and inheriting file descriptors from our parent
30
+ unless ENV['UNICORN_FD']
31
+ # grandparent - reads pipe, exits when master is ready
32
+ # \_ parent - exits immediately ASAP
33
+ # \_ unicorn master - writes to pipe when ready
34
+
35
+ rd, wr = Unicorn.pipe
36
+ grandparent = $$
37
+ if fork
38
+ wr.close # grandparent does not write
39
+ else
40
+ rd.close # unicorn master does not read
41
+ Process.setsid
42
+ exit if fork # parent dies now
43
+ end
44
+
45
+ if grandparent == $$
46
+ # this will block until HttpServer#join runs (or it dies)
47
+ master_pid = (rd.readpartial(16) rescue nil).to_i
48
+ unless master_pid > 1
49
+ warn "master failed to start, check stderr log for details"
50
+ exit!(1)
51
+ end
52
+ exit 0
53
+ else # unicorn master process
54
+ options[:ready_pipe] = wr
55
+ end
56
+ end
57
+ # $stderr/$stderr can/will be redirected separately in the Unicorn config
58
+ cfg::DEFAULTS[:stderr_path] ||= "/dev/null"
59
+ cfg::DEFAULTS[:stdout_path] ||= "/dev/null"
60
+ cfg::RACKUP[:daemonized] = true
61
+ end
62
+
63
+ end
@@ -0,0 +1,82 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ # Strongly consider https://github.com/tmm1/gctools if using Ruby 2.1+
5
+ # It is built on new APIs in Ruby 2.1, so it is more intelligent than
6
+ # this historical implementation.
7
+ #
8
+ # Users on Ruby 2.0 (not 2.1+) may also want to check out
9
+ # lib/middleware/unicorn_oobgc.rb from the Discourse project
10
+ # (https://github.com/discourse/discourse)
11
+ #
12
+ # The following information is only for historical versions of Ruby.
13
+ #
14
+ # Runs GC after requests, after closing the client socket and
15
+ # before attempting to accept more connections.
16
+ #
17
+ # This shouldn't hurt overall performance as long as the server cluster
18
+ # is at <50% CPU capacity, and improves the performance of most memory
19
+ # intensive requests. This serves to improve _client-visible_
20
+ # performance (possibly at the cost of overall performance).
21
+ #
22
+ # Increasing the number of +worker_processes+ may be necessary to
23
+ # improve average client response times because some of your workers
24
+ # will be busy doing GC and unable to service clients. Think of
25
+ # using more workers with this module as a poor man's concurrent GC.
26
+ #
27
+ # We'll call GC after each request is been written out to the socket, so
28
+ # the client never sees the extra GC hit it.
29
+ #
30
+ # This middleware is _only_ effective for applications that use a lot
31
+ # of memory, and will hurt simpler apps/endpoints that can process
32
+ # multiple requests before incurring GC.
33
+ #
34
+ # This middleware is only designed to work with unicorn, as it harms
35
+ # performance with keepalive-enabled servers.
36
+ #
37
+ # Example (in config.ru):
38
+ #
39
+ # require 'unicorn/oob_gc'
40
+ #
41
+ # # GC ever two requests that hit /expensive/foo or /more_expensive/foo
42
+ # # in your app. By default, this will GC once every 5 requests
43
+ # # for all endpoints in your app
44
+ # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
45
+ #
46
+ # Feedback from users of early implementations of this module:
47
+ # * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
48
+ # * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
49
+
50
+ module Unicorn::OobGC
51
+
52
+ # this pretends to be Rack middleware because it used to be
53
+ # But we need to hook into unicorn internals so we need to close
54
+ # the socket before clearing the request env.
55
+ #
56
+ # +interval+ is the number of requests matching the +path+ regular
57
+ # expression before invoking GC.
58
+ def self.new(app, interval = 5, path = %r{\A/})
59
+ @@nr = interval
60
+ self.const_set :OOBGC_PATH, path
61
+ self.const_set :OOBGC_INTERVAL, interval
62
+ ObjectSpace.each_object(Unicorn::HttpServer) do |s|
63
+ s.extend(self)
64
+ end
65
+ app # pretend to be Rack middleware since it was in the past
66
+ end
67
+
68
+ #:stopdoc:
69
+ def process_client(*args)
70
+ super(*args) # Unicorn::HttpServer#process_client
71
+ env = instance_variable_get(:@request).env
72
+ if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
73
+ @@nr = OOBGC_INTERVAL
74
+ env.clear
75
+ disabled = GC.enable
76
+ GC.start
77
+ GC.disable if disabled
78
+ end
79
+ end
80
+
81
+ # :startdoc:
82
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ module Unicorn
5
+ # This middleware is used to ensure input is buffered to memory
6
+ # or disk (depending on size) before the application is dispatched
7
+ # by entirely consuming it (from TeeInput) beforehand.
8
+ #
9
+ # Usage (in config.ru):
10
+ #
11
+ # require 'unicorn/preread_input'
12
+ # if defined?(Unicorn)
13
+ # use Unicorn::PrereadInput
14
+ # end
15
+ # run YourApp.new
16
+ class PrereadInput
17
+
18
+ # :stopdoc:
19
+ def initialize(app)
20
+ @app = app
21
+ end
22
+
23
+ def call(env)
24
+ buf = ""
25
+ input = env["rack.input"]
26
+ if input.respond_to?(:rewind)
27
+ true while input.read(16384, buf)
28
+ input.rewind
29
+ end
30
+ @app.call(env)
31
+ end
32
+ # :startdoc:
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: false
2
+ # fallback for non-Linux and Linux <4.5 systems w/o EPOLLEXCLUSIVE
3
+ class Unicorn::SelectWaiter # :nodoc:
4
+ def get_readers(ready, readers, timeout) # :nodoc:
5
+ ret = IO.select(readers, nil, nil, timeout) and ready.replace(ret[0])
6
+ end
7
+ end
@@ -0,0 +1,186 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+ # :enddoc:
4
+ require 'socket'
5
+
6
+ module Unicorn
7
+ module SocketHelper
8
+
9
+ # internal interface
10
+ DEFAULTS = {
11
+ # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
12
+ # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
13
+ # This change shouldn't affect unicorn users behind nginx (a
14
+ # value of 1 remains an optimization).
15
+ :tcp_defer_accept => 1,
16
+
17
+ # FreeBSD, we need to override this to 'dataready' if we
18
+ # eventually support non-HTTP/1.x
19
+ :accept_filter => 'httpready',
20
+
21
+ # same default value as Mongrel
22
+ :backlog => 1024,
23
+
24
+ # favor latency over bandwidth savings
25
+ :tcp_nopush => nil,
26
+ :tcp_nodelay => true,
27
+ }
28
+
29
+ # configure platform-specific options (only tested on Linux 2.6 so far)
30
+ def accf_arg(af_name)
31
+ [ af_name, nil ].pack('a16a240')
32
+ end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
33
+
34
+ def set_tcp_sockopt(sock, opt)
35
+ # just in case, even LANs can break sometimes. Linux sysadmins
36
+ # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
37
+ Socket.const_defined?(:SO_KEEPALIVE) and
38
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
39
+
40
+ if Socket.const_defined?(:TCP_NODELAY)
41
+ val = opt[:tcp_nodelay]
42
+ val = DEFAULTS[:tcp_nodelay] if val.nil?
43
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
44
+ end
45
+
46
+ val = opt[:tcp_nopush]
47
+ unless val.nil?
48
+ if Socket.const_defined?(:TCP_CORK) # Linux
49
+ sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
50
+ elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
51
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
52
+ end
53
+ end
54
+
55
+ # No good reason to ever have deferred accepts off in single-threaded
56
+ # servers (except maybe benchmarking)
57
+ if Socket.const_defined?(:TCP_DEFER_ACCEPT)
58
+ # this differs from nginx, since nginx doesn't allow us to
59
+ # configure the the timeout...
60
+ seconds = opt[:tcp_defer_accept]
61
+ seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
62
+ seconds = 0 unless seconds # nil/false means disable this
63
+ sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
64
+ elsif respond_to?(:accf_arg)
65
+ name = opt[:accept_filter]
66
+ name = DEFAULTS[:accept_filter] if name.nil?
67
+ sock.listen(opt[:backlog])
68
+ got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
69
+ arg = accf_arg(name)
70
+ begin
71
+ sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
72
+ rescue => e
73
+ logger.error("#{sock_name(sock)} " \
74
+ "failed to set accept_filter=#{name} (#{e.inspect})")
75
+ logger.error("perhaps accf_http(9) needs to be loaded".freeze)
76
+ end if arg != got
77
+ end
78
+ end
79
+
80
+ def set_server_sockopt(sock, opt)
81
+ opt = DEFAULTS.merge(opt || {})
82
+
83
+ set_tcp_sockopt(sock, opt) if sock.local_address.ip?
84
+
85
+ rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
86
+ if rcvbuf || sndbuf
87
+ log_buffer_sizes(sock, "before: ")
88
+ sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
89
+ sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
90
+ log_buffer_sizes(sock, " after: ")
91
+ end
92
+ sock.listen(opt[:backlog])
93
+ rescue => e
94
+ Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
95
+ end
96
+
97
+ def log_buffer_sizes(sock, pfx = '')
98
+ rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
99
+ sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
100
+ logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
101
+ end
102
+
103
+ # creates a new server, socket. address may be a HOST:PORT or
104
+ # an absolute path to a UNIX socket. address can even be a Socket
105
+ # object in which case it is immediately returned
106
+ def bind_listen(address = '0.0.0.0:8080', opt = {})
107
+ return address unless String === address
108
+
109
+ sock = if address.start_with?('/')
110
+ if File.exist?(address)
111
+ if File.socket?(address)
112
+ begin
113
+ UNIXSocket.new(address).close
114
+ # fall through, try to bind(2) and fail with EADDRINUSE
115
+ # (or succeed from a small race condition we can't sanely avoid).
116
+ rescue Errno::ECONNREFUSED
117
+ logger.info "unlinking existing socket=#{address}"
118
+ File.unlink(address)
119
+ end
120
+ else
121
+ raise ArgumentError,
122
+ "socket=#{address} specified but it is not a socket!"
123
+ end
124
+ end
125
+ old_umask = File.umask(opt[:umask] || 0)
126
+ begin
127
+ s = Socket.new(:UNIX, :STREAM)
128
+ s.bind(Socket.sockaddr_un(address))
129
+ s
130
+ ensure
131
+ File.umask(old_umask)
132
+ end
133
+ elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
134
+ new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
135
+ elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
136
+ new_tcp_server($1, $2.to_i, opt)
137
+ else
138
+ raise ArgumentError, "Don't know how to bind: #{address}"
139
+ end
140
+ set_server_sockopt(sock, opt)
141
+ sock
142
+ end
143
+
144
+ def new_tcp_server(addr, port, opt)
145
+ # n.b. we set FD_CLOEXEC in the workers
146
+ sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
147
+ if opt.key?(:ipv6only)
148
+ Socket.const_defined?(:IPV6_V6ONLY) or
149
+ abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
150
+ sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
151
+ end
152
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
153
+ if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
154
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
155
+ end
156
+ sock.bind(Socket.pack_sockaddr_in(port, addr))
157
+ sock
158
+ end
159
+
160
+ # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
161
+ def tcp_name(sock)
162
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
163
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
164
+ end
165
+ module_function :tcp_name
166
+
167
+ # Returns the configuration name of a socket as a string. sock may
168
+ # be a string value, in which case it is returned as-is
169
+ # Warning: TCP sockets may not always return the name given to it.
170
+ def sock_name(sock)
171
+ case sock
172
+ when String then sock
173
+ when Socket
174
+ begin
175
+ tcp_name(sock)
176
+ rescue ArgumentError
177
+ Socket.unpack_sockaddr_un(sock.getsockname)
178
+ end
179
+ else
180
+ raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
181
+ end
182
+ end
183
+
184
+ module_function :sock_name
185
+ end # module SocketHelper
186
+ end # module Unicorn
@@ -0,0 +1,152 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ # When processing uploads, unicorn may expose a StreamInput object under
5
+ # "rack.input" of the Rack environment when
6
+ # Unicorn::Configurator#rewindable_input is set to +false+
7
+ class Unicorn::StreamInput
8
+ # The I/O chunk size (in +bytes+) for I/O operations where
9
+ # the size cannot be user-specified when a method is called.
10
+ # The default is 16 kilobytes.
11
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
12
+
13
+ # Initializes a new StreamInput object. You normally do not have to call
14
+ # this unless you are writing an HTTP server.
15
+ def initialize(socket, request) # :nodoc:
16
+ @chunked = request.content_length.nil?
17
+ @socket = socket
18
+ @parser = request
19
+ @buf = request.buf
20
+ @rbuf = ''
21
+ @bytes_read = 0
22
+ filter_body(@rbuf, @buf) unless @buf.empty?
23
+ end
24
+
25
+ # :call-seq:
26
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
27
+ #
28
+ # Reads at most length bytes from the I/O stream, or to the end of
29
+ # file if length is omitted or is nil. length must be a non-negative
30
+ # integer or nil. If the optional buffer argument is present, it
31
+ # must reference a String, which will receive the data.
32
+ #
33
+ # At end of file, it returns nil or '' depend on length.
34
+ # ios.read() and ios.read(nil) returns ''.
35
+ # ios.read(length [, buffer]) returns nil.
36
+ #
37
+ # If the Content-Length of the HTTP request is known (as is the common
38
+ # case for POST requests), then ios.read(length [, buffer]) will block
39
+ # until the specified length is read (or it is the last chunk).
40
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
41
+ # ios.read(length [, buffer]) will return immediately if there is
42
+ # any data and only block when nothing is available (providing
43
+ # IO#readpartial semantics).
44
+ def read(length = nil, rv = '')
45
+ if length
46
+ if length <= @rbuf.size
47
+ length < 0 and raise ArgumentError, "negative length #{length} given"
48
+ rv.replace(@rbuf.slice!(0, length))
49
+ else
50
+ to_read = length - @rbuf.size
51
+ rv.replace(@rbuf.slice!(0, @rbuf.size))
52
+ until to_read == 0 || eof? || (rv.size > 0 && @chunked)
53
+ filter_body(@rbuf, @socket.readpartial(to_read, @buf))
54
+ rv << @rbuf
55
+ to_read -= @rbuf.size
56
+ end
57
+ @rbuf.clear
58
+ end
59
+ rv = nil if rv.empty? && length != 0
60
+ else
61
+ read_all(rv)
62
+ end
63
+ rv
64
+ rescue EOFError
65
+ return eof!
66
+ end
67
+
68
+ # :call-seq:
69
+ # ios.gets => string or nil
70
+ #
71
+ # Reads the next ``line'' from the I/O stream; lines are separated
72
+ # by the global record separator ($/, typically "\n"). A global
73
+ # record separator of nil reads the entire unread contents of ios.
74
+ # Returns nil if called at the end of file.
75
+ # This takes zero arguments for strict Rack::Lint compatibility,
76
+ # unlike IO#gets.
77
+ def gets
78
+ sep = $/
79
+ if sep.nil?
80
+ read_all(rv = '')
81
+ return rv.empty? ? nil : rv
82
+ end
83
+ re = /\A(.*?#{Regexp.escape(sep)})/
84
+
85
+ begin
86
+ @rbuf.sub!(re, '') and return $1
87
+ return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
88
+ filter_body(once = '', @socket.readpartial(@@io_chunk_size, @buf))
89
+ @rbuf << once
90
+ rescue EOFError
91
+ return eof!
92
+ end while true
93
+ end
94
+
95
+ # :call-seq:
96
+ # ios.each { |line| block } => ios
97
+ #
98
+ # Executes the block for every ``line'' in *ios*, where lines are
99
+ # separated by the global record separator ($/, typically "\n").
100
+ def each
101
+ while line = gets
102
+ yield line
103
+ end
104
+
105
+ self # Rack does not specify what the return value is here
106
+ end
107
+
108
+ private
109
+
110
+ def eof?
111
+ if @parser.body_eof?
112
+ while @chunked && ! @parser.parse
113
+ @buf << @socket.readpartial(@@io_chunk_size)
114
+ end
115
+ @socket = nil
116
+ true
117
+ else
118
+ false
119
+ end
120
+ rescue EOFError
121
+ return eof!
122
+ end
123
+
124
+ def filter_body(dst, src)
125
+ rv = @parser.filter_body(dst, src)
126
+ @bytes_read += dst.size
127
+ rv
128
+ end
129
+
130
+ def read_all(dst)
131
+ dst.replace(@rbuf)
132
+ @socket or return
133
+ until eof?
134
+ filter_body(@rbuf, @socket.readpartial(@@io_chunk_size, @buf))
135
+ dst << @rbuf
136
+ end
137
+ rescue EOFError
138
+ return eof!
139
+ ensure
140
+ @rbuf.clear
141
+ end
142
+
143
+ def eof!
144
+ # in case client only did a premature shutdown(SHUT_WR)
145
+ # we do support clients that shutdown(SHUT_WR) after the
146
+ # _entire_ request has been sent, and those will not have
147
+ # raised EOFError on us.
148
+ @socket.shutdown if @socket
149
+ ensure
150
+ raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
151
+ end
152
+ end
@@ -0,0 +1,132 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ # Acts like tee(1) on an input input to provide a input-like stream
5
+ # while providing rewindable semantics through a File/StringIO backing
6
+ # store. On the first pass, the input is only read on demand so your
7
+ # Rack application can use input notification (upload progress and
8
+ # like). This should fully conform to the Rack::Lint::InputWrapper
9
+ # specification on the public API. This class is intended to be a
10
+ # strict interpretation of Rack::Lint::InputWrapper functionality and
11
+ # will not support any deviations from it.
12
+ #
13
+ # When processing uploads, unicorn exposes a TeeInput object under
14
+ # "rack.input" of the Rack environment by default.
15
+ class Unicorn::TeeInput < Unicorn::StreamInput
16
+ # The maximum size (in +bytes+) to buffer in memory before
17
+ # resorting to a temporary file. Default is 112 kilobytes.
18
+ @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
19
+
20
+ # sets the maximum size of request bodies to buffer in memory,
21
+ # amounts larger than this are buffered to the filesystem
22
+ def self.client_body_buffer_size=(bytes) # :nodoc:
23
+ @@client_body_buffer_size = bytes
24
+ end
25
+
26
+ # returns the maximum size of request bodies to buffer in memory,
27
+ # amounts larger than this are buffered to the filesystem
28
+ def self.client_body_buffer_size # :nodoc:
29
+ @@client_body_buffer_size
30
+ end
31
+
32
+ # for Rack::TempfileReaper in rack 1.6+
33
+ def new_tmpio # :nodoc:
34
+ tmpio = Unicorn::TmpIO.new
35
+ (@parser.env['rack.tempfiles'] ||= []) << tmpio
36
+ tmpio
37
+ end
38
+
39
+ # Initializes a new TeeInput object. You normally do not have to call
40
+ # this unless you are writing an HTTP server.
41
+ def initialize(socket, request) # :nodoc:
42
+ @len = request.content_length
43
+ super
44
+ @tmp = @len && @len <= @@client_body_buffer_size ?
45
+ StringIO.new("") : new_tmpio
46
+ end
47
+
48
+ # :call-seq:
49
+ # ios.size => Integer
50
+ #
51
+ # Returns the size of the input. For requests with a Content-Length
52
+ # header value, this will not read data off the socket and just return
53
+ # the value of the Content-Length header as an Integer.
54
+ #
55
+ # For Transfer-Encoding:chunked requests, this requires consuming
56
+ # all of the input stream before returning since there's no other
57
+ # way to determine the size of the request body beforehand.
58
+ #
59
+ # This method is no longer part of the Rack specification as of
60
+ # Rack 1.2, so its use is not recommended. This method only exists
61
+ # for compatibility with Rack applications designed for Rack 1.1 and
62
+ # earlier. Most applications should only need to call +read+ with a
63
+ # specified +length+ in a loop until it returns +nil+.
64
+ def size
65
+ @len and return @len
66
+ pos = @tmp.pos
67
+ consume!
68
+ @tmp.pos = pos
69
+ @len = @tmp.size
70
+ end
71
+
72
+ # :call-seq:
73
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
74
+ #
75
+ # Reads at most length bytes from the I/O stream, or to the end of
76
+ # file if length is omitted or is nil. length must be a non-negative
77
+ # integer or nil. If the optional buffer argument is present, it
78
+ # must reference a String, which will receive the data.
79
+ #
80
+ # At end of file, it returns nil or "" depend on length.
81
+ # ios.read() and ios.read(nil) returns "".
82
+ # ios.read(length [, buffer]) returns nil.
83
+ #
84
+ # If the Content-Length of the HTTP request is known (as is the common
85
+ # case for POST requests), then ios.read(length [, buffer]) will block
86
+ # until the specified length is read (or it is the last chunk).
87
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
88
+ # ios.read(length [, buffer]) will return immediately if there is
89
+ # any data and only block when nothing is available (providing
90
+ # IO#readpartial semantics).
91
+ def read(*args)
92
+ @socket ? tee(super) : @tmp.read(*args)
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 ? tee(super) : @tmp.gets
106
+ end
107
+
108
+ # :call-seq:
109
+ # ios.rewind => 0
110
+ #
111
+ # Positions the *ios* pointer to the beginning of input, returns
112
+ # the offset (zero) of the +ios+ pointer. Subsequent reads will
113
+ # start from the beginning of the previously-buffered input.
114
+ def rewind
115
+ return 0 if 0 == @tmp.size
116
+ consume! if @socket
117
+ @tmp.rewind # Rack does not specify what the return value is here
118
+ end
119
+
120
+ private
121
+
122
+ # consumes the stream of the socket
123
+ def consume!
124
+ junk = ""
125
+ nil while read(@@io_chunk_size, junk)
126
+ end
127
+
128
+ def tee(buffer)
129
+ @tmp.write(buffer) if buffer
130
+ buffer
131
+ end
132
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+ # :stopdoc:
4
+ require 'tmpdir'
5
+
6
+ # some versions of Ruby had a broken Tempfile which didn't work
7
+ # well with unlinked files. This one is much shorter, easier
8
+ # to understand, and slightly faster.
9
+ class Unicorn::TmpIO < File
10
+
11
+ # creates and returns a new File object. The File is unlinked
12
+ # immediately, switched to binary mode, and userspace output
13
+ # buffering is disabled
14
+ def self.new
15
+ path = nil
16
+
17
+ # workaround File#path being tainted:
18
+ # https://bugs.ruby-lang.org/issues/14485
19
+ fp = begin
20
+ path = "#{Dir::tmpdir}/#{rand}"
21
+ super(path, RDWR|CREAT|EXCL, 0600)
22
+ rescue Errno::EEXIST
23
+ retry
24
+ end
25
+
26
+ unlink(path)
27
+ fp.binmode
28
+ fp.sync = true
29
+ fp
30
+ end
31
+
32
+ # pretend we're Tempfile for Rack::TempfileReaper
33
+ alias close! close
34
+ end