class HTTPDisk::Client

Middleware and main entry point.

Attributes

cache[R]
options[R]

Public Class Methods

new(app, options = {}) click to toggle source
Calls superclass method
# File lib/httpdisk/client.rb, line 10
def initialize(app, options = {})
  options = Sloptions.parse(options) do
    _1.string :dir, default: File.join(ENV['HOME'], 'httpdisk')
    _1.integer :expires
    _1.boolean :force
    _1.boolean :force_errors
    _1.array :ignore_params, default: []
    _1.on :logger, type: [:boolean, Logger]
    _1.boolean :utf8
  end

  super(app, options)
  @cache = Cache.new(options)
end

Public Instance Methods

call(env) click to toggle source
# File lib/httpdisk/client.rb, line 25
def call(env)
  cache_key = CacheKey.new(env, ignore_params: ignore_params)
  logger&.info("#{env.method.upcase} #{env.url} (#{cache.status(cache_key)})")
  env[:httpdisk_diskpath] = cache.diskpath(cache_key)

  # check cache, fallback to network
  if response = read(cache_key, env)
    response.env[:httpdisk] = true
  else
    response = perform(env)
    response.env[:httpdisk] = false
    write(cache_key, env, response)
  end

  encode_body(response)
  response
end
status(env) click to toggle source

Returns cache status for this request

# File lib/httpdisk/client.rb, line 44
def status(env)
  cache_key = CacheKey.new(env)
  {
    url: env.url.to_s,
    status: cache.status(cache_key).to_s,
    key: cache_key.key,
    digest: cache_key.digest,
    path: cache.diskpath(cache_key),
  }
end

Protected Instance Methods

encode_body(response) click to toggle source

Set string encoding for response body. The cache always returns ASCII-8BIT, but we have no idea what the encoding will be from the network. Not all adapters honor Content-Type (including the default adapter).

# File lib/httpdisk/client.rb, line 112
def encode_body(response)
  body = response.body || ''

  # parse Content-Type
  begin
    content_type = response['Content-Type'] && ContentType.parse(response['Content-Type'])
  rescue Parslet::ParseFailed
    # unparsable
  end

  # look at charset and set body encoding if necessary
  encoding = encoding_for(content_type)
  if body.encoding != encoding
    body = body.dup if body.frozen?
    body.force_encoding(encoding)
  end

  # if :utf8, force body to UTF-8
  if options[:utf8] && content_type && response_text?(content_type)
    body = body.dup if body.frozen?
    begin
      body.encode!('UTF-8', invalid: :replace, undef: :replace, replace: '?')
    rescue Encoding::ConverterNotFoundError
      # rare, can't do anything here
      body = "httpdisk could not convert from #{body.encoding.name} to UTF-8"
    end
  end

  response.env[:body] = body
end
encoding_for(content_type) click to toggle source
# File lib/httpdisk/client.rb, line 143
def encoding_for(content_type)
  if content_type&.charset
    begin
      return Encoding.find(content_type.charset)
    rescue ArgumentError
      # unknown charset
    end
  end
  Encoding::ASCII_8BIT
end
ignore_params() click to toggle source

options

# File lib/httpdisk/client.rb, line 162
def ignore_params
  @ignore_params ||= options[:ignore_params].map { CGI.escape(_1.to_s) }.to_set
end
logger() click to toggle source
# File lib/httpdisk/client.rb, line 166
def logger
  return if !options[:logger]

  @logger ||= case options[:logger]
  when true then Logger.new($stderr)
  when Logger then options[:logger]
  end
end
perform(env) click to toggle source

perform the request, return Faraday::Response

# File lib/httpdisk/client.rb, line 58
def perform(env)
  app.call(env)
rescue Faraday::ConnectionFailed, Faraday::SSLError, Faraday::TimeoutError => e
  # try to avoid caching proxy errors
  raise e if proxy_error?(env, e)

  stuff_999_response(env, e)
end
proxy_error?(env, err) click to toggle source
# File lib/httpdisk/client.rb, line 100
def proxy_error?(env, err)
  proxy = env.request.proxy
  return if !proxy
  return if !err.is_a?(Faraday::ConnectionFailed)

  err.to_s =~ /#{proxy.host}.*#{proxy.port}/
end
read(cache_key, env) click to toggle source

read from cache return Faraday::Response

# File lib/httpdisk/client.rb, line 68
def read(cache_key, env)
  payload = cache.read(cache_key)
  return if !payload

  env.tap do
    _1.reason_phrase = payload.reason_phrase
    _1.response_body = payload.body
    _1.response_headers = payload.headers
    _1.status = payload.status
  end
  Faraday::Response.new(env)
end
response_text?(content_type) click to toggle source
# File lib/httpdisk/client.rb, line 154
def response_text?(content_type)
  content_type.type == 'text' || content_type.mime_type == 'application/json'
end
stuff_999_response(env, err) click to toggle source

stuff a 999 error into env and create a Faraday::Response

# File lib/httpdisk/client.rb, line 90
def stuff_999_response(env, err)
  env.tap do
    _1.reason_phrase = "#{err.class} #{err.message}"
    _1.response_body = ''
    _1.response_headers = Faraday::Utils::Headers.new
    _1.status = HTTPDisk::ERROR_STATUS
  end
  Faraday::Response.new(env)
end
write(cache_key, env, response) click to toggle source

write Faraday::Response to cache

# File lib/httpdisk/client.rb, line 82
def write(cache_key, env, response)
  payload = Payload.from_response(response).tap do
    _1.comment = "#{env.method.upcase} #{env.url}"
  end
  cache.write(cache_key, payload)
end