class ReverseProxy::Client

Constants

Attributes

callbacks[RW]
url[RW]

Public Class Methods

new(url) { |self| ... } click to toggle source
# File lib/reverse_proxy/client.rb, line 26
def initialize(url)
  self.url = url
  self.callbacks = {}

  # Initialize default callbacks with empty Proc
  @@callback_methods.each do |method|
    self.callbacks[method] = Proc.new {}
  end

  yield(self) if block_given?
end

Public Instance Methods

request(env, options = {}, &block) click to toggle source
# File lib/reverse_proxy/client.rb, line 38
def request(env, options = {}, &block)
  options.reverse_merge!(
    headers:    {},
    http:       {},
    path:       nil,
    username:   nil,
    password:   nil,
    verify_ssl: true
  )

  source_request = Rack::Request.new(env)

  # We can pass in a custom path
  uri = Addressable::URI.parse("#{url}#{options[:path] || env['ORIGINAL_FULLPATH']}")

  # Initialize request
  target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(uri.request_uri)

  # Setup headers
  target_request_headers = extract_http_request_headers(source_request.env).merge(options[:headers])

  target_request.initialize_http_header(target_request_headers)

  # Basic auth
  target_request.basic_auth(options[:username], options[:password]) if options[:username] and options[:password]

  # Setup body
  if target_request.request_body_permitted? \
     && source_request.body
    source_request.body.rewind
    target_request.body_stream = source_request.body
  end

  target_request.content_length = source_request.content_length || 0
  target_request.content_type   = source_request.content_type if source_request.content_type

  # Hold the response here
  target_response = nil

  # Don't encode response/support compression which was
  # causing content length not match the actual content
  # length of the response which ended up causing issues
  # within Varnish (503)
  target_request['Accept-Encoding'] = nil

  http_options = {}
  http_options[:use_ssl] = (uri.scheme == "https")
  http_options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE unless options[:verify_ssl]
  http_options.merge!(options[:http]) if options[:http]

  # Make the request
  Net::HTTP.start(uri.hostname, uri.port, http_options) do |http|
    callbacks[:on_connect].call(http)
    target_response = http.request(target_request)
  end

  status_code = target_response.code.to_i
  payload = [status_code, target_response]

  callbacks[:on_response].call(payload)

  if set_cookie_headers = target_response.to_hash['set-cookie']
    set_cookies_hash = {}

    set_cookie_headers.each do |set_cookie_header|
      set_cookie_hash = parse_cookie(set_cookie_header)
      name = set_cookie_hash[:name]
      set_cookies_hash[name] = set_cookie_hash
    end

    callbacks[:on_set_cookies].call(payload | [set_cookies_hash])
  end

  case status_code
  when 200..299
    callbacks[:on_success].call(payload)
  when 300..399
    if redirect_url = target_response['Location']
      callbacks[:on_redirect].call(payload | [redirect_url])
    end
  when 400..499
    callbacks[:on_missing].call(payload)
  when 500..599
    callbacks[:on_error].call(payload)
  end

  callbacks[:on_complete].call(payload)

  payload
end

Private Instance Methods

extract_http_request_headers(env) click to toggle source
# File lib/reverse_proxy/client.rb, line 131
def extract_http_request_headers(env)
  headers = env.reject do |k, v|
    !(/^HTTP_[A-Z_]+$/ === k) || k == "HTTP_VERSION" || v.nil?
  end.map do |k, v|
    [reconstruct_header_name(k), v]
  end.inject(Rack::Utils::HeaderHash.new) do |hash, k_v|
    k, v = k_v
    hash[k] = v
    hash
  end

  headers
end
reconstruct_header_name(name) click to toggle source
# File lib/reverse_proxy/client.rb, line 145
def reconstruct_header_name(name)
  name.sub(/^HTTP_/, "").gsub("_", "-")
end