passenger 5.0.0.beta3 → 5.0.0.rc1

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 (218) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/.editorconfig +11 -5
  5. data/CHANGELOG +38 -0
  6. data/CONTRIBUTING.md +1 -4
  7. data/Gemfile +0 -1
  8. data/Gemfile.lock +0 -2
  9. data/Rakefile +33 -33
  10. data/bin/passenger +1 -1
  11. data/bin/passenger-config +1 -1
  12. data/bin/passenger-install-apache2-module +800 -800
  13. data/bin/passenger-install-nginx-module +592 -592
  14. data/bin/passenger-memory-stats +127 -127
  15. data/bin/passenger-status +216 -216
  16. data/build/agents.rb +127 -127
  17. data/build/apache2.rb +87 -87
  18. data/build/basics.rb +60 -60
  19. data/build/common_library.rb +165 -165
  20. data/build/cplusplus_support.rb +51 -51
  21. data/build/cxx_tests.rb +268 -268
  22. data/build/debian.rb +143 -143
  23. data/build/documentation.rb +58 -58
  24. data/build/integration_tests.rb +81 -81
  25. data/build/misc.rb +132 -132
  26. data/build/nginx.rb +20 -20
  27. data/build/node_tests.rb +7 -7
  28. data/build/oxt_tests.rb +14 -14
  29. data/build/packaging.rb +570 -570
  30. data/build/preprocessor.rb +260 -260
  31. data/build/rake_extensions.rb +71 -71
  32. data/build/ruby_extension.rb +29 -29
  33. data/build/ruby_tests.rb +6 -6
  34. data/build/test_basics.rb +37 -37
  35. data/debian.template/control.template +3 -5
  36. data/dev/copy_boost_headers +134 -134
  37. data/dev/install_scripts_bootstrap_code.rb +25 -25
  38. data/dev/list_tests +20 -20
  39. data/dev/ruby_server.rb +223 -223
  40. data/dev/runner +18 -18
  41. data/doc/ServerOptimizationGuide.txt.md +55 -2
  42. data/doc/Users guide Nginx.txt +0 -26
  43. data/doc/Users guide Standalone.txt +5 -1
  44. data/doc/users_guide_snippets/tips.txt +9 -0
  45. data/ext/common/ApplicationPool2/Group.h +23 -11
  46. data/ext/common/ApplicationPool2/Implementation.cpp +32 -7
  47. data/ext/common/ApplicationPool2/Pool.h +22 -17
  48. data/ext/common/ApplicationPool2/SmartSpawner.h +4 -1
  49. data/ext/common/ApplicationPool2/Spawner.h +1 -1
  50. data/ext/common/Constants.h +1 -1
  51. data/ext/common/agents/Base.cpp +35 -20
  52. data/ext/common/agents/HelperAgent/Main.cpp +8 -1
  53. data/ext/common/agents/HelperAgent/OptionParser.h +18 -4
  54. data/ext/common/agents/HelperAgent/RequestHandler.h +2 -83
  55. data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +54 -1
  56. data/ext/common/agents/HelperAgent/RequestHandler/InitRequest.cpp +7 -4
  57. data/ext/common/agents/Main.cpp +1 -1
  58. data/ext/common/agents/Watchdog/Main.cpp +54 -19
  59. data/ext/nginx/Configuration.c +7 -0
  60. data/ext/nginx/ContentHandler.c +9 -1
  61. data/helper-scripts/backtrace-sanitizer.rb +106 -87
  62. data/helper-scripts/crash-watch.rb +32 -0
  63. data/helper-scripts/download_binaries/extconf.rb +38 -38
  64. data/helper-scripts/meteor-loader.rb +107 -107
  65. data/helper-scripts/prespawn +101 -101
  66. data/helper-scripts/rack-loader.rb +96 -96
  67. data/helper-scripts/rack-preloader.rb +137 -137
  68. data/lib/phusion_passenger.rb +292 -292
  69. data/lib/phusion_passenger/abstract_installer.rb +438 -438
  70. data/lib/phusion_passenger/active_support3_extensions/init.rb +168 -170
  71. data/lib/phusion_passenger/admin_tools.rb +20 -20
  72. data/lib/phusion_passenger/admin_tools/instance.rb +178 -178
  73. data/lib/phusion_passenger/admin_tools/instance_registry.rb +61 -61
  74. data/lib/phusion_passenger/admin_tools/memory_stats.rb +267 -267
  75. data/lib/phusion_passenger/apache2/config_options.rb +182 -182
  76. data/lib/phusion_passenger/common_library.rb +479 -485
  77. data/lib/phusion_passenger/config/about_command.rb +161 -161
  78. data/lib/phusion_passenger/config/admin_command_command.rb +129 -129
  79. data/lib/phusion_passenger/config/agent_compiler.rb +121 -121
  80. data/lib/phusion_passenger/config/build_native_support_command.rb +43 -43
  81. data/lib/phusion_passenger/config/command.rb +25 -25
  82. data/lib/phusion_passenger/config/compile_agent_command.rb +62 -62
  83. data/lib/phusion_passenger/config/compile_nginx_engine_command.rb +88 -73
  84. data/lib/phusion_passenger/config/detach_process_command.rb +72 -72
  85. data/lib/phusion_passenger/config/download_agent_command.rb +246 -227
  86. data/lib/phusion_passenger/config/download_nginx_engine_command.rb +245 -224
  87. data/lib/phusion_passenger/config/install_agent_command.rb +144 -132
  88. data/lib/phusion_passenger/config/install_standalone_runtime_command.rb +205 -185
  89. data/lib/phusion_passenger/config/installation_utils.rb +204 -204
  90. data/lib/phusion_passenger/config/list_instances_command.rb +64 -64
  91. data/lib/phusion_passenger/config/main.rb +152 -152
  92. data/lib/phusion_passenger/config/nginx_engine_compiler.rb +319 -300
  93. data/lib/phusion_passenger/config/reopen_logs_command.rb +67 -67
  94. data/lib/phusion_passenger/config/restart_app_command.rb +155 -155
  95. data/lib/phusion_passenger/config/system_metrics_command.rb +13 -13
  96. data/lib/phusion_passenger/config/utils.rb +95 -95
  97. data/lib/phusion_passenger/config/validate_install_command.rb +198 -198
  98. data/lib/phusion_passenger/console_text_template.rb +25 -25
  99. data/lib/phusion_passenger/constants.rb +90 -90
  100. data/lib/phusion_passenger/debug_logging.rb +106 -106
  101. data/lib/phusion_passenger/loader_shared_helpers.rb +447 -432
  102. data/lib/phusion_passenger/message_channel.rb +312 -312
  103. data/lib/phusion_passenger/message_client.rb +176 -176
  104. data/lib/phusion_passenger/native_support.rb +369 -369
  105. data/lib/phusion_passenger/nginx/config_options.rb +297 -297
  106. data/lib/phusion_passenger/packaging.rb +131 -131
  107. data/lib/phusion_passenger/platform_info.rb +360 -360
  108. data/lib/phusion_passenger/platform_info/apache.rb +767 -767
  109. data/lib/phusion_passenger/platform_info/apache_detector.rb +199 -199
  110. data/lib/phusion_passenger/platform_info/binary_compatibility.rb +107 -107
  111. data/lib/phusion_passenger/platform_info/compiler.rb +570 -570
  112. data/lib/phusion_passenger/platform_info/curl.rb +32 -32
  113. data/lib/phusion_passenger/platform_info/cxx_portability.rb +188 -188
  114. data/lib/phusion_passenger/platform_info/depcheck.rb +372 -372
  115. data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +109 -109
  116. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +4 -4
  117. data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +10 -34
  118. data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +101 -101
  119. data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +5 -5
  120. data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +13 -13
  121. data/lib/phusion_passenger/platform_info/linux.rb +55 -55
  122. data/lib/phusion_passenger/platform_info/operating_system.rb +149 -149
  123. data/lib/phusion_passenger/platform_info/ruby.rb +468 -448
  124. data/lib/phusion_passenger/platform_info/zlib.rb +9 -9
  125. data/lib/phusion_passenger/plugin.rb +66 -66
  126. data/lib/phusion_passenger/preloader_shared_helpers.rb +126 -126
  127. data/lib/phusion_passenger/public_api.rb +191 -191
  128. data/lib/phusion_passenger/rack/out_of_band_gc.rb +93 -94
  129. data/lib/phusion_passenger/rack/thread_handler_extension.rb +231 -227
  130. data/lib/phusion_passenger/request_handler.rb +567 -577
  131. data/lib/phusion_passenger/request_handler/thread_handler.rb +379 -381
  132. data/lib/phusion_passenger/ruby_core_enhancements.rb +86 -86
  133. data/lib/phusion_passenger/ruby_core_io_enhancements.rb +74 -74
  134. data/lib/phusion_passenger/simple_benchmarking.rb +25 -25
  135. data/lib/phusion_passenger/standalone/app_finder.rb +153 -150
  136. data/lib/phusion_passenger/standalone/command.rb +44 -40
  137. data/lib/phusion_passenger/standalone/config_utils.rb +53 -53
  138. data/lib/phusion_passenger/standalone/control_utils.rb +38 -59
  139. data/lib/phusion_passenger/standalone/main.rb +73 -73
  140. data/lib/phusion_passenger/standalone/start_command.rb +697 -685
  141. data/lib/phusion_passenger/standalone/start_command/builtin_engine.rb +193 -155
  142. data/lib/phusion_passenger/standalone/start_command/nginx_engine.rb +162 -133
  143. data/lib/phusion_passenger/standalone/status_command.rb +64 -64
  144. data/lib/phusion_passenger/standalone/stop_command.rb +72 -72
  145. data/lib/phusion_passenger/standalone/version_command.rb +9 -9
  146. data/lib/phusion_passenger/union_station/connection.rb +32 -32
  147. data/lib/phusion_passenger/union_station/core.rb +251 -251
  148. data/lib/phusion_passenger/union_station/transaction.rb +126 -126
  149. data/lib/phusion_passenger/utils.rb +199 -167
  150. data/lib/phusion_passenger/utils/ansi_colors.rb +128 -128
  151. data/lib/phusion_passenger/utils/download.rb +196 -196
  152. data/lib/phusion_passenger/utils/file_system_watcher.rb +158 -158
  153. data/lib/phusion_passenger/utils/hosts_file_parser.rb +101 -101
  154. data/lib/phusion_passenger/utils/lock.rb +31 -31
  155. data/lib/phusion_passenger/utils/native_support_utils.rb +31 -31
  156. data/lib/phusion_passenger/utils/progress_bar.rb +26 -26
  157. data/lib/phusion_passenger/utils/shellwords.rb +20 -20
  158. data/lib/phusion_passenger/utils/terminal_choice_menu.rb +206 -206
  159. data/lib/phusion_passenger/utils/unseekable_socket.rb +272 -272
  160. data/lib/phusion_passenger/vendor/crash_watch/app.rb +129 -0
  161. data/lib/phusion_passenger/vendor/crash_watch/gdb_controller.rb +341 -0
  162. data/lib/phusion_passenger/vendor/crash_watch/version.rb +24 -0
  163. data/lib/phusion_passenger/vendor/daemon_controller.rb +877 -0
  164. data/lib/phusion_passenger/vendor/daemon_controller/lock_file.rb +127 -0
  165. data/lib/phusion_passenger/vendor/daemon_controller/spawn.rb +26 -0
  166. data/lib/phusion_passenger/vendor/daemon_controller/version.rb +29 -0
  167. data/packaging/rpm/passenger_spec/passenger.spec.template +0 -1
  168. data/passenger.gemspec +0 -1
  169. data/resources/templates/config/nginx_engine_compiler/possible_solutions_for_download_and_extraction_problems.txt.erb +27 -0
  170. data/resources/templates/standalone/config.erb +19 -15
  171. data/test/integration_tests/apache2_tests.rb +566 -566
  172. data/test/integration_tests/downloaded_binaries_tests.rb +126 -125
  173. data/test/integration_tests/native_packaging_spec.rb +296 -296
  174. data/test/integration_tests/nginx_tests.rb +393 -393
  175. data/test/integration_tests/shared/example_webapp_tests.rb +282 -280
  176. data/test/integration_tests/source_packaging_test.rb +138 -138
  177. data/test/integration_tests/spec_helper.rb +5 -5
  178. data/test/integration_tests/standalone_tests.rb +367 -367
  179. data/test/ruby/debug_logging_spec.rb +133 -133
  180. data/test/ruby/message_channel_spec.rb +186 -186
  181. data/test/ruby/rack/loader_spec.rb +28 -28
  182. data/test/ruby/rack/preloader_spec.rb +34 -34
  183. data/test/ruby/rails3.0/loader_spec.rb +12 -12
  184. data/test/ruby/rails3.0/preloader_spec.rb +18 -18
  185. data/test/ruby/rails3.1/loader_spec.rb +12 -12
  186. data/test/ruby/rails3.1/preloader_spec.rb +18 -18
  187. data/test/ruby/rails3.2/loader_spec.rb +12 -12
  188. data/test/ruby/rails3.2/preloader_spec.rb +18 -18
  189. data/test/ruby/rails4.0/loader_spec.rb +12 -12
  190. data/test/ruby/rails4.0/preloader_spec.rb +18 -18
  191. data/test/ruby/rails4.1/loader_spec.rb +12 -12
  192. data/test/ruby/rails4.1/preloader_spec.rb +18 -18
  193. data/test/ruby/request_handler_spec.rb +730 -730
  194. data/test/ruby/shared/loader_sharedspec.rb +224 -224
  195. data/test/ruby/shared/rails/union_station_extensions_sharedspec.rb +327 -327
  196. data/test/ruby/shared/ruby_loader_sharedspec.rb +47 -47
  197. data/test/ruby/spec_helper.rb +65 -65
  198. data/test/ruby/standalone/runtime_installer_spec.rb +384 -384
  199. data/test/ruby/union_station_spec.rb +276 -276
  200. data/test/ruby/utils/file_system_watcher_spec.rb +220 -220
  201. data/test/ruby/utils/hosts_file_parser.rb +248 -248
  202. data/test/ruby/utils/tee_input_spec.rb +215 -215
  203. data/test/ruby/utils/unseekable_socket_spec.rb +57 -57
  204. data/test/ruby/utils_spec.rb +21 -21
  205. data/test/stub/rack/config.ru +87 -87
  206. data/test/stub/rack/library.rb +8 -8
  207. data/test/stub/rack/start.rb +30 -30
  208. data/test/support/apache2_controller.rb +191 -191
  209. data/test/support/nginx_controller.rb +90 -99
  210. data/test/support/placebo-preloader.rb +57 -57
  211. data/test/support/test_helper.rb +435 -435
  212. metadata +11 -21
  213. metadata.gz.asc +7 -7
  214. data/lib/phusion_passenger/standalone/command2.rb +0 -292
  215. data/lib/phusion_passenger/standalone/start2_command.rb +0 -799
  216. data/resources/templates/standalone/download_tool_missing.txt.erb +0 -18
  217. data/resources/templates/standalone/possible_solutions_for_download_and_extraction_problems.txt.erb +0 -17
  218. data/resources/templates/standalone/run_installer_as_root.txt.erb +0 -8
@@ -29,385 +29,383 @@ PhusionPassenger.require_passenger_lib 'utils/native_support_utils'
29
29
  PhusionPassenger.require_passenger_lib 'utils/unseekable_socket'
30
30
 
31
31
  module PhusionPassenger
32
- class RequestHandler
33
-
34
-
35
- # This class encapsulates the logic of a single RequestHandler thread.
36
- class ThreadHandler
37
- include DebugLogging
38
- include Utils
39
-
40
- class Interrupted < StandardError
41
- end
42
-
43
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
44
- GET = 'GET'.freeze
45
- PING = 'PING'.freeze
46
- OOBW = 'OOBW'.freeze
47
- PASSENGER_CONNECT_PASSWORD = 'PASSENGER_CONNECT_PASSWORD'.freeze
48
- CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
49
- TRANSFER_ENCODING = 'TRANSFER_ENCODING'.freeze
50
-
51
- MAX_HEADER_SIZE = 128 * 1024
52
-
53
- OBJECT_SPACE_SUPPORTS_LIVE_OBJECTS = ObjectSpace.respond_to?(:live_objects)
54
- OBJECT_SPACE_SUPPORTS_ALLOCATED_OBJECTS = ObjectSpace.respond_to?(:allocated_objects)
55
- OBJECT_SPACE_SUPPORTS_COUNT_OBJECTS = ObjectSpace.respond_to?(:count_objects)
56
- GC_SUPPORTS_TIME = GC.respond_to?(:time)
57
- GC_SUPPORTS_CLEAR_STATS = GC.respond_to?(:clear_stats)
58
-
59
- attr_reader :thread
60
- attr_reader :stats_mutex
61
- attr_reader :interruptable
62
- attr_reader :iteration
63
-
64
- def initialize(request_handler, options = {})
65
- @request_handler = request_handler
66
- @server_socket = Utils.require_option(options, :server_socket)
67
- @socket_name = Utils.require_option(options, :socket_name)
68
- @protocol = Utils.require_option(options, :protocol)
69
- @app_group_name = Utils.require_option(options, :app_group_name)
70
- Utils.install_options_as_ivars(self, options,
71
- :app,
72
- :union_station_core,
73
- :connect_password,
74
- :keepalive_enabled
75
- )
76
-
77
- @stats_mutex = Mutex.new
78
- @interruptable = false
79
- @iteration = 0
80
-
81
- if @protocol == :session
82
- metaclass = class << self; self; end
83
- metaclass.class_eval do
84
- alias parse_request parse_session_request
85
- end
86
- elsif @protocol == :http
87
- metaclass = class << self; self; end
88
- metaclass.class_eval do
89
- alias parse_request parse_http_request
90
- end
91
- else
92
- raise ArgumentError, "Unknown protocol specified"
93
- end
94
- end
95
-
96
- def install
97
- @thread = Thread.current
98
- Thread.current[:passenger_thread_handler] = self
99
- PhusionPassenger.call_event(:starting_request_handler_thread)
100
- end
101
-
102
- def main_loop(finish_callback)
103
- socket_wrapper = Utils::UnseekableSocket.new
104
- channel = MessageChannel.new
105
- buffer = ''
106
- buffer.force_encoding('binary') if buffer.respond_to?(:force_encoding)
107
-
108
- begin
109
- finish_callback.call
110
- while true
111
- hijacked = accept_and_process_next_request(socket_wrapper, channel, buffer)
112
- socket_wrapper = Utils::UnseekableSocket.new if hijacked
113
- end
114
- rescue Interrupted
115
- # Do nothing.
116
- end
117
- debug("Thread handler main loop exited normally")
118
- ensure
119
- @stats_mutex.synchronize { @interruptable = true }
120
- end
121
-
122
- private
123
- # Returns true if the socket has been hijacked, false otherwise.
124
- def accept_and_process_next_request(socket_wrapper, channel, buffer)
125
- @stats_mutex.synchronize do
126
- @interruptable = true
127
- end
128
- if @last_connection
129
- connection = @last_connection
130
- channel.io = connection
131
- @last_connection = nil
132
- headers = parse_request(connection, channel, buffer)
133
- else
134
- connection = socket_wrapper.wrap(@server_socket.accept)
135
- end
136
- @stats_mutex.synchronize do
137
- @interruptable = false
138
- @iteration += 1
139
- end
140
- trace(3, "Accepted new request on socket #{@socket_name}")
141
- if !headers
142
- # New socket accepted, instead of keeping-alive an old one
143
- channel.io = connection
144
- headers = parse_request(connection, channel, buffer)
145
- end
146
- if headers
147
- prepare_request(connection, headers)
148
- begin
149
- if headers[REQUEST_METHOD] == GET
150
- process_request(headers, connection, socket_wrapper, @protocol == :http)
151
- elsif headers[REQUEST_METHOD] == PING
152
- process_ping(headers, connection)
153
- elsif headers[REQUEST_METHOD] == OOBW
154
- process_oobw(headers, connection)
155
- else
156
- process_request(headers, connection, socket_wrapper, @protocol == :http)
157
- end
158
- rescue Exception
159
- has_error = true
160
- raise
161
- ensure
162
- if headers[RACK_HIJACK_IO]
163
- socket_wrapper = nil
164
- connection = nil
165
- channel = nil
166
- end
167
- finalize_request(connection, headers, has_error)
168
- trace(3, "Request done.")
169
- end
170
- else
171
- trace(2, "No headers parsed; disconnecting client.")
172
- end
173
- rescue Interrupted
174
- raise
175
- rescue => e
176
- if socket_wrapper && socket_wrapper.source_of_exception?(e)
177
- # EPIPE is harmless, it just means that the client closed the connection.
178
- # Other errors might indicate a problem so we print them, but they're
179
- # probably not bad enough to warrant stopping the request handler.
180
- if !e.is_a?(Errno::EPIPE)
181
- print_exception("Passenger RequestHandler's client socket", e)
182
- end
183
- else
184
- if headers
185
- PhusionPassenger.log_request_exception(headers, e)
186
- end
187
- raise e if should_reraise_error?(e)
188
- end
189
- ensure
190
- # Close connection if keep-alive not possible
191
- if connection && !connection.closed? && !@last_connection
192
- # The 'close_write' here prevents forked child
193
- # processes from unintentionally keeping the
194
- # connection open.
195
- begin
196
- connection.close_write
197
- rescue SystemCallError, IOError
198
- end
199
- begin
200
- connection.close
201
- rescue SystemCallError
202
- end
203
- end
204
- end
205
-
206
- def parse_session_request(connection, channel, buffer)
207
- headers_data = channel.read_scalar(buffer, MAX_HEADER_SIZE)
208
- if headers_data.nil?
209
- return
210
- end
211
- headers = Utils::NativeSupportUtils.split_by_null_into_hash(headers_data)
212
- if @connect_password && headers[PASSENGER_CONNECT_PASSWORD] != @connect_password
213
- warn "*** Passenger RequestHandler warning: " <<
214
- "someone tried to connect with an invalid connect password."
215
- return
216
- else
217
- return headers
218
- end
219
- rescue SecurityError => e
220
- warn("*** Passenger RequestHandler warning: " <<
221
- "HTTP header size exceeded maximum.")
222
- return
223
- end
224
-
225
- # Like parse_session_request, but parses an HTTP request. This is a very minimalistic
226
- # HTTP parser and is not intended to be complete, fast or secure, since the HTTP server
227
- # socket is intended to be used for debugging purposes only.
228
- def parse_http_request(connection, channel, buffer)
229
- headers = {}
230
-
231
- data = ""
232
- while data !~ /\r\n\r\n/ && data.size < MAX_HEADER_SIZE
233
- data << connection.readpartial(16 * 1024)
234
- end
235
- if data.size >= MAX_HEADER_SIZE
236
- warn("*** Passenger RequestHandler warning: " <<
237
- "HTTP header size exceeded maximum.")
238
- return
239
- end
240
-
241
- data.gsub!(/\r\n\r\n.*/, '')
242
- data.split("\r\n").each_with_index do |line, i|
243
- if i == 0
244
- # GET / HTTP/1.1
245
- line =~ /^([A-Za-z]+) (.+?) (HTTP\/\d\.\d)$/
246
- request_method = $1
247
- request_uri = $2
248
- protocol = $3
249
- path_info, query_string = request_uri.split("?", 2)
250
- headers[REQUEST_METHOD] = request_method
251
- headers["REQUEST_URI"] = request_uri
252
- headers["QUERY_STRING"] = query_string || ""
253
- headers["SCRIPT_NAME"] = ""
254
- headers["PATH_INFO"] = path_info
255
- headers["SERVER_NAME"] = "127.0.0.1"
256
- headers["SERVER_PORT"] = connection.addr[1].to_s
257
- headers["SERVER_PROTOCOL"] = protocol
258
- else
259
- header, value = line.split(/\s*:\s*/, 2)
260
- header.upcase! # "Foo-Bar" => "FOO-BAR"
261
- header.gsub!("-", "_") # => "FOO_BAR"
262
- if header == CONTENT_LENGTH || header == "CONTENT_TYPE"
263
- headers[header] = value
264
- else
265
- headers["HTTP_#{header}"] = value
266
- end
267
- end
268
- end
269
-
270
- if @connect_password && headers["HTTP_X_PASSENGER_CONNECT_PASSWORD"] != @connect_password
271
- warn "*** Passenger RequestHandler warning: " <<
272
- "someone tried to connect with an invalid connect password."
273
- return
274
- else
275
- return headers
276
- end
277
- rescue EOFError
278
- return
279
- end
280
-
281
- def process_ping(env, connection)
282
- connection.write("pong")
283
- end
284
-
285
- def process_oobw(env, connection)
286
- PhusionPassenger.call_event(:oob_work)
287
- connection.write("oobw done")
288
- end
289
-
290
- # def process_request(env, connection, socket_wrapper, full_http_response)
291
- # raise NotImplementedError, "Override with your own implementation!"
292
- # end
293
-
294
- def prepare_request(connection, headers)
295
- transfer_encoding = headers[TRANSFER_ENCODING]
296
- content_length = headers[CONTENT_LENGTH]
297
- @can_keepalive = @keepalive_enabled &&
298
- !transfer_encoding &&
299
- !content_length
300
- @keepalive_performed = false
301
-
302
- if !transfer_encoding && !content_length
303
- connection.simulate_eof!
304
- end
305
-
306
- if @union_station_core && headers[PASSENGER_TXN_ID]
307
- txn_id = headers[PASSENGER_TXN_ID]
308
- union_station_key = headers[PASSENGER_UNION_STATION_KEY]
309
- transaction = @union_station_core.continue_transaction(txn_id,
310
- @app_group_name,
311
- :requests, union_station_key)
312
- headers[UNION_STATION_REQUEST_TRANSACTION] = transaction
313
- headers[UNION_STATION_CORE] = @union_station_core
314
- headers[PASSENGER_APP_GROUP_NAME] = @app_group_name
315
- Thread.current[UNION_STATION_REQUEST_TRANSACTION] = transaction
316
- Thread.current[UNION_STATION_CORE] = @union_station_core
317
- Thread.current[PASSENGER_TXN_ID] = txn_id
318
- Thread.current[PASSENGER_UNION_STATION_KEY] = union_station_key
319
- if OBJECT_SPACE_SUPPORTS_LIVE_OBJECTS
320
- transaction.message("Initial objects on heap: #{ObjectSpace.live_objects}")
321
- end
322
- if OBJECT_SPACE_SUPPORTS_ALLOCATED_OBJECTS
323
- transaction.message("Initial objects allocated so far: #{ObjectSpace.allocated_objects}")
324
- elsif OBJECT_SPACE_SUPPORTS_COUNT_OBJECTS
325
- count = ObjectSpace.count_objects
326
- transaction.message("Initial objects allocated so far: #{count[:TOTAL] - count[:FREE]}")
327
- end
328
- if GC_SUPPORTS_TIME
329
- transaction.message("Initial GC time: #{GC.time}")
330
- end
331
- transaction.begin_measure("app request handler processing")
332
- end
333
-
334
- #################
335
- end
336
-
337
- def finalize_request(connection, headers, has_error)
338
- transaction = headers[UNION_STATION_REQUEST_TRANSACTION]
339
- Thread.current[UNION_STATION_CORE] = nil
340
- Thread.current[UNION_STATION_REQUEST_TRANSACTION] = nil
341
-
342
- if connection
343
- connection.stop_simulating_eof!
344
- end
345
-
346
- if transaction && !transaction.closed?
347
- exception_occurred = false
348
- begin
349
- transaction.end_measure("app request handler processing", has_error)
350
- if OBJECT_SPACE_SUPPORTS_LIVE_OBJECTS
351
- transaction.message("Final objects on heap: #{ObjectSpace.live_objects}")
352
- end
353
- if OBJECT_SPACE_SUPPORTS_ALLOCATED_OBJECTS
354
- transaction.message("Final objects allocated so far: #{ObjectSpace.allocated_objects}")
355
- elsif OBJECT_SPACE_SUPPORTS_COUNT_OBJECTS
356
- count = ObjectSpace.count_objects
357
- transaction.message("Final objects allocated so far: #{count[:TOTAL] - count[:FREE]}")
358
- end
359
- if GC_SUPPORTS_TIME
360
- transaction.message("Final GC time: #{GC.time}")
361
- end
362
- if GC_SUPPORTS_CLEAR_STATS
363
- # Clear statistics to void integer wraps.
364
- GC.clear_stats
365
- end
366
- rescue Exception
367
- # Maybe this exception was raised while communicating
368
- # with the logging agent. If that is the case then
369
- # transaction.close may also raise an exception, but we're only
370
- # interested in the original exception. So if this
371
- # situation occurs we must ignore any exceptions raised
372
- # by transaction.close.
373
- exception_occurred = true
374
- raise
375
- ensure
376
- # It is important that the following call receives an ACK
377
- # from the logging agent and that we don't close the socket
378
- # connection until the ACK has been received, otherwise
379
- # the helper agent may close the transaction before this
380
- # process's openTransaction command is processed.
381
- begin
382
- transaction.close
383
- rescue
384
- raise if !exception_occurred
385
- end
386
- end
387
- end
388
-
389
- if !has_error && @keepalive_performed && connection
390
- trace(3, "Keep-aliving connection.")
391
- @last_connection = connection
392
- end
393
-
394
- #################
395
- end
396
-
397
- def should_reraise_error?(e)
398
- # Stubable by unit tests.
399
- return true
400
- end
401
-
402
- def should_reraise_app_error?(e, socket_wrapper)
403
- return false
404
- end
405
-
406
- def should_swallow_app_error?(e, socket_wrapper)
407
- return socket_wrapper && socket_wrapper.source_of_exception?(e) && e.is_a?(Errno::EPIPE)
408
- end
409
- end
410
-
411
-
412
- end # class RequestHandler
32
+ class RequestHandler
33
+
34
+ # This class encapsulates the logic of a single RequestHandler thread.
35
+ class ThreadHandler
36
+ include DebugLogging
37
+ include Utils
38
+
39
+ class Interrupted < StandardError
40
+ end
41
+
42
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
43
+ GET = 'GET'.freeze
44
+ PING = 'PING'.freeze
45
+ OOBW = 'OOBW'.freeze
46
+ PASSENGER_CONNECT_PASSWORD = 'PASSENGER_CONNECT_PASSWORD'.freeze
47
+ CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
48
+ TRANSFER_ENCODING = 'TRANSFER_ENCODING'.freeze
49
+
50
+ MAX_HEADER_SIZE = 128 * 1024
51
+
52
+ OBJECT_SPACE_SUPPORTS_LIVE_OBJECTS = ObjectSpace.respond_to?(:live_objects)
53
+ OBJECT_SPACE_SUPPORTS_ALLOCATED_OBJECTS = ObjectSpace.respond_to?(:allocated_objects)
54
+ OBJECT_SPACE_SUPPORTS_COUNT_OBJECTS = ObjectSpace.respond_to?(:count_objects)
55
+ GC_SUPPORTS_TIME = GC.respond_to?(:time)
56
+ GC_SUPPORTS_CLEAR_STATS = GC.respond_to?(:clear_stats)
57
+
58
+ attr_reader :thread
59
+ attr_reader :stats_mutex
60
+ attr_reader :interruptable
61
+ attr_reader :iteration
62
+
63
+ def initialize(request_handler, options = {})
64
+ @request_handler = request_handler
65
+ @server_socket = Utils.require_option(options, :server_socket)
66
+ @socket_name = Utils.require_option(options, :socket_name)
67
+ @protocol = Utils.require_option(options, :protocol)
68
+ @app_group_name = Utils.require_option(options, :app_group_name)
69
+ Utils.install_options_as_ivars(self, options,
70
+ :app,
71
+ :union_station_core,
72
+ :connect_password,
73
+ :keepalive_enabled
74
+ )
75
+
76
+ @stats_mutex = Mutex.new
77
+ @interruptable = false
78
+ @iteration = 0
79
+
80
+ if @protocol == :session
81
+ metaclass = class << self; self; end
82
+ metaclass.class_eval do
83
+ alias parse_request parse_session_request
84
+ end
85
+ elsif @protocol == :http
86
+ metaclass = class << self; self; end
87
+ metaclass.class_eval do
88
+ alias parse_request parse_http_request
89
+ end
90
+ else
91
+ raise ArgumentError, "Unknown protocol specified"
92
+ end
93
+ end
94
+
95
+ def install
96
+ @thread = Thread.current
97
+ Thread.current[:passenger_thread_handler] = self
98
+ PhusionPassenger.call_event(:starting_request_handler_thread)
99
+ end
100
+
101
+ def main_loop(finish_callback)
102
+ socket_wrapper = Utils::UnseekableSocket.new
103
+ channel = MessageChannel.new
104
+ buffer = ''
105
+ buffer.force_encoding('binary') if buffer.respond_to?(:force_encoding)
106
+
107
+ begin
108
+ finish_callback.call
109
+ while true
110
+ hijacked = accept_and_process_next_request(socket_wrapper, channel, buffer)
111
+ socket_wrapper = Utils::UnseekableSocket.new if hijacked
112
+ end
113
+ rescue Interrupted
114
+ # Do nothing.
115
+ end
116
+ debug("Thread handler main loop exited normally")
117
+ ensure
118
+ @stats_mutex.synchronize { @interruptable = true }
119
+ end
120
+
121
+ private
122
+ # Returns true if the socket has been hijacked, false otherwise.
123
+ def accept_and_process_next_request(socket_wrapper, channel, buffer)
124
+ @stats_mutex.synchronize do
125
+ @interruptable = true
126
+ end
127
+ if @last_connection
128
+ connection = @last_connection
129
+ channel.io = connection
130
+ @last_connection = nil
131
+ headers = parse_request(connection, channel, buffer)
132
+ else
133
+ connection = socket_wrapper.wrap(@server_socket.accept)
134
+ end
135
+ @stats_mutex.synchronize do
136
+ @interruptable = false
137
+ @iteration += 1
138
+ end
139
+ trace(3, "Accepted new request on socket #{@socket_name}")
140
+ if !headers
141
+ # New socket accepted, instead of keeping-alive an old one
142
+ channel.io = connection
143
+ headers = parse_request(connection, channel, buffer)
144
+ end
145
+ if headers
146
+ prepare_request(connection, headers)
147
+ begin
148
+ if headers[REQUEST_METHOD] == GET
149
+ process_request(headers, connection, socket_wrapper, @protocol == :http)
150
+ elsif headers[REQUEST_METHOD] == PING
151
+ process_ping(headers, connection)
152
+ elsif headers[REQUEST_METHOD] == OOBW
153
+ process_oobw(headers, connection)
154
+ else
155
+ process_request(headers, connection, socket_wrapper, @protocol == :http)
156
+ end
157
+ rescue Exception
158
+ has_error = true
159
+ raise
160
+ ensure
161
+ if headers[RACK_HIJACK_IO]
162
+ socket_wrapper = nil
163
+ connection = nil
164
+ channel = nil
165
+ end
166
+ finalize_request(connection, headers, has_error)
167
+ trace(3, "Request done.")
168
+ end
169
+ else
170
+ trace(2, "No headers parsed; disconnecting client.")
171
+ end
172
+ rescue Interrupted
173
+ raise
174
+ rescue => e
175
+ if socket_wrapper && socket_wrapper.source_of_exception?(e)
176
+ # EPIPE is harmless, it just means that the client closed the connection.
177
+ # Other errors might indicate a problem so we print them, but they're
178
+ # probably not bad enough to warrant stopping the request handler.
179
+ if !e.is_a?(Errno::EPIPE)
180
+ print_exception("Passenger RequestHandler's client socket", e)
181
+ end
182
+ else
183
+ if headers
184
+ PhusionPassenger.log_request_exception(headers, e)
185
+ end
186
+ raise e if should_reraise_error?(e)
187
+ end
188
+ ensure
189
+ # Close connection if keep-alive not possible
190
+ if connection && !connection.closed? && !@last_connection
191
+ # The 'close_write' here prevents forked child
192
+ # processes from unintentionally keeping the
193
+ # connection open.
194
+ begin
195
+ connection.close_write
196
+ rescue SystemCallError, IOError
197
+ end
198
+ begin
199
+ connection.close
200
+ rescue SystemCallError
201
+ end
202
+ end
203
+ end
204
+
205
+ def parse_session_request(connection, channel, buffer)
206
+ headers_data = channel.read_scalar(buffer, MAX_HEADER_SIZE)
207
+ if headers_data.nil?
208
+ return
209
+ end
210
+ headers = Utils::NativeSupportUtils.split_by_null_into_hash(headers_data)
211
+ if @connect_password && headers[PASSENGER_CONNECT_PASSWORD] != @connect_password
212
+ warn "*** Passenger RequestHandler warning: " <<
213
+ "someone tried to connect with an invalid connect password."
214
+ return
215
+ else
216
+ return headers
217
+ end
218
+ rescue SecurityError => e
219
+ warn("*** Passenger RequestHandler warning: " <<
220
+ "HTTP header size exceeded maximum.")
221
+ return
222
+ end
223
+
224
+ # Like parse_session_request, but parses an HTTP request. This is a very minimalistic
225
+ # HTTP parser and is not intended to be complete, fast or secure, since the HTTP server
226
+ # socket is intended to be used for debugging purposes only.
227
+ def parse_http_request(connection, channel, buffer)
228
+ headers = {}
229
+
230
+ data = ""
231
+ while data !~ /\r\n\r\n/ && data.size < MAX_HEADER_SIZE
232
+ data << connection.readpartial(16 * 1024)
233
+ end
234
+ if data.size >= MAX_HEADER_SIZE
235
+ warn("*** Passenger RequestHandler warning: " <<
236
+ "HTTP header size exceeded maximum.")
237
+ return
238
+ end
239
+
240
+ data.gsub!(/\r\n\r\n.*/, '')
241
+ data.split("\r\n").each_with_index do |line, i|
242
+ if i == 0
243
+ # GET / HTTP/1.1
244
+ line =~ /^([A-Za-z]+) (.+?) (HTTP\/\d\.\d)$/
245
+ request_method = $1
246
+ request_uri = $2
247
+ protocol = $3
248
+ path_info, query_string = request_uri.split("?", 2)
249
+ headers[REQUEST_METHOD] = request_method
250
+ headers["REQUEST_URI"] = request_uri
251
+ headers["QUERY_STRING"] = query_string || ""
252
+ headers["SCRIPT_NAME"] = ""
253
+ headers["PATH_INFO"] = path_info
254
+ headers["SERVER_NAME"] = "127.0.0.1"
255
+ headers["SERVER_PORT"] = connection.addr[1].to_s
256
+ headers["SERVER_PROTOCOL"] = protocol
257
+ else
258
+ header, value = line.split(/\s*:\s*/, 2)
259
+ header.upcase! # "Foo-Bar" => "FOO-BAR"
260
+ header.gsub!("-", "_") # => "FOO_BAR"
261
+ if header == CONTENT_LENGTH || header == "CONTENT_TYPE"
262
+ headers[header] = value
263
+ else
264
+ headers["HTTP_#{header}"] = value
265
+ end
266
+ end
267
+ end
268
+
269
+ if @connect_password && headers["HTTP_X_PASSENGER_CONNECT_PASSWORD"] != @connect_password
270
+ warn "*** Passenger RequestHandler warning: " <<
271
+ "someone tried to connect with an invalid connect password."
272
+ return
273
+ else
274
+ return headers
275
+ end
276
+ rescue EOFError
277
+ return
278
+ end
279
+
280
+ def process_ping(env, connection)
281
+ connection.write("pong")
282
+ end
283
+
284
+ def process_oobw(env, connection)
285
+ PhusionPassenger.call_event(:oob_work)
286
+ connection.write("oobw done")
287
+ end
288
+
289
+ # def process_request(env, connection, socket_wrapper, full_http_response)
290
+ # raise NotImplementedError, "Override with your own implementation!"
291
+ # end
292
+
293
+ def prepare_request(connection, headers)
294
+ transfer_encoding = headers[TRANSFER_ENCODING]
295
+ content_length = headers[CONTENT_LENGTH]
296
+ @can_keepalive = @keepalive_enabled &&
297
+ !transfer_encoding &&
298
+ !content_length
299
+ @keepalive_performed = false
300
+
301
+ if !transfer_encoding && !content_length
302
+ connection.simulate_eof!
303
+ end
304
+
305
+ if @union_station_core && headers[PASSENGER_TXN_ID]
306
+ txn_id = headers[PASSENGER_TXN_ID]
307
+ union_station_key = headers[PASSENGER_UNION_STATION_KEY]
308
+ transaction = @union_station_core.continue_transaction(txn_id,
309
+ @app_group_name,
310
+ :requests, union_station_key)
311
+ headers[UNION_STATION_REQUEST_TRANSACTION] = transaction
312
+ headers[UNION_STATION_CORE] = @union_station_core
313
+ headers[PASSENGER_APP_GROUP_NAME] = @app_group_name
314
+ Thread.current[UNION_STATION_REQUEST_TRANSACTION] = transaction
315
+ Thread.current[UNION_STATION_CORE] = @union_station_core
316
+ Thread.current[PASSENGER_TXN_ID] = txn_id
317
+ Thread.current[PASSENGER_UNION_STATION_KEY] = union_station_key
318
+ if OBJECT_SPACE_SUPPORTS_LIVE_OBJECTS
319
+ transaction.message("Initial objects on heap: #{ObjectSpace.live_objects}")
320
+ end
321
+ if OBJECT_SPACE_SUPPORTS_ALLOCATED_OBJECTS
322
+ transaction.message("Initial objects allocated so far: #{ObjectSpace.allocated_objects}")
323
+ elsif OBJECT_SPACE_SUPPORTS_COUNT_OBJECTS
324
+ count = ObjectSpace.count_objects
325
+ transaction.message("Initial objects allocated so far: #{count[:TOTAL] - count[:FREE]}")
326
+ end
327
+ if GC_SUPPORTS_TIME
328
+ transaction.message("Initial GC time: #{GC.time}")
329
+ end
330
+ transaction.begin_measure("app request handler processing")
331
+ end
332
+
333
+ #################
334
+ end
335
+
336
+ def finalize_request(connection, headers, has_error)
337
+ transaction = headers[UNION_STATION_REQUEST_TRANSACTION]
338
+ Thread.current[UNION_STATION_CORE] = nil
339
+ Thread.current[UNION_STATION_REQUEST_TRANSACTION] = nil
340
+
341
+ if connection
342
+ connection.stop_simulating_eof!
343
+ end
344
+
345
+ if transaction && !transaction.closed?
346
+ exception_occurred = false
347
+ begin
348
+ transaction.end_measure("app request handler processing", has_error)
349
+ if OBJECT_SPACE_SUPPORTS_LIVE_OBJECTS
350
+ transaction.message("Final objects on heap: #{ObjectSpace.live_objects}")
351
+ end
352
+ if OBJECT_SPACE_SUPPORTS_ALLOCATED_OBJECTS
353
+ transaction.message("Final objects allocated so far: #{ObjectSpace.allocated_objects}")
354
+ elsif OBJECT_SPACE_SUPPORTS_COUNT_OBJECTS
355
+ count = ObjectSpace.count_objects
356
+ transaction.message("Final objects allocated so far: #{count[:TOTAL] - count[:FREE]}")
357
+ end
358
+ if GC_SUPPORTS_TIME
359
+ transaction.message("Final GC time: #{GC.time}")
360
+ end
361
+ if GC_SUPPORTS_CLEAR_STATS
362
+ # Clear statistics to void integer wraps.
363
+ GC.clear_stats
364
+ end
365
+ rescue Exception
366
+ # Maybe this exception was raised while communicating
367
+ # with the logging agent. If that is the case then
368
+ # transaction.close may also raise an exception, but we're only
369
+ # interested in the original exception. So if this
370
+ # situation occurs we must ignore any exceptions raised
371
+ # by transaction.close.
372
+ exception_occurred = true
373
+ raise
374
+ ensure
375
+ # It is important that the following call receives an ACK
376
+ # from the logging agent and that we don't close the socket
377
+ # connection until the ACK has been received, otherwise
378
+ # the helper agent may close the transaction before this
379
+ # process's openTransaction command is processed.
380
+ begin
381
+ transaction.close
382
+ rescue
383
+ raise if !exception_occurred
384
+ end
385
+ end
386
+ end
387
+
388
+ if !has_error && @keepalive_performed && connection
389
+ trace(3, "Keep-aliving connection.")
390
+ @last_connection = connection
391
+ end
392
+
393
+ #################
394
+ end
395
+
396
+ def should_reraise_error?(e)
397
+ # Stubable by unit tests.
398
+ return true
399
+ end
400
+
401
+ def should_reraise_app_error?(e, socket_wrapper)
402
+ return false
403
+ end
404
+
405
+ def should_swallow_app_error?(e, socket_wrapper)
406
+ return socket_wrapper && socket_wrapper.source_of_exception?(e) && e.is_a?(Errno::EPIPE)
407
+ end
408
+ end
409
+
410
+ end # class RequestHandler
413
411
  end # module PhusionPassenger