passenger 4.0.0.rc4 → 4.0.0.rc6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- data.tar.gz.asc +12 -0
- data/.travis.yml +4 -4
- data/NEWS +46 -0
- data/bin/passenger-config +31 -1
- data/bin/passenger-install-apache2-module +1 -1
- data/bin/passenger-install-nginx-module +1 -0
- data/build/common_library.rb +4 -0
- data/build/cplusplus_support.rb +27 -6
- data/build/cxx_tests.rb +1 -1
- data/build/misc.rb +28 -6
- data/build/packaging.rb +72 -65
- data/build/test_basics.rb +1 -1
- data/dev/googlecode_upload.py +265 -0
- data/dev/run_travis.sh +9 -0
- data/doc/Users guide Apache.html +376 -193
- data/doc/Users guide Apache.idmap.txt +80 -62
- data/doc/Users guide Apache.txt +61 -35
- data/doc/Users guide Nginx.html +278 -83
- data/doc/Users guide Nginx.idmap.txt +26 -10
- data/doc/Users guide Nginx.txt +59 -31
- data/doc/Users guide Standalone.html +1 -1
- data/doc/users_guide_snippets/installation.txt +121 -11
- data/doc/users_guide_snippets/rvm_helper_tool.txt +56 -0
- data/ext/apache2/Bucket.cpp +1 -1
- data/ext/apache2/Configuration.cpp +7 -1
- data/ext/apache2/Configuration.hpp +4 -0
- data/ext/apache2/Hooks.cpp +2 -2
- data/ext/common/AgentsStarter.cpp +2 -2
- data/ext/common/AgentsStarter.h +1 -1
- data/ext/common/AgentsStarter.hpp +2 -2
- data/ext/common/ApplicationPool2/DirectSpawner.h +4 -8
- data/ext/common/ApplicationPool2/Group.h +17 -11
- data/ext/common/ApplicationPool2/Implementation.cpp +39 -11
- data/ext/common/ApplicationPool2/Pool.h +23 -4
- data/ext/common/ApplicationPool2/Process.h +30 -11
- data/ext/common/ApplicationPool2/SmartSpawner.h +3 -1
- data/ext/common/Constants.h +1 -1
- data/ext/common/EventedBufferedInput.h +4 -0
- data/ext/common/Utils.cpp +21 -3
- data/ext/common/Utils.h +8 -1
- data/ext/common/Utils/HttpHeaderBufferer.h +1 -1
- data/ext/common/Utils/IOUtils.cpp +5 -4
- data/ext/common/Utils/IOUtils.h +32 -14
- data/ext/common/Utils/MessagePassing.h +2 -2
- data/ext/common/Utils/ProcessMetricsCollector.h +47 -15
- data/ext/common/Utils/ScopeGuard.h +20 -3
- data/ext/common/Utils/StrIntUtils.h +14 -5
- data/ext/common/agents/Base.cpp +161 -50
- data/ext/common/agents/HelperAgent/AgentOptions.h +2 -2
- data/ext/common/agents/HelperAgent/Main.cpp +1 -0
- data/ext/common/agents/HelperAgent/RequestHandler.h +166 -52
- data/ext/common/agents/LoggingAgent/Main.cpp +1 -1
- data/ext/common/agents/Watchdog/Main.cpp +2 -2
- data/ext/nginx/Configuration.c +31 -4
- data/ext/nginx/Configuration.h +1 -0
- data/ext/nginx/ContentHandler.c +148 -34
- data/ext/nginx/ngx_http_passenger_module.c +4 -1
- data/ext/oxt/detail/spin_lock_pthreads.hpp +4 -4
- data/ext/oxt/macros.hpp +30 -8
- data/lib/phusion_passenger.rb +2 -2
- data/lib/phusion_passenger/classic_rails/thread_handler_extension.rb +1 -1
- data/lib/phusion_passenger/native_support.rb +19 -1
- data/lib/phusion_passenger/platform_info/compiler.rb +6 -0
- data/lib/phusion_passenger/platform_info/ruby.rb +54 -5
- data/lib/phusion_passenger/preloader_shared_helpers.rb +8 -1
- data/lib/phusion_passenger/rack/out_of_band_gc.rb +3 -1
- data/lib/phusion_passenger/rack/thread_handler_extension.rb +32 -5
- data/lib/phusion_passenger/request_handler/thread_handler.rb +28 -8
- data/lib/phusion_passenger/ruby_core_enhancements.rb +9 -1
- data/lib/phusion_passenger/standalone/runtime_installer.rb +1 -0
- data/lib/phusion_passenger/utils/unseekable_socket.rb +50 -5
- data/passenger.gemspec +1 -1
- data/resources/templates/apache2/config_snippets.txt.erb +1 -1
- data/test/cxx/ApplicationPool2/PoolTest.cpp +4 -9
- data/test/cxx/RequestHandlerTest.cpp +5 -5
- data/test/ruby/classic_rails/loader_spec.rb +1 -1
- data/test/ruby/classic_rails/preloader_spec.rb +1 -1
- data/test/ruby/request_handler_spec.rb +207 -1
- data/test/ruby/shared/loader_sharedspec.rb +1 -0
- data/test/ruby/spec_helper.rb +11 -1
- data/test/stub/apache2/httpd.conf.erb +1 -1
- metadata +5 -3
- metadata.gz.asc +12 -0
@@ -27,7 +27,7 @@ module PhusionPassenger
|
|
27
27
|
module ClassicRails
|
28
28
|
|
29
29
|
module ThreadHandlerExtension
|
30
|
-
def process_request(env, connection, full_http_response)
|
30
|
+
def process_request(env, connection, socket_wrapper, full_http_response)
|
31
31
|
cgi = CGIFixed.new(env, connection, connection)
|
32
32
|
::Dispatcher.dispatch(cgi,
|
33
33
|
::ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Phusion Passenger - https://www.phusionpassenger.com/
|
2
|
-
# Copyright (c) 2010
|
2
|
+
# Copyright (c) 2010-2013 Phusion
|
3
3
|
#
|
4
4
|
# "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
|
5
5
|
#
|
@@ -30,6 +30,7 @@ class NativeSupportLoader
|
|
30
30
|
|
31
31
|
def start
|
32
32
|
require 'phusion_passenger'
|
33
|
+
load_from_native_support_output_dir ||
|
33
34
|
load_from_source_root ||
|
34
35
|
load_from_load_path ||
|
35
36
|
load_from_home_dir ||
|
@@ -74,6 +75,20 @@ private
|
|
74
75
|
return nil
|
75
76
|
end
|
76
77
|
end
|
78
|
+
|
79
|
+
def load_from_native_support_output_dir
|
80
|
+
output_dir = ENV['PASSENGER_NATIVE_SUPPORT_OUTPUT_DIR']
|
81
|
+
if output_dir && !output_dir.empty?
|
82
|
+
begin
|
83
|
+
require "#{output_dir}/#{VERSION_STRING}/#{archdir}/#{library_name}"
|
84
|
+
return true
|
85
|
+
rescue LoadError
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
else
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
end
|
77
92
|
|
78
93
|
def load_from_source_root
|
79
94
|
if PhusionPassenger.originally_packaged?
|
@@ -112,6 +127,9 @@ private
|
|
112
127
|
require 'phusion_passenger/platform_info/ruby'
|
113
128
|
|
114
129
|
target_dirs = []
|
130
|
+
if (output_dir = ENV['PASSENGER_NATIVE_SUPPORT_OUTPUT_DIR']) && !output_dir.empty?
|
131
|
+
target_dirs << "#{output_dir}/#{VERSION_STRING}/#{archdir}"
|
132
|
+
end
|
115
133
|
if native_support_dir_in_source_root
|
116
134
|
target_dirs << "#{native_support_dir_in_source_root}/#{archdir}"
|
117
135
|
end
|
@@ -251,6 +251,12 @@ public
|
|
251
251
|
:c, '', '-mno-tls-direct-seg-refs')
|
252
252
|
end
|
253
253
|
memoize :compiler_supports_no_tls_direct_seg_refs_option?, true
|
254
|
+
|
255
|
+
def self.compiler_supports_wno_ambiguous_member_template?
|
256
|
+
return try_compile("Checking for C compiler '-Wno-ambiguous-member-template' support",
|
257
|
+
:c, '', '-Wno-ambiguous-member-template')
|
258
|
+
end
|
259
|
+
memoize :compiler_supports_wno_ambiguous_member_template?, true
|
254
260
|
|
255
261
|
# Returns whether compiling C++ with -fvisibility=hidden might result
|
256
262
|
# in tons of useless warnings, like this:
|
@@ -68,9 +68,33 @@ module PlatformInfo
|
|
68
68
|
return filename
|
69
69
|
end
|
70
70
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
|
72
|
+
# Correctness of these commands are confirmed by mpapis.
|
73
|
+
# If we ever encounter a case for which this logic is not sufficient,
|
74
|
+
# try mpapis' pseudo code:
|
75
|
+
#
|
76
|
+
# rvm_update_prefix = write_to rvm_path ? "" : "rvmsudo"
|
77
|
+
# rvm_gemhome_prefix = write_to GEM_HOME ? "" : "rvmsudo"
|
78
|
+
# repair_command = "#{rvm_update_prefix} rvm get stable && rvm reload && #{rvm_gemhome_prefix} rvm repair all"
|
79
|
+
# wrapper_command = "#{rvm_gemhome_prefix} rvm wrapper #{rvm_ruby_string} --no-prefix --all"
|
80
|
+
case rvm_installation_mode
|
81
|
+
when :single
|
82
|
+
repair_command = "rvm get stable && rvm reload && rvm repair all"
|
83
|
+
wrapper_command = "rvm wrapper #{rvm_ruby_string} --no-prefix --all"
|
84
|
+
when :multi
|
85
|
+
repair_command = "rvmsudo rvm get stable && rvm reload && rvmsudo rvm repair all"
|
86
|
+
wrapper_command = "rvmsudo rvm wrapper #{rvm_ruby_string} --no-prefix --all"
|
87
|
+
when :mixed
|
88
|
+
repair_command = "rvmsudo rvm get stable && rvm reload && rvm repair all"
|
89
|
+
wrapper_command = "rvm wrapper #{rvm_ruby_string} --no-prefix --all"
|
90
|
+
end
|
91
|
+
|
92
|
+
STDERR.puts "Your RVM wrapper scripts are too old, or some " +
|
93
|
+
"wrapper scripts are missing. Please update/regenerate " +
|
94
|
+
"them first by running:\n\n" +
|
95
|
+
" #{repair_command}\n\n" +
|
96
|
+
"If that doesn't seem to work, please run:\n\n" +
|
97
|
+
" #{wrapper_command}"
|
74
98
|
exit 1
|
75
99
|
else
|
76
100
|
# Something's wrong with the user's RVM installation.
|
@@ -211,8 +235,12 @@ module PlatformInfo
|
|
211
235
|
# try various strategies...
|
212
236
|
|
213
237
|
# $GEM_HOME usually contains the gem set name.
|
214
|
-
|
215
|
-
|
238
|
+
# It may be something like:
|
239
|
+
# /Users/hongli/.rvm/gems/ruby-1.9.3-p392
|
240
|
+
# But also:
|
241
|
+
# /home/bitnami/.rvm/gems/ruby-1.9.3-p385-perf@njist325/ruby/1.9.1
|
242
|
+
if GEM_HOME && GEM_HOME =~ %r{rvm/gems/(.+)}
|
243
|
+
return $1.sub(/\/.*/, '')
|
216
244
|
end
|
217
245
|
|
218
246
|
# User somehow managed to nuke $GEM_HOME. Extract info
|
@@ -238,6 +266,27 @@ module PlatformInfo
|
|
238
266
|
return nil
|
239
267
|
end
|
240
268
|
memoize :rvm_ruby_string
|
269
|
+
|
270
|
+
# Returns the RVM installation mode:
|
271
|
+
# :single - RVM is installed in single-user mode.
|
272
|
+
# :multi - RVM is installed in multi-user mode.
|
273
|
+
# :mixed - RVM is in a mixed-mode installation.
|
274
|
+
# nil - The current Ruby interpreter is not using RVM.
|
275
|
+
def self.rvm_installation_mode
|
276
|
+
if in_rvm?
|
277
|
+
if ENV['rvm_path'] =~ /\.rvm/
|
278
|
+
return :single
|
279
|
+
else
|
280
|
+
if GEM_HOME =~ /\.rvm/
|
281
|
+
return :mixed
|
282
|
+
else
|
283
|
+
return :multi
|
284
|
+
end
|
285
|
+
end
|
286
|
+
else
|
287
|
+
return nil
|
288
|
+
end
|
289
|
+
end
|
241
290
|
|
242
291
|
# Returns either 'sudo' or 'rvmsudo' depending on whether the current
|
243
292
|
# Ruby interpreter is managed by RVM.
|
@@ -79,7 +79,14 @@ module PreloaderSharedHelpers
|
|
79
79
|
end
|
80
80
|
return nil
|
81
81
|
ensure
|
82
|
-
|
82
|
+
if client && Process.pid == original_pid
|
83
|
+
begin
|
84
|
+
client.close
|
85
|
+
rescue Errno::EINVAL
|
86
|
+
# Work around OS X bug.
|
87
|
+
# https://code.google.com/p/phusion-passenger/issues/detail?id=854
|
88
|
+
end
|
89
|
+
end
|
83
90
|
end
|
84
91
|
|
85
92
|
def run_main_loop(options)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: binary
|
2
2
|
# Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
# Copyright (c) 2012 Phusion
|
3
|
+
# Copyright (c) 2012-2013 Phusion
|
4
4
|
#
|
5
5
|
# "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
|
6
6
|
#
|
@@ -36,7 +36,9 @@ class OutOfBandGc
|
|
36
36
|
|
37
37
|
::PhusionPassenger.on_event(:oob_work) do
|
38
38
|
t0 = Time.now
|
39
|
+
disabled = GC.enable
|
39
40
|
GC.start
|
41
|
+
GC.disable if disabled
|
40
42
|
logger.info "Out Of Band GC finished in #{Time.now - t0} sec" if logger
|
41
43
|
end
|
42
44
|
end
|
@@ -51,7 +51,7 @@ module ThreadHandlerExtension
|
|
51
51
|
STATUS = "Status: " # :nodoc:
|
52
52
|
NAME_VALUE_SEPARATOR = ": " # :nodoc:
|
53
53
|
|
54
|
-
def process_request(env, connection, full_http_response)
|
54
|
+
def process_request(env, connection, socket_wrapper, full_http_response)
|
55
55
|
rewindable_input = PhusionPassenger::Utils::TeeInput.new(connection, env)
|
56
56
|
begin
|
57
57
|
env[RACK_VERSION] = RACK_VERSION_VALUE
|
@@ -66,9 +66,26 @@ module ThreadHandlerExtension
|
|
66
66
|
env[RACK_URL_SCHEME] = HTTP
|
67
67
|
end
|
68
68
|
env[RACK_HIJACK_P] = true
|
69
|
-
env[RACK_HIJACK] = lambda
|
69
|
+
env[RACK_HIJACK] = lambda do
|
70
|
+
env[RACK_HIJACK_IO] ||= begin
|
71
|
+
connection.stop_simulating_eof!
|
72
|
+
connection
|
73
|
+
end
|
74
|
+
end
|
70
75
|
|
71
|
-
|
76
|
+
begin
|
77
|
+
status, headers, body = @app.call(env)
|
78
|
+
rescue => e
|
79
|
+
if should_reraise_app_error?(e, socket_wrapper)
|
80
|
+
raise e
|
81
|
+
elsif !should_swallow_app_error?(e, socket_wrapper)
|
82
|
+
# It's a good idea to catch application exceptions here because
|
83
|
+
# otherwise maliciously crafted responses can crash the app,
|
84
|
+
# forcing it to be respawned, and thereby effectively DoSing it.
|
85
|
+
print_exception("Rack application object", e)
|
86
|
+
end
|
87
|
+
return false
|
88
|
+
end
|
72
89
|
|
73
90
|
# Application requested a full socket hijack.
|
74
91
|
return true if env[RACK_HIJACK_IO]
|
@@ -119,8 +136,18 @@ module ThreadHandlerExtension
|
|
119
136
|
else
|
120
137
|
connection.writev(headers_output)
|
121
138
|
if body
|
122
|
-
|
123
|
-
|
139
|
+
begin
|
140
|
+
body.each do |s|
|
141
|
+
connection.write(s)
|
142
|
+
end
|
143
|
+
rescue => e
|
144
|
+
if should_reraise_app_error?(e, socket_wrapper)
|
145
|
+
raise e
|
146
|
+
elsif !should_swallow_app_error?(e, socket_wrapper)
|
147
|
+
# Body objects can raise exceptions in #each.
|
148
|
+
print_exception("Rack body object #each method", e)
|
149
|
+
end
|
150
|
+
return false
|
124
151
|
end
|
125
152
|
end
|
126
153
|
return false
|
@@ -35,12 +35,15 @@ class RequestHandler
|
|
35
35
|
# This class encapsulates the logic of a single RequestHandler thread.
|
36
36
|
class ThreadHandler
|
37
37
|
include DebugLogging
|
38
|
+
include Utils
|
38
39
|
include Utils::RobustInterruption
|
39
40
|
|
40
41
|
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
41
42
|
PING = 'PING'.freeze
|
42
43
|
OOBW = 'OOBW'.freeze
|
43
44
|
PASSENGER_CONNECT_PASSWORD = 'PASSENGER_CONNECT_PASSWORD'.freeze
|
45
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
46
|
+
TRANSFER_ENCODING = 'TRANSFER_ENCODING'.freeze
|
44
47
|
|
45
48
|
MAX_HEADER_SIZE = 128 * 1024
|
46
49
|
|
@@ -122,14 +125,14 @@ private
|
|
122
125
|
trace(3, "Accepted new request on socket #{@socket_name}")
|
123
126
|
channel.io = connection
|
124
127
|
if headers = parse_request(connection, channel, buffer)
|
125
|
-
prepare_request(headers)
|
128
|
+
prepare_request(connection, headers)
|
126
129
|
begin
|
127
130
|
if headers[REQUEST_METHOD] == PING
|
128
131
|
process_ping(headers, connection)
|
129
132
|
elsif headers[REQUEST_METHOD] == OOBW
|
130
133
|
process_oobw(headers, connection)
|
131
134
|
else
|
132
|
-
process_request(headers, connection, @protocol == :http)
|
135
|
+
process_request(headers, connection, socket_wrapper, @protocol == :http)
|
133
136
|
end
|
134
137
|
rescue Exception
|
135
138
|
has_error = true
|
@@ -140,7 +143,7 @@ private
|
|
140
143
|
connection = nil
|
141
144
|
channel = nil
|
142
145
|
end
|
143
|
-
finalize_request(headers, has_error)
|
146
|
+
finalize_request(connection, headers, has_error)
|
144
147
|
trace(3, "Request done.")
|
145
148
|
end
|
146
149
|
else
|
@@ -152,7 +155,7 @@ private
|
|
152
155
|
# Other errors might indicate a problem so we print them, but they're
|
153
156
|
# probably not bad enough to warrant stopping the request handler.
|
154
157
|
if !e.is_a?(Errno::EPIPE)
|
155
|
-
|
158
|
+
print_exception("Passenger RequestHandler's client socket", e)
|
156
159
|
end
|
157
160
|
else
|
158
161
|
if @analytics_logger && headers && headers[PASSENGER_TXN_ID]
|
@@ -236,7 +239,7 @@ private
|
|
236
239
|
header, value = line.split(/\s*:\s*/, 2)
|
237
240
|
header.upcase! # "Foo-Bar" => "FOO-BAR"
|
238
241
|
header.gsub!("-", "_") # => "FOO_BAR"
|
239
|
-
if header ==
|
242
|
+
if header == CONTENT_LENGTH || header == "CONTENT_TYPE"
|
240
243
|
headers[header] = value
|
241
244
|
else
|
242
245
|
headers["HTTP_#{header}"] = value
|
@@ -264,11 +267,16 @@ private
|
|
264
267
|
connection.write("oobw done")
|
265
268
|
end
|
266
269
|
|
267
|
-
# def process_request(env, connection, full_http_response)
|
270
|
+
# def process_request(env, connection, socket_wrapper, full_http_response)
|
268
271
|
# raise NotImplementedError, "Override with your own implementation!"
|
269
272
|
# end
|
270
273
|
|
271
|
-
def prepare_request(headers)
|
274
|
+
def prepare_request(connection, headers)
|
275
|
+
if (!headers.has_key?(CONTENT_LENGTH) && !headers.has_key?(TRANSFER_ENCODING)) ||
|
276
|
+
headers[CONTENT_LENGTH] == 0
|
277
|
+
connection.simulate_eof!
|
278
|
+
end
|
279
|
+
|
272
280
|
if @analytics_logger && headers[PASSENGER_TXN_ID]
|
273
281
|
txn_id = headers[PASSENGER_TXN_ID]
|
274
282
|
union_station_key = headers[PASSENGER_UNION_STATION_KEY]
|
@@ -297,7 +305,11 @@ private
|
|
297
305
|
#################
|
298
306
|
end
|
299
307
|
|
300
|
-
def finalize_request(headers, has_error)
|
308
|
+
def finalize_request(connection, headers, has_error)
|
309
|
+
if connection
|
310
|
+
connection.stop_simulating_eof!
|
311
|
+
end
|
312
|
+
|
301
313
|
log = headers[PASSENGER_ANALYTICS_WEB_LOG]
|
302
314
|
if log && !log.closed?
|
303
315
|
exception_occurred = false
|
@@ -373,6 +385,14 @@ private
|
|
373
385
|
# Stubable by unit tests.
|
374
386
|
return true
|
375
387
|
end
|
388
|
+
|
389
|
+
def should_reraise_app_error?(e, socket_wrapper)
|
390
|
+
return false
|
391
|
+
end
|
392
|
+
|
393
|
+
def should_swallow_app_error?(e, socket_wrapper)
|
394
|
+
return socket_wrapper && socket_wrapper.source_of_exception?(e) && e.is_a?(Errno::EPIPE)
|
395
|
+
end
|
376
396
|
end
|
377
397
|
|
378
398
|
|
@@ -43,8 +43,16 @@ class Exception
|
|
43
43
|
else
|
44
44
|
location = "in #{current_location} "
|
45
45
|
end
|
46
|
+
current_thread = Thread.current
|
47
|
+
if !(thread_id = current_thread[:id])
|
48
|
+
current_thread.to_s =~ /:(0x[0-9a-f]+)/i
|
49
|
+
thread_id = $1 || '?'
|
50
|
+
end
|
51
|
+
if thread_name = current_thread[:name]
|
52
|
+
thread_name = "(#{thread_name})"
|
53
|
+
end
|
46
54
|
return "*** Exception #{self.class} #{location}" <<
|
47
|
-
"(#{self}) (process #{$$}, thread #{
|
55
|
+
"(#{self}) (process #{$$}, thread #{thread_id}#{thread_name}):\n" <<
|
48
56
|
"\tfrom " << backtrace.join("\n\tfrom ")
|
49
57
|
end
|
50
58
|
end
|
@@ -545,6 +545,7 @@ private
|
|
545
545
|
"--without-http_scgi_module " <<
|
546
546
|
"--without-http_uwsgi_module " <<
|
547
547
|
"--with-http_gzip_static_module " <<
|
548
|
+
"--with-http_stub_status_module " <<
|
548
549
|
"'--add-module=#{PhusionPassenger.source_root}/ext/nginx'"
|
549
550
|
run_command_with_throbber(command, "Preparing Nginx...") do |status_text|
|
550
551
|
yield(0, 1, status_text)
|
@@ -26,14 +26,14 @@ require 'phusion_passenger/utils' # So that we can know whether #writev is sup
|
|
26
26
|
module PhusionPassenger
|
27
27
|
module Utils
|
28
28
|
|
29
|
-
# Some frameworks (e.g. Merb) call
|
29
|
+
# Some frameworks (e.g. Merb) call `seek` and `rewind` on the input stream
|
30
30
|
# if it responds to these methods. In case of Phusion Passenger, the input
|
31
|
-
# stream is a socket, and altough socket objects respond to
|
32
|
-
#
|
31
|
+
# stream is a socket, and altough socket objects respond to `seek` and
|
32
|
+
# `rewind`, calling these methods will raise an exception. We don't want
|
33
33
|
# this to happen so in AbstractRequestHandler we wrap the client socket
|
34
34
|
# into an UnseekableSocket wrapper, which doesn't respond to these methods.
|
35
35
|
#
|
36
|
-
# We used to dynamically undef
|
36
|
+
# We used to dynamically undef `seek` and `rewind` on sockets, but this
|
37
37
|
# blows the Ruby interpreter's method cache and made things slower.
|
38
38
|
# Wrapping a socket is faster despite extra method calls.
|
39
39
|
#
|
@@ -87,8 +87,21 @@ class UnseekableSocket
|
|
87
87
|
def binmode
|
88
88
|
end
|
89
89
|
|
90
|
+
# This makes select() work.
|
90
91
|
def to_io
|
91
|
-
|
92
|
+
@socket
|
93
|
+
end
|
94
|
+
|
95
|
+
def simulate_eof!
|
96
|
+
@simulate_eof = true
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop_simulating_eof!
|
100
|
+
@simulate_eof = false
|
101
|
+
end
|
102
|
+
|
103
|
+
def fileno
|
104
|
+
@socket.fileno
|
92
105
|
end
|
93
106
|
|
94
107
|
def addr
|
@@ -128,36 +141,54 @@ class UnseekableSocket
|
|
128
141
|
end
|
129
142
|
|
130
143
|
def gets
|
144
|
+
return nil if @simulate_eof
|
131
145
|
@socket.gets
|
132
146
|
rescue => e
|
133
147
|
raise annotate(e)
|
134
148
|
end
|
135
149
|
|
136
150
|
def read(*args)
|
151
|
+
if @simulate_eof
|
152
|
+
length, buffer = args
|
153
|
+
if buffer
|
154
|
+
buffer.replace(binary_string(""))
|
155
|
+
else
|
156
|
+
buffer = binary_string("")
|
157
|
+
end
|
158
|
+
if length
|
159
|
+
return nil
|
160
|
+
else
|
161
|
+
return buffer
|
162
|
+
end
|
163
|
+
end
|
137
164
|
@socket.read(*args)
|
138
165
|
rescue => e
|
139
166
|
raise annotate(e)
|
140
167
|
end
|
141
168
|
|
142
169
|
def readpartial(*args)
|
170
|
+
raise EOFError, "end of file reached" if @simulate_eof
|
143
171
|
@socket.readpartial(*args)
|
144
172
|
rescue => e
|
145
173
|
raise annotate(e)
|
146
174
|
end
|
147
175
|
|
148
176
|
def readline
|
177
|
+
raise EOFError, "end of file reached" if @simulate_eof
|
149
178
|
@socket.readline
|
150
179
|
rescue => e
|
151
180
|
raise annotate(e)
|
152
181
|
end
|
153
182
|
|
154
183
|
def each(&block)
|
184
|
+
return if @simulate_eof
|
155
185
|
@socket.each(&block)
|
156
186
|
rescue => e
|
157
187
|
raise annotate(e)
|
158
188
|
end
|
159
189
|
|
160
190
|
def eof?
|
191
|
+
return true if @simulate_eof
|
161
192
|
@socket.eof?
|
162
193
|
rescue => e
|
163
194
|
raise annotate(e)
|
@@ -196,6 +227,20 @@ private
|
|
196
227
|
exception.instance_variable_set(:"@from_unseekable_socket", @socket.object_id)
|
197
228
|
return exception
|
198
229
|
end
|
230
|
+
|
231
|
+
def raise_error_because_activity_disallowed!
|
232
|
+
raise IOError, "It is not possible to read or write from the client socket because the current."
|
233
|
+
end
|
234
|
+
|
235
|
+
if ''.respond_to?(:force_encoding)
|
236
|
+
def binary_string(str)
|
237
|
+
return ''.force_encoding('binary')
|
238
|
+
end
|
239
|
+
else
|
240
|
+
def binary_string(str)
|
241
|
+
return ''
|
242
|
+
end
|
243
|
+
end
|
199
244
|
end
|
200
245
|
|
201
246
|
end # module Utils
|