File: //lib/ruby/vendor_ruby/rack/proxy.rb
require "net_http_hacked"
require "rack/http_streaming_response"
module Rack
# Subclass and bring your own #rewrite_request and #rewrite_response
class Proxy
VERSION = "0.7.7".freeze
HOP_BY_HOP_HEADERS = {
'connection' => true,
'keep-alive' => true,
'proxy-authenticate' => true,
'proxy-authorization' => true,
'te' => true,
'trailer' => true,
'transfer-encoding' => true,
'upgrade' => true
}.freeze
class << self
def extract_http_request_headers(env)
headers = env.reject do |k, v|
!(/^HTTP_[A-Z0-9_\.]+$/ === k) || v.nil?
end.map do |k, v|
[reconstruct_header_name(k), v]
end.then { |pairs| build_header_hash(pairs) }
x_forwarded_for = (headers['X-Forwarded-For'].to_s.split(/, +/) << env['REMOTE_ADDR']).join(', ')
headers.merge!('X-Forwarded-For' => x_forwarded_for)
end
def normalize_headers(headers)
mapped = headers.map do |k, v|
[titleize(k), if v.is_a? Array then v.join("\n") else v end]
end
build_header_hash Hash[mapped]
end
def build_header_hash(pairs)
if Rack.const_defined?(:Headers)
# Rack::Headers is only available from Rack 3 onward
Headers.new.tap { |headers| pairs.each { |k, v| headers[k] = v } }
else
# Rack::Utils::HeaderHash is deprecated from Rack 3 onward and is to be removed in 3.1
Utils::HeaderHash.new(pairs)
end
end
protected
def reconstruct_header_name(name)
titleize(name.sub(/^HTTP_/, "").gsub("_", "-"))
end
def titleize(str)
str.split("-").map(&:capitalize).join("-")
end
end
# @option opts [String, URI::HTTP] :backend Backend host to proxy requests to
def initialize(app = nil, opts= {})
if app.is_a?(Hash)
opts = app
@app = nil
else
@app = app
end
@streaming = opts.fetch(:streaming, true)
@ssl_verify_none = opts.fetch(:ssl_verify_none, false)
@backend = opts[:backend] ? URI(opts[:backend]) : nil
@read_timeout = opts.fetch(:read_timeout, 60)
@ssl_version = opts[:ssl_version]
@cert = opts[:cert]
@key = opts[:key]
@verify_mode = opts[:verify_mode]
@username = opts[:username]
@password = opts[:password]
@opts = opts
end
def call(env)
rewrite_response(perform_request(rewrite_env(env)))
end
# Return modified env
def rewrite_env(env)
env
end
# Return a rack triplet [status, headers, body]
def rewrite_response(triplet)
triplet
end
protected
def perform_request(env)
source_request = Rack::Request.new(env)
# Initialize request
if source_request.fullpath == ""
full_path = URI.parse(env['REQUEST_URI']).request_uri
else
full_path = source_request.fullpath
end
target_request = Net::HTTP.const_get(source_request.request_method.capitalize, false).new(full_path)
# Setup headers
target_request.initialize_http_header(self.class.extract_http_request_headers(source_request.env))
# Setup body
if target_request.request_body_permitted? && source_request.body
target_request.body_stream = source_request.body
target_request.content_length = source_request.content_length.to_i
target_request.content_type = source_request.content_type if source_request.content_type
target_request.body_stream.rewind
end
# Use basic auth if we have to
target_request.basic_auth(@username, @password) if @username && @password
backend = env.delete('rack.backend') || @backend || source_request
use_ssl = backend.scheme == "https" || @cert
read_timeout = env.delete('http.read_timeout') || @read_timeout
# Create the response
if @streaming
# streaming response (the actual network communication is deferred, a.k.a. streamed)
target_response = HttpStreamingResponse.new(target_request, backend.host, backend.port)
target_response.use_ssl = use_ssl
target_response.read_timeout = read_timeout
target_response.ssl_version = @ssl_version if @ssl_version
target_response.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_NONE) if use_ssl
target_response.cert = @cert if @cert
target_response.key = @key if @key
else
http = Net::HTTP.new(backend.host, backend.port)
http.use_ssl = use_ssl if use_ssl
http.read_timeout = read_timeout
http.ssl_version = @ssl_version if @ssl_version
http.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_NONE if use_ssl) if use_ssl
http.cert = @cert if @cert
http.key = @key if @key
target_response = http.start do
http.request(target_request)
end
end
code = target_response.code
headers = self.class.normalize_headers(target_response.respond_to?(:headers) ? target_response.headers : target_response.to_hash)
body = target_response.body || [""]
body = [body] unless body.respond_to?(:each)
# According to https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3.1Acc
# should remove hop-by-hop header fields
headers.reject! { |k| HOP_BY_HOP_HEADERS[k.downcase] }
[code, headers, body]
end
end
end