module PhusionPassenger::Rack::ThreadHandlerExtension
def bytesize(str)
def bytesize(str) str.bytesize end
def bytesize(str)
def bytesize(str) str.size end
def chunk_data(data, size)
def chunk_data(data, size) [size.to_s(16), CRLF, data, CRLF] end
def force_binary(str)
def force_binary(str) str.force_encoding(BINARY) end
def force_binary(str)
def force_binary(str) str end
def generate_headers_array(status, headers)
def generate_headers_array(status, headers) status_str = status.to_s result = ["HTTP/1.1 #{status_str} Whatever\r\n"] headers.each do |key, values| if values.is_a?(String) values = values.split(NEWLINE) elsif key == RACK_HIJACK # We do not check for this key name in every loop # iteration as an optimization. next else values = values.to_s.split(NEWLINE) end values.each do |value| result << key result << NAME_VALUE_SEPARATOR result << value result << CRLF end end return result end
def perform_keep_alive(env, headers)
def perform_keep_alive(env, headers) if @can_keepalive @keepalive_performed = true else headers << CONNECTION_CLOSE_CRLF end end
def process_body(env, connection, socket_wrapper, status, headers, body)
def process_body(env, connection, socket_wrapper, status, headers, body) if hijack_callback = headers[RACK_HIJACK] # Application requested a partial socket hijack. body = nil headers_output = generate_headers_array(status, headers) headers_output << "Connection: close\r\n" headers_output << CRLF connection.writev(headers_output) connection.flush hijacked_socket = env[RACK_HIJACK].call hijack_callback.call(hijacked_socket) true elsif body.is_a?(Array) # The body may be an ActionController::StringCoercion::UglyBody # object instead of a real Array, even when #is_a? claims so. # Call #to_a just to be sure. body = body.to_a output_body = should_output_body?(status, env) headers_output = generate_headers_array(status, headers) perform_keep_alive(env, headers_output) if output_body && should_add_message_length_header?(status, headers) body_size = 0 body.each { |part| body_size += bytesize(part.to_s) } headers_output << CONTENT_LENGTH_HEADER_AND_SEPARATOR headers_output << body_size.to_s headers_output << CRLF end headers_output << CRLF if output_body connection.writev2(headers_output, body) else connection.writev(headers_output) end false elsif body.is_a?(String) output_body = should_output_body?(status, env) headers_output = generate_headers_array(status, headers) perform_keep_alive(env, headers_output) if output_body && should_add_message_length_header?(status, headers) headers_output << CONTENT_LENGTH_HEADER_AND_SEPARATOR headers_output << bytesize(body).to_s headers_output << CRLF end headers_output << CRLF if output_body headers_output << body end connection.writev(headers_output) false else output_body = should_output_body?(status, env) headers_output = generate_headers_array(status, headers) perform_keep_alive(env, headers_output) chunk = output_body && should_add_message_length_header?(status, headers) if chunk headers_output << TRANSFER_ENCODING_HEADER_AND_VALUE_CRLF2 else headers_output << CRLF end connection.writev(headers_output) if output_body && body begin if chunk body.each do |part| part = force_binary(part) size = bytesize(part) if size != 0 connection.writev(chunk_data(part, size)) end end connection.write(TERMINATION_CHUNK) else body.each do |s| connection.write(s) end end rescue => e if should_reraise_app_error?(e, socket_wrapper) raise e elsif !should_swallow_app_error?(e, socket_wrapper) # Body objects can raise exceptions in #each. print_exception("Rack body object #each method", e) end false end end false end end
def process_request(env, connection, socket_wrapper, full_http_response)
def process_request(env, connection, socket_wrapper, full_http_response) rewindable_input = PhusionPassenger::Utils::TeeInput.new(connection, env) begin env[RACK_VERSION] = RACK_VERSION_VALUE env[RACK_INPUT] = rewindable_input env[RACK_ERRORS] = STDERR env[RACK_MULTITHREAD] = @request_handler.concurrency > 1 env[RACK_MULTIPROCESS] = true env[RACK_RUN_ONCE] = false if env[HTTPS] == YES || env[HTTPS] == ON || env[HTTPS] == ONE env[RACK_URL_SCHEME] = HTTPS_DOWNCASE else env[RACK_URL_SCHEME] = HTTP end env[RACK_HIJACK_P] = true env[RACK_HIJACK] = lambda do env[RACK_HIJACK_IO] ||= begin connection.stop_simulating_eof! connection end end begin status, headers, body = @app.call(env) rescue => e if should_reraise_app_error?(e, socket_wrapper) raise e elsif !should_swallow_app_error?(e, socket_wrapper) # It's a good idea to catch application exceptions here because # otherwise maliciously crafted responses can crash the app, # forcing it to be respawned, and thereby effectively DoSing it. print_exception("Rack application object", e) PhusionPassenger.log_request_exception(env, e) end return false end # Application requested a full socket hijack. return true if env[RACK_HIJACK_IO] begin process_body(env, connection, socket_wrapper, status.to_i, headers, body) ensure body.close if body && body.respond_to?(:close) end ensure rewindable_input.close end end
def should_add_message_length_header?(status, headers)
def should_add_message_length_header?(status, headers) return !headers.has_key?(TRANSFER_ENCODING_HEADER) && !headers.has_key?(CONTENT_LENGTH_HEADER) end
def should_output_body?(status, env)
def should_output_body?(status, env) return (status < 100 || (status >= 200 && status != 204 && status != 205 && status != 304)) && env[REQUEST_METHOD] != HEAD end