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.

Files changed (83) hide show
  1. data.tar.gz.asc +12 -0
  2. data/.travis.yml +4 -4
  3. data/NEWS +46 -0
  4. data/bin/passenger-config +31 -1
  5. data/bin/passenger-install-apache2-module +1 -1
  6. data/bin/passenger-install-nginx-module +1 -0
  7. data/build/common_library.rb +4 -0
  8. data/build/cplusplus_support.rb +27 -6
  9. data/build/cxx_tests.rb +1 -1
  10. data/build/misc.rb +28 -6
  11. data/build/packaging.rb +72 -65
  12. data/build/test_basics.rb +1 -1
  13. data/dev/googlecode_upload.py +265 -0
  14. data/dev/run_travis.sh +9 -0
  15. data/doc/Users guide Apache.html +376 -193
  16. data/doc/Users guide Apache.idmap.txt +80 -62
  17. data/doc/Users guide Apache.txt +61 -35
  18. data/doc/Users guide Nginx.html +278 -83
  19. data/doc/Users guide Nginx.idmap.txt +26 -10
  20. data/doc/Users guide Nginx.txt +59 -31
  21. data/doc/Users guide Standalone.html +1 -1
  22. data/doc/users_guide_snippets/installation.txt +121 -11
  23. data/doc/users_guide_snippets/rvm_helper_tool.txt +56 -0
  24. data/ext/apache2/Bucket.cpp +1 -1
  25. data/ext/apache2/Configuration.cpp +7 -1
  26. data/ext/apache2/Configuration.hpp +4 -0
  27. data/ext/apache2/Hooks.cpp +2 -2
  28. data/ext/common/AgentsStarter.cpp +2 -2
  29. data/ext/common/AgentsStarter.h +1 -1
  30. data/ext/common/AgentsStarter.hpp +2 -2
  31. data/ext/common/ApplicationPool2/DirectSpawner.h +4 -8
  32. data/ext/common/ApplicationPool2/Group.h +17 -11
  33. data/ext/common/ApplicationPool2/Implementation.cpp +39 -11
  34. data/ext/common/ApplicationPool2/Pool.h +23 -4
  35. data/ext/common/ApplicationPool2/Process.h +30 -11
  36. data/ext/common/ApplicationPool2/SmartSpawner.h +3 -1
  37. data/ext/common/Constants.h +1 -1
  38. data/ext/common/EventedBufferedInput.h +4 -0
  39. data/ext/common/Utils.cpp +21 -3
  40. data/ext/common/Utils.h +8 -1
  41. data/ext/common/Utils/HttpHeaderBufferer.h +1 -1
  42. data/ext/common/Utils/IOUtils.cpp +5 -4
  43. data/ext/common/Utils/IOUtils.h +32 -14
  44. data/ext/common/Utils/MessagePassing.h +2 -2
  45. data/ext/common/Utils/ProcessMetricsCollector.h +47 -15
  46. data/ext/common/Utils/ScopeGuard.h +20 -3
  47. data/ext/common/Utils/StrIntUtils.h +14 -5
  48. data/ext/common/agents/Base.cpp +161 -50
  49. data/ext/common/agents/HelperAgent/AgentOptions.h +2 -2
  50. data/ext/common/agents/HelperAgent/Main.cpp +1 -0
  51. data/ext/common/agents/HelperAgent/RequestHandler.h +166 -52
  52. data/ext/common/agents/LoggingAgent/Main.cpp +1 -1
  53. data/ext/common/agents/Watchdog/Main.cpp +2 -2
  54. data/ext/nginx/Configuration.c +31 -4
  55. data/ext/nginx/Configuration.h +1 -0
  56. data/ext/nginx/ContentHandler.c +148 -34
  57. data/ext/nginx/ngx_http_passenger_module.c +4 -1
  58. data/ext/oxt/detail/spin_lock_pthreads.hpp +4 -4
  59. data/ext/oxt/macros.hpp +30 -8
  60. data/lib/phusion_passenger.rb +2 -2
  61. data/lib/phusion_passenger/classic_rails/thread_handler_extension.rb +1 -1
  62. data/lib/phusion_passenger/native_support.rb +19 -1
  63. data/lib/phusion_passenger/platform_info/compiler.rb +6 -0
  64. data/lib/phusion_passenger/platform_info/ruby.rb +54 -5
  65. data/lib/phusion_passenger/preloader_shared_helpers.rb +8 -1
  66. data/lib/phusion_passenger/rack/out_of_band_gc.rb +3 -1
  67. data/lib/phusion_passenger/rack/thread_handler_extension.rb +32 -5
  68. data/lib/phusion_passenger/request_handler/thread_handler.rb +28 -8
  69. data/lib/phusion_passenger/ruby_core_enhancements.rb +9 -1
  70. data/lib/phusion_passenger/standalone/runtime_installer.rb +1 -0
  71. data/lib/phusion_passenger/utils/unseekable_socket.rb +50 -5
  72. data/passenger.gemspec +1 -1
  73. data/resources/templates/apache2/config_snippets.txt.erb +1 -1
  74. data/test/cxx/ApplicationPool2/PoolTest.cpp +4 -9
  75. data/test/cxx/RequestHandlerTest.cpp +5 -5
  76. data/test/ruby/classic_rails/loader_spec.rb +1 -1
  77. data/test/ruby/classic_rails/preloader_spec.rb +1 -1
  78. data/test/ruby/request_handler_spec.rb +207 -1
  79. data/test/ruby/shared/loader_sharedspec.rb +1 -0
  80. data/test/ruby/spec_helper.rb +11 -1
  81. data/test/stub/apache2/httpd.conf.erb +1 -1
  82. metadata +5 -3
  83. 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, 2011, 2012 Phusion
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
- STDERR.puts "Your RVM wrapper scripts are too old. Please " +
72
- "update them first by running 'rvm get head && " +
73
- "rvm reload && rvm repair all'."
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
- if GEM_HOME && GEM_HOME.include?("rvm/gems/")
215
- return File.basename(GEM_HOME)
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
- client.close if client && Process.pid == original_pid
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 { env[RACK_HIJACK_IO] ||= connection }
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
- status, headers, body = @app.call(env)
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
- body.each do |s|
123
- connection.write(s)
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
- Utils.print_exception("Passenger RequestHandler's client socket", e)
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 == "CONTENT_LENGTH" || header == "CONTENT_TYPE"
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 #{Thread.current}):\n" <<
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 _seek_ and _rewind_ on the input stream
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 _seek_ and
32
- # _rewind_, calling these methods will raise an exception. We don't want
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 _seek_ and _rewind_ on sockets, but this
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
- self
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