class AeReverseProxy::Client

Constants

CALLBACK_METHODS

Attributes

callbacks[RW]
uri[RW]

Public Class Methods

new(uri) { |self| ... } click to toggle source
# File lib/ae_reverse_proxy/client.rb, line 28
def initialize(uri)
  self.uri = uri
  self.callbacks = CALLBACK_METHODS.to_h { |method| [method, proc {}] }

  yield(self) if block_given?
end

Public Instance Methods

forward_request(env, options = {}) click to toggle source
# File lib/ae_reverse_proxy/client.rb, line 35
def forward_request(env, options = {})
  # Initialize requests
  source_request = Rack::Request.new(env)
  target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(source_request.fullpath)

  # Setup headers for forwarding.
  target_request_headers = extract_http_request_headers(source_request.env).merge({
    'ORIGIN' => uri.origin,
    'HOST' => uri.authority,
  })
  target_request.initialize_http_header(target_request_headers)

  # Setup basic auth.
  target_request.basic_auth(options[:username], options[:password]) if options[:username] && 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

  # Setup content encoding and type.
  target_request.content_length = source_request.content_length || 0
  target_request.content_type   = source_request.content_type if source_request.content_type

  # 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

  # Setup HTTP SSL options.
  http_options = {}
  http_options[:use_ssl] = (uri.scheme == 'https')

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

  # Initiate callbacks.
  status_code = target_response.code.to_i
  payload = [status_code, target_response]

  callbacks[:on_response].call(payload)

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

    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
    callbacks[:on_redirect].call(payload | [target_response['Location']]) if target_response['Location']
  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/ae_reverse_proxy/client.rb, line 117
def extract_http_request_headers(env)
  env
    .reject { |k, v| !(/^HTTP_[A-Z_]+$/ === k) || k == 'HTTP_VERSION' || v.nil? }
    .map { |k, v| [reconstruct_header_name(k), v] }
    .each_with_object(Rack::Utils::HeaderHash.new) do |k_v, hash|
      k, v = k_v
      hash[k] = v
    end
end
reconstruct_header_name(name) click to toggle source
# File lib/ae_reverse_proxy/client.rb, line 127
def reconstruct_header_name(name)
  name.sub(/^HTTP_/, '').gsub('_', '-')
end