team-umlaut/umlaut

View on GitHub
app/mixin_logic/umlaut_http.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# Helper module for service adaptors making HTTP requests, as most do.
# Eventually this may include some HTTP request wrapper methods that
# set timeout appropriately, and perhaps even do some HTTP level cacheing.
#
# Right now, just a helper method for generating headers for proxy-like requests.

module UmlautHttp
  require 'net/http'
  require 'net/https'
  require 'uri'

  # Generate headers for a proxy-like request, so a service adaptor
  # can make a request to a foreign service that appears to be HTTP-proxied
  # from the original client browser, instead of just originating from
  # Umlaut. This in some cases helps get around service traffic limiting,
  # and in general is more honest and gives the service some good information
  # about the actual end user.
  #
  # Not generally neccesary for accessing actual APIs, but sometimes useful
  # for screen scraping, or for an API intended to be client-side JS only.
  #
  # request is an Umlaut Request, which has in it information about
  # original client request and ip. host is optional, and is the
  # ultimate destination you will be sending the proxy-like request to.
  # if supplied, a not entirely honest X-Forwarded-Host header will be
  # added. 
  def proxy_like_headers(request, host = nil)
    orig_env = request.http_env
    if (request.http_env.nil? || ! request.http_env.kind_of?(Hash))
      Rails.logger.warn("proxy_like_headers: orig_env arg is missing, proxy-like headers will be flawed. request id: #{request.id}. ")
      orig_env = {}
    end

    header = {}

    # Bunch of headers we proxy as-is from the original client request,
    # supplying reasonable defaults. 
    header["User-Agent"] = orig_env['HTTP_USER_AGENT'] || 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0'
    header['Accept'] = orig_env['HTTP_ACCEPT'] || 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    header['Accept-Language'] = orig_env['HTTP_ACCEPT_LANGUAGE'] || 'en-us,en;q=0.5'

    # Do NOT pass on accept-encoding, we don't want gzip or compressed response. 
    #header['Accept-Encoding'] = orig_env['HTTP_ACCEPT_ENCODING'] || ''
    header['Accept-Encoding'] = "identity"

    header["Accept-Charset"] = orig_env['HTTP_ACCEPT_CHARSET'] || 'UTF-8,*'

    # Set referrer to be, well, an Umlaut page, like the one we are
    # currently generating would be best. That is, the resolve link. 
    
    header["Referer"] = "http://#{orig_env['HTTP_HOST']}#{orig_env['REQUEST_URI']}"

    # Proxy X-Forwarded headers. 

    # The original Client's ip, most important and honest. Look for
    # and add on to any existing x-forwarded-for, if neccesary, as per
    # x-forwarded-for convention. 

    header['X-Forwarded-For'] =  (orig_env['HTTP_X_FORWARDED_FOR']) ?
       (orig_env['HTTP_X_FORWARDED_FOR'].to_s + ', ' + request.client_ip_addr.to_s) :
       request.client_ip_addr.to_s
       
    #Theoretically the original host requested by the client in the Host HTTP request header. We're disembling a bit.  
    header['X-Forwarded-Host'] = host if host
    # The proxy server: That is, Umlaut, us. 
    header['X-Forwarded-Server'] = orig_env['SERVER_NAME'] || '' 
    
    return header
    
  end

  # Right now the main thing this will do for you is (sort of, stupidly) 
  # maintain cookies given in redirects. 
  # Later it might do other cooler things. Returns a Net::HTTP::Response
  def http_fetch(uri, options = {})
    options[:max_redirects] ||= 8
    options[:redirects_left] ||= options[:max_redirects]
    options[:raise_on_http_error_code] = true unless options.has_key?(:raise_on_http_error_code)

      uri = URI.parse(uri) unless uri.kind_of?(URI)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true if uri.scheme == "https"
      headers = options[:headers] || {}
      headers["Cookie"] = options[:cookies] if options[:cookies]

      response = http.request_get(uri.request_uri, headers)
            
      if ( response.kind_of?(Net::HTTPRedirection))
         raise ArgumentError, "HTTP redirect too deep (max #{options[:max_redirects]})" if options[:redirects_left] <= 0

         options[:cookies] = response['Set-Cookie']
         
         options[:redirects_left] = options[:redirects_left] - 1
         
         return http_fetch(response['location'], options)
      else
        if options[:raise_on_http_error_code] 
          Rails.logger.warn("UmlautHttp#http_fetch: unexpected non-successful response: #{uri}: #{response}") unless response.is_a?(Net::HTTPSuccess)
          response.value 
        end
        return response
      end
  end
  
  
end