This commit is contained in:
@@ -0,0 +1 @@
|
||||
require "rack/typhoeus/middleware/params_decoder"
|
||||
@@ -0,0 +1,57 @@
|
||||
require 'rack/typhoeus/middleware/params_decoder/helper'
|
||||
|
||||
module Rack
|
||||
module Typhoeus
|
||||
module Middleware
|
||||
|
||||
# This Rack middleware takes care of the proper deserialization of
|
||||
# the nested params encoded by Typhoeus.
|
||||
#
|
||||
# @example Require the railtie when using Rails.
|
||||
# require 'typhoeus/railtie'
|
||||
#
|
||||
# @example Include the middleware for Rack based applications.
|
||||
# use Rack::Typhoeus::Middleware::ParamsDecoder
|
||||
#
|
||||
# @example Use the helper directly. Not recommended as b/c the interface might change.
|
||||
# require 'rack/typhoeus/middleware/params_decoder/helper'
|
||||
# include Rack::Typhoeus::Middleware::ParamsDecoder::Helper
|
||||
# decode!(params)
|
||||
#
|
||||
# @author Dwayne Macgowan
|
||||
# @since 0.5.4
|
||||
class ParamsDecoder
|
||||
include ParamsDecoder::Helper
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
req = Rack::Request.new(env)
|
||||
decode(req.params).each_pair { |k, v| update_params req, k, v }
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Persist params change in environment. Extracted from:
|
||||
# https://github.com/rack/rack/blob/master/lib/rack/request.rb#L243
|
||||
def update_params(req, k, v)
|
||||
found = false
|
||||
if req.GET.has_key?(k)
|
||||
found = true
|
||||
req.GET[k] = v
|
||||
end
|
||||
if req.POST.has_key?(k)
|
||||
found = true
|
||||
req.POST[k] = v
|
||||
end
|
||||
unless found
|
||||
req.GET[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,76 @@
|
||||
module Rack
|
||||
module Typhoeus
|
||||
module Middleware
|
||||
class ParamsDecoder
|
||||
module Helper
|
||||
|
||||
# Recursively decodes Typhoeus encoded arrays in given Hash.
|
||||
#
|
||||
# @example Use directly in a Rails controller.
|
||||
# class ApplicationController
|
||||
# before_filter :decode_typhoeus_arrays
|
||||
# end
|
||||
#
|
||||
# @author Dwayne Macgowan
|
||||
#
|
||||
def decode_typhoeus_arrays
|
||||
decode!(params)
|
||||
end
|
||||
|
||||
# Recursively decodes Typhoeus encoded arrays in given Hash.
|
||||
#
|
||||
# @param hash [Hash]. This Hash will be modified!
|
||||
#
|
||||
# @return [Hash] Hash with properly decoded nested arrays.
|
||||
def decode!(hash)
|
||||
return hash unless hash.is_a?(Hash)
|
||||
hash.each_pair do |key,value|
|
||||
if value.is_a?(Hash)
|
||||
decode!(value)
|
||||
hash[key] = convert(value)
|
||||
end
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
def decode(hash)
|
||||
decode!(hash.dup)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Checks if Hash is an Array encoded as a Hash.
|
||||
# Specifically will check for the Hash to have this
|
||||
# form: {'0' => v0, '1' => v1, .., 'n' => vN }
|
||||
#
|
||||
# @param hash [Hash]
|
||||
#
|
||||
# @return [Boolean] True if its a encoded Array, else false.
|
||||
def encoded?(hash)
|
||||
return false if hash.empty?
|
||||
if hash.keys.size > 1
|
||||
keys = hash.keys.map{|i| i.to_i if i.respond_to?(:to_i)}.sort
|
||||
keys == hash.keys.size.times.to_a
|
||||
else
|
||||
hash.keys.first =~ /0/
|
||||
end
|
||||
end
|
||||
|
||||
# If the Hash is an array encoded by typhoeus an array is returned
|
||||
# else the self is returned
|
||||
#
|
||||
# @param hash [Hash] The Hash to convert into an Array.
|
||||
#
|
||||
# @return [Arraya/Hash]
|
||||
def convert(hash)
|
||||
if encoded?(hash)
|
||||
hash.sort{ |a, b| a[0].to_i <=> b[0].to_i }.map{ |key, value| value }
|
||||
else
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,143 @@
|
||||
require 'digest/sha2'
|
||||
require 'ethon'
|
||||
|
||||
require 'typhoeus/config'
|
||||
require 'typhoeus/easy_factory'
|
||||
require 'typhoeus/errors'
|
||||
require 'typhoeus/expectation'
|
||||
require 'typhoeus/hydra'
|
||||
require 'typhoeus/pool'
|
||||
require 'typhoeus/request'
|
||||
require 'typhoeus/response'
|
||||
require 'typhoeus/version'
|
||||
|
||||
# If we are using any Rack-based application, then we need the Typhoeus rack
|
||||
# middleware to ensure our app is running properly.
|
||||
if defined?(Rack)
|
||||
require "rack/typhoeus"
|
||||
end
|
||||
|
||||
# If the Redis gem is available, load the redis cache adapter
|
||||
if defined?(Redis)
|
||||
require "typhoeus/cache/redis"
|
||||
end
|
||||
|
||||
# If the Dalli gem is available, load the Dalli cache adapter
|
||||
if defined?(Dalli)
|
||||
require "typhoeus/cache/dalli"
|
||||
end
|
||||
|
||||
# If we are using Rails, load the Rails cache adapter
|
||||
if defined?(Rails)
|
||||
require "typhoeus/cache/rails"
|
||||
end
|
||||
|
||||
# If we are using Rails, then we will include the Typhoeus railtie.
|
||||
# if defined?(Rails)
|
||||
# require "typhoeus/railtie"
|
||||
# end
|
||||
|
||||
# Typhoeus is a HTTP client library based on Ethon which
|
||||
# wraps libcurl. Sitting on top of libcurl makes Typhoeus
|
||||
# very reliable and fast.
|
||||
#
|
||||
# There are some gems using Typhoeus like
|
||||
# {https://github.com/myronmarston/vcr VCR},
|
||||
# {https://github.com/bblimke/webmock WebMock} or
|
||||
# {https://github.com/technoweenie/faraday Faraday}. VCR
|
||||
# and WebMock provide their own adapter whereas
|
||||
# Faraday relies on {Faraday::Adapter::Typhoeus}
|
||||
# since Typhoeus version 0.5.
|
||||
#
|
||||
# @example (see Typhoeus::Request)
|
||||
# @example (see Typhoeus::Hydra)
|
||||
#
|
||||
# @see Typhoeus::Request
|
||||
# @see Typhoeus::Hydra
|
||||
# @see Faraday::Adapter::Typhoeus
|
||||
#
|
||||
# @since 0.5.0
|
||||
module Typhoeus
|
||||
extend Request::Actions
|
||||
extend Request::Callbacks::Types
|
||||
|
||||
# The default Typhoeus user agent.
|
||||
USER_AGENT = "Typhoeus - https://github.com/typhoeus/typhoeus"
|
||||
|
||||
# Set the Typhoeus configuration options by passing a block.
|
||||
#
|
||||
# @example (see Typhoeus::Config)
|
||||
#
|
||||
# @yield [ Typhoeus::Config ]
|
||||
#
|
||||
# @return [ Typhoeus::Config ] The configuration.
|
||||
#
|
||||
# @see Typhoeus::Config
|
||||
def self.configure
|
||||
yield Config
|
||||
end
|
||||
|
||||
# Stub out a specific request.
|
||||
#
|
||||
# @example (see Typhoeus::Expectation)
|
||||
#
|
||||
# @param [ String ] base_url The url to stub out.
|
||||
# @param [ Hash ] options The options to stub out.
|
||||
#
|
||||
# @return [ Typhoeus::Expectation ] The expecatation.
|
||||
#
|
||||
# @see Typhoeus::Expectation
|
||||
def self.stub(base_url, options = {}, &block)
|
||||
expectation = Expectation.all.find{ |e| e.base_url == base_url && e.options == options }
|
||||
if expectation.nil?
|
||||
expectation = Expectation.new(base_url, options)
|
||||
Expectation.all << expectation
|
||||
end
|
||||
|
||||
expectation.and_return(&block) unless block.nil?
|
||||
expectation
|
||||
end
|
||||
|
||||
# Add before callbacks.
|
||||
#
|
||||
# @example Add before callback.
|
||||
# Typhoeus.before { |request| p request.base_url }
|
||||
#
|
||||
# @param [ Block ] block The callback.
|
||||
#
|
||||
# @yield [ Typhoeus::Request ]
|
||||
#
|
||||
# @return [ Array<Block> ] All before blocks.
|
||||
def self.before(&block)
|
||||
@before ||= []
|
||||
@before << block if block_given?
|
||||
@before
|
||||
end
|
||||
|
||||
# Execute given block as if block connection is turned off.
|
||||
# The old block connection state is restored afterwards.
|
||||
#
|
||||
# @example Make a real request, no matter if it's blocked.
|
||||
# Typhoeus::Config.block_connection = true
|
||||
# Typhoeus.get("www.example.com").code
|
||||
# #=> raise Typhoeus::Errors::NoStub
|
||||
#
|
||||
# Typhoeus.with_connection do
|
||||
# Typhoeus.get("www.example.com").code
|
||||
# #=> :ok
|
||||
# end
|
||||
#
|
||||
# @yield Yields control to the block after disabling block_connection.
|
||||
# Afterwards, the block_connection is set to its original
|
||||
# value.
|
||||
# @return [ Object ] Returns the return value of the block.
|
||||
#
|
||||
# @see Typhoeus::Config.block_connection
|
||||
def self.with_connection
|
||||
old = Config.block_connection
|
||||
Config.block_connection = false
|
||||
result = yield if block_given?
|
||||
Config.block_connection = old
|
||||
result
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,180 @@
|
||||
require 'faraday'
|
||||
|
||||
module Faraday # :nodoc:
|
||||
class Adapter # :nodoc:
|
||||
|
||||
# Adapter to use Faraday with Typhoeus.
|
||||
#
|
||||
# @example Use Typhoeus.
|
||||
# require 'faraday'
|
||||
# require 'typhoeus'
|
||||
# require 'typhoeus/adapters/faraday'
|
||||
#
|
||||
# conn = Faraday.new(url: "www.example.com") do |faraday|
|
||||
# faraday.adapter :typhoeus
|
||||
#
|
||||
# # You can include Typhoeus options to be used for every request
|
||||
# # faraday.adapter :typhoeus, forbid_reuse: true, maxredirs: 1
|
||||
# end
|
||||
#
|
||||
# response = conn.get("/")
|
||||
class Typhoeus < Faraday::Adapter
|
||||
self.supports_parallel = true
|
||||
|
||||
(class << self; self; end).instance_eval do
|
||||
remove_method :setup_parallel_manager if method_defined? :setup_parallel_manager
|
||||
end
|
||||
|
||||
remove_method :call if method_defined? :call
|
||||
remove_method :perform_request if method_defined? :perform_request
|
||||
remove_method :request if method_defined? :request
|
||||
remove_method :read_body if method_defined? :read_body
|
||||
remove_method :configure_ssl if method_defined? :configure_ssl
|
||||
remove_method :configure_proxy if method_defined? :configure_proxy
|
||||
remove_method :configure_timeout if method_defined? :configure_timeout
|
||||
remove_method :configure_socket if method_defined? :configure_socket
|
||||
remove_method :parallel? if method_defined? :parallel?
|
||||
|
||||
# Initialize the Typhoeus adapter
|
||||
#
|
||||
# @param [ App ] app Farday app
|
||||
# @option [ Hash ] adapter_options Typhoeus options
|
||||
#
|
||||
# @return [ void ]
|
||||
def initialize(app, adapter_options = {})
|
||||
super(app)
|
||||
@adapter_options = adapter_options
|
||||
end
|
||||
|
||||
# Setup Hydra with provided options.
|
||||
#
|
||||
# @example Setup Hydra.
|
||||
# Faraday::Adapter::Typhoeus.setup_parallel_manager
|
||||
# #=> #<Typhoeus::Hydra ... >
|
||||
#
|
||||
# @param (see Typhoeus::Hydra#initialize)
|
||||
# @option (see Typhoeus::Hydra#initialize)
|
||||
#
|
||||
# @return [ Typhoeus::Hydra ] The hydra.
|
||||
def self.setup_parallel_manager(options = {})
|
||||
::Typhoeus::Hydra.new(options)
|
||||
end
|
||||
|
||||
dependency 'typhoeus'
|
||||
|
||||
# Hook into Faraday and perform the request with Typhoeus.
|
||||
#
|
||||
# @param [ Hash ] env The environment.
|
||||
#
|
||||
# @return [ void ]
|
||||
def call(env)
|
||||
super
|
||||
perform_request env
|
||||
@app.call env
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def perform_request(env)
|
||||
if parallel?(env)
|
||||
env[:parallel_manager].queue request(env)
|
||||
else
|
||||
request(env).run
|
||||
end
|
||||
end
|
||||
|
||||
def request(env)
|
||||
read_body env
|
||||
|
||||
req = typhoeus_request(env)
|
||||
|
||||
configure_ssl req, env
|
||||
configure_proxy req, env
|
||||
configure_timeout req, env
|
||||
configure_socket req, env
|
||||
|
||||
req.on_complete do |resp|
|
||||
if resp.timed_out?
|
||||
env[:typhoeus_timed_out] = true
|
||||
unless parallel?(env)
|
||||
raise Faraday::TimeoutError, "request timed out"
|
||||
end
|
||||
elsif (resp.response_code == 0) || ((resp.return_code != :ok) && !resp.mock?)
|
||||
env[:typhoeus_connection_failed] = true
|
||||
env[:typhoeus_return_message] = resp.return_message
|
||||
unless parallel?(env)
|
||||
raise Faraday::ConnectionFailed, resp.return_message
|
||||
end
|
||||
end
|
||||
|
||||
save_response(env, resp.code, resp.body) do |response_headers|
|
||||
response_headers.parse resp.response_headers
|
||||
end
|
||||
# in async mode, :response is initialized at this point
|
||||
env[:response].finish(env) if parallel?(env)
|
||||
end
|
||||
|
||||
req
|
||||
end
|
||||
|
||||
def typhoeus_request(env)
|
||||
opts = {
|
||||
:method => env[:method],
|
||||
:body => env[:body],
|
||||
:headers => env[:request_headers]
|
||||
}.merge(@adapter_options)
|
||||
|
||||
::Typhoeus::Request.new(env[:url].to_s, opts)
|
||||
end
|
||||
|
||||
def read_body(env)
|
||||
env[:body] = env[:body].read if env[:body].respond_to? :read
|
||||
end
|
||||
|
||||
def configure_ssl(req, env)
|
||||
ssl = env[:ssl]
|
||||
|
||||
verify_p = (ssl && ssl.fetch(:verify, true))
|
||||
|
||||
ssl_verifyhost = verify_p ? 2 : 0
|
||||
req.options[:ssl_verifyhost] = ssl_verifyhost
|
||||
req.options[:ssl_verifypeer] = verify_p
|
||||
req.options[:sslversion] = ssl[:version] if ssl[:version]
|
||||
req.options[:sslcert] = ssl[:client_cert] if ssl[:client_cert]
|
||||
req.options[:sslkey] = ssl[:client_key] if ssl[:client_key]
|
||||
req.options[:cainfo] = ssl[:ca_file] if ssl[:ca_file]
|
||||
req.options[:capath] = ssl[:ca_path] if ssl[:ca_path]
|
||||
client_cert_passwd_key = [:client_cert_passwd, :client_certificate_password].detect { |name| ssl.key?(name) }
|
||||
req.options[:keypasswd] = ssl[client_cert_passwd_key] if client_cert_passwd_key
|
||||
end
|
||||
|
||||
def configure_proxy(req, env)
|
||||
proxy = env[:request][:proxy]
|
||||
return unless proxy
|
||||
|
||||
req.options[:proxy] = "#{proxy[:uri].scheme}://#{proxy[:uri].host}:#{proxy[:uri].port}"
|
||||
|
||||
if proxy[:user] && proxy[:password]
|
||||
req.options[:proxyauth] = :any
|
||||
req.options[:proxyuserpwd] = "#{proxy[:user]}:#{proxy[:password]}"
|
||||
end
|
||||
end
|
||||
|
||||
def configure_timeout(req, env)
|
||||
env_req = env[:request]
|
||||
req.options[:timeout_ms] = (env_req[:timeout] * 1000).to_i if env_req[:timeout]
|
||||
req.options[:connecttimeout_ms] = (env_req[:open_timeout] * 1000).to_i if env_req[:open_timeout]
|
||||
end
|
||||
|
||||
def configure_socket(req, env)
|
||||
if bind = env[:request][:bind]
|
||||
req.options[:interface] = bind[:host]
|
||||
end
|
||||
end
|
||||
|
||||
def parallel?(env)
|
||||
!!env[:parallel_manager]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
module Typhoeus
|
||||
module Cache
|
||||
# This module provides a simple way to cache HTTP responses using Dalli.
|
||||
class Dalli
|
||||
# @example Set Dalli as the Typhoeus cache backend
|
||||
# Typhoeus::Config.cache = Typhoeus::Cache::Dalli.new
|
||||
#
|
||||
# @param [ Dalli::Client ] client
|
||||
# A connection to the cache server. Defaults to `Dalli::Client.new`
|
||||
# @param [ Hash ] options
|
||||
# Options
|
||||
# @option options [ Integer ] :default_ttl
|
||||
# The default TTL of cached responses in seconds, for requests which do not set a cache_ttl.
|
||||
def initialize(client = ::Dalli::Client.new, options = {})
|
||||
@client = client
|
||||
@default_ttl = options[:default_ttl]
|
||||
end
|
||||
|
||||
def get(request)
|
||||
@client.get(request.cache_key)
|
||||
end
|
||||
|
||||
def set(request, response)
|
||||
@client.set(request.cache_key, response, request.cache_ttl || @default_ttl)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
module Typhoeus
|
||||
module Cache
|
||||
# This module provides a simple way to cache HTTP responses in using the Rails cache.
|
||||
class Rails
|
||||
# @example Use the Rails cache setup to cache Typhoeus responses.
|
||||
# Typhoeus::Config.cache = Typhoeus::Cache::Rails.new
|
||||
#
|
||||
# @param [ ActiveSupport::Cache::Store ] cache
|
||||
# A Rails cache backend. Defaults to Rails.cache.
|
||||
# @param [ Hash ] options
|
||||
# Options
|
||||
# @option options [ Integer ] :default_ttl
|
||||
# The default TTL of cached responses in seconds, for requests which do not set a cache_ttl.
|
||||
def initialize(cache = ::Rails.cache, options = {})
|
||||
@cache = cache
|
||||
@default_ttl = options[:default_ttl]
|
||||
end
|
||||
|
||||
def get(request)
|
||||
@cache.read(request)
|
||||
end
|
||||
|
||||
def set(request, response)
|
||||
@cache.write(request.cache_key, response, :expires_in => request.cache_ttl || @default_ttl)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
module Typhoeus
|
||||
module Cache
|
||||
# This module provides a simple way to cache HTTP responses in Redis.
|
||||
class Redis
|
||||
# @example Set Redis as the Typhoeus cache backend
|
||||
# Typhoeus::Config.cache = Typhoeus::Cache::Redis.new
|
||||
#
|
||||
# @param [ Redis ] redis
|
||||
# A connection to Redis. Defaults to `Redis.new`, which uses the
|
||||
# `REDIS_URL` environment variable to connect
|
||||
# @param [ Hash ] options
|
||||
# Options
|
||||
# @option options [ Integer ] :default_ttl
|
||||
# The default TTL of cached responses in seconds, for requests which do not set a cache_ttl.
|
||||
def initialize(redis = ::Redis.new, options = {})
|
||||
@redis = redis
|
||||
@default_ttl = options[:default_ttl]
|
||||
end
|
||||
|
||||
def get(request)
|
||||
serialized_response = @redis.get(request.cache_key)
|
||||
return unless serialized_response
|
||||
Marshal.load(serialized_response)
|
||||
end
|
||||
|
||||
def set(request, response)
|
||||
ttl = request.cache_ttl || @default_ttl
|
||||
key = request.cache_key
|
||||
serialized_response = Marshal.dump(response)
|
||||
@redis.set(key, serialized_response)
|
||||
@redis.expire(key, ttl) if ttl
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
module Typhoeus
|
||||
|
||||
# The Typhoeus configuration used to set global
|
||||
# options.
|
||||
# @example Set the configuration options within a block.
|
||||
# Typhoeus.configure do |config|
|
||||
# config.verbose = true
|
||||
# end
|
||||
#
|
||||
# @example Set the configuration directly.
|
||||
# Typhoeus::Config.verbose = true
|
||||
module Config
|
||||
extend self
|
||||
|
||||
# Defines whether the connection is blocked.
|
||||
# Defaults to false. When set to true, only
|
||||
# stubbed requests are allowed. A
|
||||
# {Typhoeus::Errors::NoStub} error is raised,
|
||||
# when trying to do a real request. It's possible
|
||||
# to work around inside
|
||||
# {Typhoeus.with_connection}.
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
#
|
||||
# @see Typhoeus::Request::BlockConnection
|
||||
# @see Typhoeus::Hydra::BlockConnection
|
||||
# @see Typhoeus#with_connection
|
||||
# @see Typhoeus::Errors::NoStub
|
||||
attr_accessor :block_connection
|
||||
|
||||
# Defines whether GET requests are memoized when using the {Typhoeus::Hydra}.
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
#
|
||||
# @see Typhoeus::Hydra
|
||||
# @see Typhoeus::Hydra::Memoizable
|
||||
attr_accessor :memoize
|
||||
|
||||
# Defines whether curls debug output is shown.
|
||||
# Unfortunately it prints to stderr.
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
#
|
||||
# @see http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTVERBOSE
|
||||
attr_accessor :verbose
|
||||
|
||||
# Defines the default HTTP timeout for the entire request in seconds
|
||||
# See README for more details about timeouts
|
||||
#
|
||||
# @return [ Integer, Float ]
|
||||
#
|
||||
# @see https://curl.haxx.se/libcurl/c/curl_easy_setopt#CURLOPTTIMEOUT
|
||||
attr_accessor :timeout
|
||||
|
||||
# Defines the default HTTP timeout for the connection phase in seconds
|
||||
# See README for more details about timeouts
|
||||
#
|
||||
# @return [ Integer, Float ]
|
||||
#
|
||||
# @see https://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT
|
||||
attr_accessor :connecttimeout
|
||||
|
||||
# Defines whether requests are cached.
|
||||
#
|
||||
# @return [ Object ]
|
||||
#
|
||||
# @see Typhoeus::Hydra::Cacheable
|
||||
# @see Typhoeus::Request::Cacheable
|
||||
attr_accessor :cache
|
||||
|
||||
# Defines whether to use a default user agent.
|
||||
#
|
||||
# @return [ String ]
|
||||
#
|
||||
# @see Typhoeus::Request#set_defaults
|
||||
attr_accessor :user_agent
|
||||
|
||||
# Defines wether to use a proxy server for every request.
|
||||
#
|
||||
# @return [ String ]
|
||||
#
|
||||
# @see Typhoeus::Request#set_defaults
|
||||
attr_accessor :proxy
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,206 @@
|
||||
require 'set'
|
||||
|
||||
module Typhoeus
|
||||
|
||||
# This is a Factory for easies to be used in the hydra.
|
||||
# Before an easy is ready to be added to a multi the
|
||||
# on_complete callback to be set.
|
||||
# This is done by this class.
|
||||
#
|
||||
# @api private
|
||||
class EasyFactory
|
||||
|
||||
RENAMED_OPTIONS = {
|
||||
:auth_method => :httpauth,
|
||||
:connect_timeout => :connecttimeout,
|
||||
:encoding => :accept_encoding,
|
||||
:follow_location => :followlocation,
|
||||
:max_redirects => :maxredirs,
|
||||
:proxy_type => :proxytype,
|
||||
:ssl_cacert => :cainfo,
|
||||
:ssl_capath => :capath,
|
||||
:ssl_cert => :sslcert,
|
||||
:ssl_cert_type => :sslcerttype,
|
||||
:ssl_key => :sslkey,
|
||||
:ssl_key_password => :keypasswd,
|
||||
:ssl_key_type => :sslkeytype,
|
||||
:ssl_version => :sslversion,
|
||||
}
|
||||
|
||||
CHANGED_OPTIONS = {
|
||||
:disable_ssl_host_verification => :ssl_verifyhost,
|
||||
:disable_ssl_peer_verification => :ssl_verifypeer,
|
||||
:proxy_auth_method => :proxyauth,
|
||||
}
|
||||
|
||||
REMOVED_OPTIONS = Set.new([:cache_key_basis, :cache_timeout, :user_agent])
|
||||
|
||||
SANITIZE_IGNORE = Set.new([:method, :cache_ttl, :cache])
|
||||
SANITIZE_TIMEOUT = Set.new([:timeout_ms, :connecttimeout_ms])
|
||||
|
||||
# Returns the request provided.
|
||||
#
|
||||
# @return [ Typhoeus::Request ]
|
||||
attr_reader :request
|
||||
|
||||
# Returns the hydra provided.
|
||||
#
|
||||
# @return [ Typhoeus::Hydra ]
|
||||
attr_reader :hydra
|
||||
|
||||
# Create an easy factory.
|
||||
#
|
||||
# @example Create easy factory.
|
||||
# Typhoeus::Hydra::EasyFactory.new(request, hydra)
|
||||
#
|
||||
# @param [ Request ] request The request to build an easy for.
|
||||
# @param [ Hydra ] hydra The hydra to build an easy for.
|
||||
def initialize(request, hydra = nil)
|
||||
@request = request
|
||||
@hydra = hydra
|
||||
end
|
||||
|
||||
# Return the easy in question.
|
||||
#
|
||||
# @example Return easy.
|
||||
# easy_factory.easy
|
||||
#
|
||||
# @return [ Ethon::Easy ] The easy.
|
||||
def easy
|
||||
@easy ||= Typhoeus::Pool.get
|
||||
end
|
||||
|
||||
# Fabricated easy.
|
||||
#
|
||||
# @example Prepared easy.
|
||||
# easy_factory.get
|
||||
#
|
||||
# @return [ Ethon::Easy ] The easy.
|
||||
def get
|
||||
begin
|
||||
read_callback_body = request.options.delete(:set_read_callback)
|
||||
|
||||
easy.http_request(
|
||||
request.base_url.to_s,
|
||||
request.options.fetch(:method, :get),
|
||||
sanitize(request.options)
|
||||
)
|
||||
|
||||
# this needs to happen after http_request because
|
||||
# ethon will set infilesize to zero if form.empty?
|
||||
set_read_callback(read_callback_body) if !read_callback_body.nil?
|
||||
rescue Ethon::Errors::InvalidOption => e
|
||||
help = provide_help(e.message.match(/:\s(\w+)/)[1])
|
||||
raise $!, "#{$!}#{help}", $!.backtrace
|
||||
end
|
||||
set_callback
|
||||
easy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sanitize(options)
|
||||
# set nosignal to true by default
|
||||
# this improves thread safety and timeout behavior
|
||||
sanitized = {:nosignal => true}
|
||||
options.each do |k,v|
|
||||
s = k.to_sym
|
||||
next if SANITIZE_IGNORE.include?(s)
|
||||
if new_option = RENAMED_OPTIONS[k.to_sym]
|
||||
warn("Deprecated option #{k}. Please use #{new_option} instead.")
|
||||
sanitized[new_option] = v
|
||||
# sanitize timeouts
|
||||
elsif SANITIZE_TIMEOUT.include?(s)
|
||||
if !v.integer?
|
||||
warn("Value '#{v}' for option '#{k}' must be integer.")
|
||||
end
|
||||
sanitized[k] = v.ceil
|
||||
else
|
||||
sanitized[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
sanitize_timeout!(sanitized, :timeout)
|
||||
sanitize_timeout!(sanitized, :connecttimeout)
|
||||
|
||||
sanitized
|
||||
end
|
||||
|
||||
def sanitize_timeout!(options, timeout)
|
||||
timeout_ms = :"#{timeout}_ms"
|
||||
if options[timeout] && options[timeout].round != options[timeout]
|
||||
if !options[timeout_ms]
|
||||
options[timeout_ms] = (options[timeout]*1000).ceil
|
||||
end
|
||||
options[timeout] = options[timeout].ceil
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
# Sets on_complete callback on easy in order to be able to
|
||||
# track progress.
|
||||
#
|
||||
# @example Set callback.
|
||||
# easy_factory.set_callback
|
||||
#
|
||||
# @return [ Ethon::Easy ] The easy.
|
||||
def set_callback
|
||||
if request.streaming?
|
||||
response = nil
|
||||
easy.on_headers do |easy|
|
||||
response = Response.new(Ethon::Easy::Mirror.from_easy(easy).options)
|
||||
request.execute_headers_callbacks(response)
|
||||
end
|
||||
request.on_body.each do |callback|
|
||||
easy.on_body do |chunk, easy|
|
||||
callback.call(chunk, response)
|
||||
end
|
||||
end
|
||||
else
|
||||
easy.on_headers do |easy|
|
||||
request.execute_headers_callbacks(Response.new(Ethon::Easy::Mirror.from_easy(easy).options))
|
||||
end
|
||||
end
|
||||
request.on_progress.each do |callback|
|
||||
easy.on_progress do |dltotal, dlnow, ultotal, ulnow, easy|
|
||||
callback.call(dltotal, dlnow, ultotal, ulnow, response)
|
||||
end
|
||||
end
|
||||
easy.on_complete do |easy|
|
||||
request.finish(Response.new(easy.mirror.options))
|
||||
Typhoeus::Pool.release(easy)
|
||||
if hydra && !hydra.queued_requests.empty?
|
||||
hydra.dequeue_many
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sets up an easy upload with CURLOPT_READFUNCTION
|
||||
# along with CURLOPT_INFILESIZE_LARGE and CURLOPT_UPLOAD
|
||||
#
|
||||
# @param [ String/File ] body The body read by the readfunction.
|
||||
#
|
||||
# @return [ Ethon::Easy ] The easy.
|
||||
def set_read_callback(body)
|
||||
easy.infilesize_large =
|
||||
if body.respond_to?(:bytesize)
|
||||
body.bytesize
|
||||
elsif body.respond_to?(:size)
|
||||
body.size
|
||||
end
|
||||
|
||||
easy.upload = true
|
||||
easy.set_read_callback(body)
|
||||
|
||||
easy
|
||||
end
|
||||
|
||||
def provide_help(option)
|
||||
if new_option = CHANGED_OPTIONS[option.to_sym]
|
||||
"\nPlease try #{new_option} instead of #{option}." if new_option
|
||||
elsif REMOVED_OPTIONS.include?(option.to_sym)
|
||||
"\nThe option #{option} was removed."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
require 'typhoeus/errors/typhoeus_error'
|
||||
require 'typhoeus/errors/no_stub'
|
||||
|
||||
module Typhoeus
|
||||
|
||||
# This namespace contains all errors raised by Typhoeus.
|
||||
module Errors
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
module Typhoeus
|
||||
module Errors
|
||||
|
||||
# Raises when block connection is turned on
|
||||
# and making a real request.
|
||||
class NoStub < TyphoeusError
|
||||
def initialize(request)
|
||||
super("The connection is blocked and no stub defined: #{request.url}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
module Typhoeus
|
||||
module Errors
|
||||
|
||||
# Default typhoeus error class for all custom errors.
|
||||
class TyphoeusError < StandardError
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,217 @@
|
||||
module Typhoeus
|
||||
|
||||
# This class represents an expectation. It is part
|
||||
# of the stubbing mechanism. An expectation contains
|
||||
# a url and options, like a request. They are compared
|
||||
# to the request url and options in order to evaluate
|
||||
# whether they match. If that's the case, the attached
|
||||
# responses are returned one by one.
|
||||
#
|
||||
# @example Stub a request and get specified response.
|
||||
# expected = Typhoeus::Response.new
|
||||
# Typhoeus.stub("www.example.com").and_return(expected)
|
||||
#
|
||||
# actual = Typhoeus.get("www.example.com")
|
||||
# expected == actual
|
||||
# #=> true
|
||||
#
|
||||
# @example Stub a request and get a lazily-constructed response containing data from actual widgets that exist in the system when the stubbed request is made.
|
||||
# Typhoeus.stub("www.example.com/widgets") do
|
||||
# actual_widgets = Widget.all
|
||||
# Typhoeus::Response.new(
|
||||
# :body => actual_widgets.inject([]) do |ids, widget|
|
||||
# ids << widget.id
|
||||
# end.join(",")
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# @example Stub a request and get a lazily-constructed response in the format requested.
|
||||
# Typhoeus.stub("www.example.com") do |request|
|
||||
# accept = (request.options[:headers]||{})['Accept'] || "application/json"
|
||||
# format = accept.split(",").first
|
||||
# body_obj = { 'things' => [ { 'id' => 'foo' } ] }
|
||||
#
|
||||
# Typhoeus::Response.new(
|
||||
# :headers => {
|
||||
# 'Content-Type' => format
|
||||
# },
|
||||
# :body => SERIALIZERS[format].serialize(body_obj)
|
||||
# )
|
||||
# end
|
||||
class Expectation
|
||||
|
||||
# @api private
|
||||
attr_reader :base_url
|
||||
|
||||
# @api private
|
||||
attr_reader :options
|
||||
|
||||
# @api private
|
||||
attr_reader :from
|
||||
|
||||
class << self
|
||||
|
||||
# Returns all expectations.
|
||||
#
|
||||
# @example Return expectations.
|
||||
# Typhoeus::Expectation.all
|
||||
#
|
||||
# @return [ Array<Typhoeus::Expectation> ] The expectations.
|
||||
def all
|
||||
@expectations ||= []
|
||||
end
|
||||
|
||||
# Clears expectations. This is handy while
|
||||
# testing, and you want to make sure that
|
||||
# you don't get canned responses.
|
||||
#
|
||||
# @example Clear expectations.
|
||||
# Typhoeus::Expectation.clear
|
||||
def clear
|
||||
all.clear
|
||||
end
|
||||
|
||||
# Returns stubbed response matching the
|
||||
# provided request.
|
||||
#
|
||||
# @example Find response
|
||||
# Typhoeus::Expectation.response_for(request)
|
||||
#
|
||||
# @return [ Typhoeus::Response ] The stubbed response from a
|
||||
# matching expectation, or nil if no matching expectation
|
||||
# is found.
|
||||
#
|
||||
# @api private
|
||||
def response_for(request)
|
||||
expectation = find_by(request)
|
||||
return nil if expectation.nil?
|
||||
|
||||
expectation.response(request)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def find_by(request)
|
||||
all.find do |expectation|
|
||||
expectation.matches?(request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an expectation.
|
||||
#
|
||||
# @example Create expectation.
|
||||
# Typhoeus::Expectation.new(base_url)
|
||||
#
|
||||
# @return [ Expectation ] The created expectation.
|
||||
#
|
||||
# @api private
|
||||
def initialize(base_url, options = {})
|
||||
@base_url = base_url
|
||||
@options = options
|
||||
@response_counter = 0
|
||||
@from = nil
|
||||
end
|
||||
|
||||
# Set from value to mark an expectaion. Useful for
|
||||
# other libraries, e.g. WebMock.
|
||||
#
|
||||
# @example Mark expectation.
|
||||
# expectation.from(:webmock)
|
||||
#
|
||||
# @param [ String ] value Value to set.
|
||||
#
|
||||
# @return [ Expectation ] Returns self.
|
||||
#
|
||||
# @api private
|
||||
def stubbed_from(value)
|
||||
@from = value
|
||||
self
|
||||
end
|
||||
|
||||
# Specify what should be returned,
|
||||
# when this expectation is hit.
|
||||
#
|
||||
# @example Add response.
|
||||
# expectation.and_return(response)
|
||||
#
|
||||
# @return [ void ]
|
||||
def and_return(response=nil, &block)
|
||||
new_response = (response.nil? ? block : response)
|
||||
responses.push(*new_response)
|
||||
end
|
||||
|
||||
# Checks whether this expectation matches
|
||||
# the provided request.
|
||||
#
|
||||
# @example Check if request matches.
|
||||
# expectation.matches? request
|
||||
#
|
||||
# @param [ Request ] request The request to check.
|
||||
#
|
||||
# @return [ Boolean ] True when matches, else false.
|
||||
#
|
||||
# @api private
|
||||
def matches?(request)
|
||||
url_match?(request.base_url) && options_match?(request)
|
||||
end
|
||||
|
||||
# Return canned responses.
|
||||
#
|
||||
# @example Return responses.
|
||||
# expectation.responses
|
||||
#
|
||||
# @return [ Array<Typhoeus::Response> ] The responses.
|
||||
#
|
||||
# @api private
|
||||
def responses
|
||||
@responses ||= []
|
||||
end
|
||||
|
||||
# Return the response. When there are
|
||||
# multiple responses, they are returned one
|
||||
# by one.
|
||||
#
|
||||
# @example Return response.
|
||||
# expectation.response
|
||||
#
|
||||
# @return [ Response ] The response.
|
||||
#
|
||||
# @api private
|
||||
def response(request)
|
||||
response = responses.fetch(@response_counter, responses.last)
|
||||
if response.respond_to?(:call)
|
||||
response = response.call(request)
|
||||
end
|
||||
@response_counter += 1
|
||||
response.mock = @from || true
|
||||
response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Check whether the options matches the request options.
|
||||
# I checks options and original options.
|
||||
def options_match?(request)
|
||||
(options ? options.all?{ |k,v| request.original_options[k] == v || request.options[k] == v } : true)
|
||||
end
|
||||
|
||||
# Check whether the base_url matches the request url.
|
||||
# The base_url can be a string, regex or nil. String and
|
||||
# regexp are checked, nil is always true, else false.
|
||||
#
|
||||
# Nil serves as a placeholder in case you want to match
|
||||
# all urls.
|
||||
def url_match?(request_url)
|
||||
case base_url
|
||||
when String
|
||||
base_url == request_url
|
||||
when Regexp
|
||||
base_url === request_url
|
||||
when nil
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,95 @@
|
||||
require 'typhoeus/hydra/addable'
|
||||
require 'typhoeus/hydra/before'
|
||||
require 'typhoeus/hydra/cacheable'
|
||||
require 'typhoeus/hydra/block_connection'
|
||||
require 'typhoeus/hydra/memoizable'
|
||||
require 'typhoeus/hydra/queueable'
|
||||
require 'typhoeus/hydra/runnable'
|
||||
require 'typhoeus/hydra/stubbable'
|
||||
|
||||
module Typhoeus
|
||||
|
||||
# Hydra manages making parallel HTTP requests. This
|
||||
# is achieved by using libcurls multi interface:
|
||||
# http://curl.haxx.se/libcurl/c/libcurl-multi.html
|
||||
# The benefits are that you don't have to worry running
|
||||
# the requests by yourself.
|
||||
#
|
||||
# Hydra will also handle how many requests you can
|
||||
# make in parallel. Things will get flakey if you
|
||||
# try to make too many requests at the same time.
|
||||
# The built in limit is 200. When more requests than
|
||||
# that are queued up, hydra will save them for later
|
||||
# and start the requests as others are finished. You
|
||||
# can raise or lower the concurrency limit through
|
||||
# the Hydra constructor.
|
||||
#
|
||||
# Regarding the asynchronous behavior of the hydra,
|
||||
# it is important to know that this is completely hidden
|
||||
# from the developer and you are free to apply
|
||||
# whatever technique you want to your code. That should not
|
||||
# conflict with libcurls internal concurrency mechanism.
|
||||
#
|
||||
# @example Use the hydra to do multiple requests.
|
||||
# hydra = Typhoeus::Hydra.new
|
||||
# requests = (0..9).map{ Typhoeus::Request.new("www.example.com") }
|
||||
# requests.each{ |request| hydra.queue(request) }
|
||||
# hydra.run
|
||||
#
|
||||
# @note Callbacks are going to delay the request
|
||||
# execution.
|
||||
class Hydra
|
||||
include Hydra::Addable
|
||||
include Hydra::Runnable
|
||||
include Hydra::Memoizable
|
||||
include Hydra::Cacheable
|
||||
include Hydra::BlockConnection
|
||||
include Hydra::Stubbable
|
||||
include Hydra::Before
|
||||
include Hydra::Queueable
|
||||
|
||||
# @example Set max_concurrency.
|
||||
# Typhoeus::Hydra.new(max_concurrency: 20)
|
||||
attr_accessor :max_concurrency
|
||||
|
||||
# @api private
|
||||
attr_reader :multi
|
||||
|
||||
class << self
|
||||
|
||||
# Returns a memoized hydra instance.
|
||||
#
|
||||
# @example Get a hydra.
|
||||
# Typhoeus::Hydra.hydra
|
||||
#
|
||||
# @return [Typhoeus::Hydra] A new hydra.
|
||||
def hydra
|
||||
Thread.current[:typhoeus_hydra] ||= new
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new hydra. All
|
||||
# {http://rubydoc.info/github/typhoeus/ethon/Ethon/Multi#initialize-instance_method Ethon::Multi#initialize}
|
||||
# options are also available.
|
||||
#
|
||||
# @example Create a hydra.
|
||||
# Typhoeus::Hydra.new
|
||||
#
|
||||
# @example Create a hydra with max_concurrency.
|
||||
# Typhoeus::Hydra.new(max_concurrency: 20)
|
||||
#
|
||||
# @param [ Hash ] options The options hash.
|
||||
#
|
||||
# @option options :max_concurrency [ Integer ] Number
|
||||
# of max concurrent connections to create. Default is
|
||||
# 200.
|
||||
#
|
||||
# @see http://rubydoc.info/github/typhoeus/ethon/Ethon/Multi#initialize-instance_method
|
||||
# Ethon::Multi#initialize
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
@max_concurrency = Integer(@options.fetch(:max_concurrency, 200))
|
||||
@multi = Ethon::Multi.new(options.reject{|k,_| k==:max_concurrency})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module handles the request adding on
|
||||
# hydra.
|
||||
#
|
||||
# @api private
|
||||
module Addable
|
||||
|
||||
# Adds request to multi.
|
||||
#
|
||||
# @example Add request.
|
||||
# hydra.add(request)
|
||||
#
|
||||
# @param [ Typhoeus::Request ] request to add.
|
||||
#
|
||||
# @return [ void ]
|
||||
def add(request)
|
||||
multi.add(EasyFactory.new(request, self).get)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module provides a way to hook into before
|
||||
# a request gets queued in hydra. This is very powerful
|
||||
# and you should be careful because when you accidently
|
||||
# return a falsy value the request won't be executed.
|
||||
#
|
||||
# @api private
|
||||
module Before
|
||||
|
||||
# Overrride add in order to execute callbacks in
|
||||
# Typhoeus.before. Will break and return when a
|
||||
# callback returns nil, false or a response. Calls super
|
||||
# otherwise.
|
||||
#
|
||||
# @example Add the request.
|
||||
# hydra.add(request)
|
||||
def add(request)
|
||||
Typhoeus.before.each do |callback|
|
||||
value = callback.call(request)
|
||||
if value.nil? || value == false || value.is_a?(Response)
|
||||
dequeue
|
||||
return value
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module handles the blocked connection request mode on
|
||||
# the hydra side, where only stubbed requests
|
||||
# are allowed.
|
||||
# Connection blocking needs to be turned on:
|
||||
# Typhoeus.configure do |config|
|
||||
# config.block_connection = true
|
||||
# end
|
||||
#
|
||||
# When trying to do real requests a NoStub error
|
||||
# is raised.
|
||||
#
|
||||
# @api private
|
||||
module BlockConnection
|
||||
|
||||
# Overrides add in order to check before if block connection
|
||||
# is turned on. If thats the case a NoStub error is
|
||||
# raised.
|
||||
#
|
||||
# @example Add the request.
|
||||
# hydra.add(request)
|
||||
#
|
||||
# @param [ Request ] request The request to enqueue.
|
||||
def add(request)
|
||||
if request.blocked?
|
||||
raise Typhoeus::Errors::NoStub.new(request)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
module Cacheable
|
||||
def add(request)
|
||||
if request.cacheable? && response = request.cached_response
|
||||
response.cached = true
|
||||
request.finish(response)
|
||||
dequeue
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module handles the GET request memoization
|
||||
# on the hydra side. Memoization needs to be turned
|
||||
# on:
|
||||
# Typhoeus.configure do |config|
|
||||
# config.memoize = true
|
||||
# end
|
||||
#
|
||||
# @api private
|
||||
module Memoizable
|
||||
|
||||
# Return the memory.
|
||||
#
|
||||
# @example Return the memory.
|
||||
# hydra.memory
|
||||
#
|
||||
# @return [ Hash ] The memory.
|
||||
def memory
|
||||
@memory ||= {}
|
||||
end
|
||||
|
||||
# Overrides add in order to check before if request
|
||||
# is memoizable and already in memory. If thats the case,
|
||||
# super is not called, instead the response is set and
|
||||
# the on_complete callback called.
|
||||
#
|
||||
# @example Add the request.
|
||||
# hydra.add(request)
|
||||
#
|
||||
# @param [ Request ] request The request to add.
|
||||
#
|
||||
# @return [ Request ] The added request.
|
||||
def add(request)
|
||||
if request.memoizable? && memory.has_key?(request)
|
||||
response = memory[request]
|
||||
request.finish(response, true)
|
||||
dequeue
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Overrides run to make sure the memory is cleared after
|
||||
# each run.
|
||||
#
|
||||
# @example Run hydra.
|
||||
# hydra.run
|
||||
def run
|
||||
super
|
||||
memory.clear
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module handles the request queueing on
|
||||
# hydra.
|
||||
#
|
||||
# @api private
|
||||
module Queueable
|
||||
|
||||
# Return the queued requests.
|
||||
#
|
||||
# @example Return queued requests.
|
||||
# hydra.queued_requests
|
||||
#
|
||||
# @return [ Array<Typhoeus::Request> ] The queued requests.
|
||||
def queued_requests
|
||||
@queued_requests ||= []
|
||||
end
|
||||
|
||||
# Abort the current hydra run as good as
|
||||
# possible. This means that it only
|
||||
# clears the queued requests and can't do
|
||||
# anything about already running requests.
|
||||
#
|
||||
# @example Abort hydra.
|
||||
# hydra.abort
|
||||
def abort
|
||||
queued_requests.clear
|
||||
end
|
||||
|
||||
# Enqueues a request in order to be performed
|
||||
# by the hydra. This can even be done while
|
||||
# the hydra is running. Also sets hydra on
|
||||
# request.
|
||||
#
|
||||
# @example Queue request.
|
||||
# hydra.queue(request)
|
||||
def queue(request)
|
||||
request.hydra = self
|
||||
queued_requests << request
|
||||
end
|
||||
|
||||
# Pushes a request to the front of the queue,
|
||||
# to be performed by the hydra. Also sets hydra
|
||||
# on request
|
||||
#
|
||||
# @example Queue reques.
|
||||
# hydra.queue_front(request)
|
||||
def queue_front(request)
|
||||
request.hydra = self
|
||||
queued_requests.unshift request
|
||||
end
|
||||
|
||||
# Removes a request from queued_requests and
|
||||
# adds it to the hydra in order to be
|
||||
# performed next.
|
||||
#
|
||||
# @example Dequeue request.
|
||||
# hydra.dequeue
|
||||
#
|
||||
# @since 0.6.4
|
||||
def dequeue
|
||||
add(queued_requests.shift) unless queued_requests.empty?
|
||||
end
|
||||
|
||||
# Removes requests from queued_requests and
|
||||
# adds them to the hydra until max_concurrency
|
||||
# is reached.
|
||||
#
|
||||
# @example Dequeue requests.
|
||||
# hydra.dequeue_many
|
||||
#
|
||||
# @since 0.6.8
|
||||
def dequeue_many
|
||||
number = multi.easy_handles.count
|
||||
until number == max_concurrency || queued_requests.empty?
|
||||
add(queued_requests.shift)
|
||||
number += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module contains logic to run a hydra.
|
||||
module Runnable
|
||||
|
||||
# Start the hydra run.
|
||||
#
|
||||
# @example Start hydra run.
|
||||
# hydra.run
|
||||
#
|
||||
# @return [ Symbol ] Return value from multi.perform.
|
||||
def run
|
||||
dequeue_many
|
||||
multi.perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
module Typhoeus
|
||||
class Hydra
|
||||
|
||||
# This module handles stubbing on the hydra side.
|
||||
# It plays well with the block_connection configuration,
|
||||
# which raises when you make a request which is not stubbed.
|
||||
#
|
||||
# @api private
|
||||
module Stubbable
|
||||
|
||||
# Override add in order to check for matching expecations.
|
||||
# When an expecation is found, super is not called. Instead a
|
||||
# canned response is assigned to the request.
|
||||
#
|
||||
# @example Add the request.
|
||||
# hydra.add(request)
|
||||
def add(request)
|
||||
if response = Expectation.response_for(request)
|
||||
request.execute_headers_callbacks(response)
|
||||
request.on_body.each{ |callback| callback.call(response.body, response) }
|
||||
request.finish(response)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
require 'thread'
|
||||
|
||||
module Typhoeus
|
||||
|
||||
# The easy pool stores already initialized
|
||||
# easy handles for future use. This is useful
|
||||
# because creating them is expensive.
|
||||
#
|
||||
# @api private
|
||||
module Pool
|
||||
@mutex = Mutex.new
|
||||
@pid = Process.pid
|
||||
|
||||
# Releases easy into the pool. The easy handle is
|
||||
# reset before it gets back in.
|
||||
#
|
||||
# @example Release easy.
|
||||
# Typhoeus::Pool.release(easy)
|
||||
def self.release(easy)
|
||||
easy.cookielist = "flush" # dump all known cookies to 'cookiejar'
|
||||
easy.cookielist = "all" # remove all cookies from memory for this handle
|
||||
easy.reset
|
||||
@mutex.synchronize { easies << easy }
|
||||
end
|
||||
|
||||
# Return an easy from the pool.
|
||||
#
|
||||
# @example Return easy.
|
||||
# Typhoeus::Pool.get
|
||||
#
|
||||
# @return [ Ethon::Easy ] The easy.
|
||||
def self.get
|
||||
@mutex.synchronize do
|
||||
if @pid == Process.pid
|
||||
easies.pop
|
||||
else
|
||||
# Process has forked. Clear all easies to avoid sockets being
|
||||
# shared between processes.
|
||||
@pid = Process.pid
|
||||
easies.clear
|
||||
nil
|
||||
end
|
||||
end || Ethon::Easy.new
|
||||
end
|
||||
|
||||
# Clear the pool
|
||||
def self.clear
|
||||
@mutex.synchronize { easies.clear }
|
||||
end
|
||||
|
||||
# Use yielded easy, will be released automatically afterwards.
|
||||
#
|
||||
# @example Use easy.
|
||||
# Typhoeus::Pool.with_easy do |easy|
|
||||
# # use easy
|
||||
# end
|
||||
def self.with_easy(&block)
|
||||
easy = get
|
||||
yield easy
|
||||
ensure
|
||||
release(easy) if easy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.easies
|
||||
@easies ||= []
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
require "typhoeus"
|
||||
|
||||
module Rails
|
||||
module Typhoeus
|
||||
class Railtie < Rails::Railtie
|
||||
# Need to include the Typhoeus middleware.
|
||||
initializer "include the identity map" do |app|
|
||||
app.config.middleware.use "Rack::Typhoeus::Middleware::ParamsDecoder"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,223 @@
|
||||
require 'zlib'
|
||||
require 'digest/sha1'
|
||||
require 'typhoeus/request/actions'
|
||||
require 'typhoeus/request/before'
|
||||
require 'typhoeus/request/block_connection'
|
||||
require 'typhoeus/request/cacheable'
|
||||
require 'typhoeus/request/callbacks'
|
||||
require 'typhoeus/request/marshal'
|
||||
require 'typhoeus/request/memoizable'
|
||||
require 'typhoeus/request/operations'
|
||||
require 'typhoeus/request/responseable'
|
||||
require 'typhoeus/request/streamable'
|
||||
require 'typhoeus/request/stubbable'
|
||||
|
||||
module Typhoeus
|
||||
|
||||
# This class represents a request.
|
||||
#
|
||||
# @example (see #initialize)
|
||||
#
|
||||
# @example Make a request with the shortcut.
|
||||
# response = Typhoeus.get("www.example.com")
|
||||
#
|
||||
# @see (see #initialize)
|
||||
class Request
|
||||
extend Request::Actions
|
||||
include Request::Callbacks::Types
|
||||
include Request::Callbacks
|
||||
include Request::Streamable
|
||||
include Request::Marshal
|
||||
include Request::Operations
|
||||
include Request::Responseable
|
||||
include Request::Memoizable
|
||||
include Request::Cacheable
|
||||
include Request::BlockConnection
|
||||
include Request::Stubbable
|
||||
include Request::Before
|
||||
|
||||
# Returns the provided base url.
|
||||
#
|
||||
# @return [ String ]
|
||||
attr_accessor :base_url
|
||||
|
||||
# Returns options, which includes default parameters.
|
||||
#
|
||||
# @return [ Hash ]
|
||||
attr_accessor :options
|
||||
|
||||
# Returns the hydra in which the request ran, if any.
|
||||
#
|
||||
# @return [ Typhoeus::Hydra ]
|
||||
#
|
||||
# @api private
|
||||
attr_accessor :hydra
|
||||
|
||||
# Returns the original options provided.
|
||||
#
|
||||
# @return [ Hash ]
|
||||
#
|
||||
# @api private
|
||||
attr_accessor :original_options
|
||||
|
||||
# @return [ Boolean ]
|
||||
#
|
||||
# @api private
|
||||
attr_accessor :block_connection
|
||||
|
||||
# Creates a new request.
|
||||
#
|
||||
# @example Simplest request.
|
||||
# response = Typhoeus::Request.new("www.example.com").run
|
||||
#
|
||||
# @example Request with url parameters.
|
||||
# response = Typhoeus::Request.new(
|
||||
# "www.example.com",
|
||||
# params: {a: 1}
|
||||
# ).run
|
||||
#
|
||||
# @example Request with a body.
|
||||
# response = Typhoeus::Request.new(
|
||||
# "www.example.com",
|
||||
# body: {b: 2}
|
||||
# ).run
|
||||
#
|
||||
# @example Request with parameters and body.
|
||||
# response = Typhoeus::Request.new(
|
||||
# "www.example.com",
|
||||
# params: {a: 1},
|
||||
# body: {b: 2}
|
||||
# ).run
|
||||
#
|
||||
# @example Create a request and allow follow redirections.
|
||||
# response = Typhoeus::Request.new(
|
||||
# "www.example.com",
|
||||
# followlocation: true
|
||||
# ).run
|
||||
#
|
||||
# @param [ String ] base_url The url to request.
|
||||
# @param [ options ] options The options.
|
||||
#
|
||||
# @option options [ Hash ] :params Translated
|
||||
# into url parameters.
|
||||
# @option options [ Hash ] :body Translated
|
||||
# into HTTP POST request body.
|
||||
#
|
||||
# @return [ Typhoeus::Request ] The request.
|
||||
#
|
||||
# @note See {http://rubydoc.info/github/typhoeus/ethon/Ethon/Easy/Options Ethon::Easy::Options} for more options.
|
||||
#
|
||||
# @see Typhoeus::Hydra
|
||||
# @see Typhoeus::Response
|
||||
# @see Typhoeus::Request::Actions
|
||||
def initialize(base_url, options = {})
|
||||
@base_url = base_url
|
||||
@original_options = options
|
||||
@options = options.dup
|
||||
|
||||
set_defaults
|
||||
end
|
||||
|
||||
# Return the url.
|
||||
# In contrast to base_url which returns the value you specified, url returns
|
||||
# the full url including the parameters.
|
||||
#
|
||||
# @example Get the url.
|
||||
# request.url
|
||||
#
|
||||
# @since 0.5.5
|
||||
def url
|
||||
easy = EasyFactory.new(self).get
|
||||
url = easy.url
|
||||
Typhoeus::Pool.release(easy)
|
||||
url
|
||||
end
|
||||
|
||||
# Returns whether other is equal to self.
|
||||
#
|
||||
# @example Are request equal?
|
||||
# request.eql?(other_request)
|
||||
#
|
||||
# @param [ Object ] other The object to check.
|
||||
#
|
||||
# @return [ Boolean ] Returns true if equal, else false.
|
||||
#
|
||||
# @api private
|
||||
def eql?(other)
|
||||
self.class == other.class &&
|
||||
self.base_url == other.base_url &&
|
||||
fuzzy_hash_eql?(self.options, other.options)
|
||||
end
|
||||
|
||||
# Overrides Object#hash.
|
||||
#
|
||||
# @return [ Integer ] The integer representing the request.
|
||||
#
|
||||
# @api private
|
||||
def hash
|
||||
Zlib.crc32 cache_key
|
||||
end
|
||||
|
||||
# Returns a cache key for use with caching methods that required a string
|
||||
# for a key. Will get used by ActiveSupport::Cache stores automatically.
|
||||
#
|
||||
# @return [ String ] The cache key.
|
||||
def cache_key
|
||||
Digest::SHA1.hexdigest "#{self.class.name}#{base_url}#{hashable_string_for(options)}"
|
||||
end
|
||||
|
||||
# Mimics libcurls POST body generation. This is not accurate, but good
|
||||
# enough for VCR.
|
||||
#
|
||||
# @return [ String ] The encoded body.
|
||||
# otherwise.
|
||||
#
|
||||
# @api private
|
||||
def encoded_body
|
||||
Ethon::Easy::Form.new(nil, options[:body]).to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Checks if two hashes are equal or not, discarding
|
||||
# first-level hash order.
|
||||
#
|
||||
# @param [ Hash ] left
|
||||
# @param [ Hash ] right hash to check for equality
|
||||
#
|
||||
# @return [ Boolean ] Returns true if hashes have
|
||||
# same values for same keys and same length,
|
||||
# even if the keys are given in a different order.
|
||||
def fuzzy_hash_eql?(left, right)
|
||||
return true if (left == right)
|
||||
|
||||
(left.count == right.count) && left.inject(true) do |res, kvp|
|
||||
res && (kvp[1] == right[kvp[0]])
|
||||
end
|
||||
end
|
||||
|
||||
def hashable_string_for(obj)
|
||||
case obj
|
||||
when Hash
|
||||
hashable_string_for(obj.sort_by {|sub_obj| sub_obj.first.to_s})
|
||||
when Array
|
||||
obj.map {|sub_obj| hashable_string_for(sub_obj)}.to_s
|
||||
else
|
||||
obj.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Sets default header and verbose when turned on.
|
||||
def set_defaults
|
||||
default_user_agent = Config.user_agent || Typhoeus::USER_AGENT
|
||||
|
||||
options[:headers] = {'User-Agent' => default_user_agent}.merge(options[:headers] || {})
|
||||
options[:headers]['Expect'] ||= ''
|
||||
options[:verbose] = Typhoeus::Config.verbose if options[:verbose].nil? && !Typhoeus::Config.verbose.nil?
|
||||
options[:timeout] = Typhoeus::Config.timeout if options[:timeout].nil? && !Typhoeus::Config.timeout.nil?
|
||||
options[:connecttimeout] = Typhoeus::Config.connecttimeout if options[:connecttimeout].nil? && !Typhoeus::Config.connecttimeout.nil?
|
||||
options[:maxredirs] ||= 50
|
||||
options[:proxy] = Typhoeus::Config.proxy unless options.has_key?(:proxy) || Typhoeus::Config.proxy.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,125 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# Module containing logic about shortcuts to
|
||||
# http methods. Like
|
||||
# Typhoeus.get("www.example.com")
|
||||
module Actions
|
||||
|
||||
# Make a get request.
|
||||
#
|
||||
# @example Make get request.
|
||||
# Typhoeus.get("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def get(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :get)).run
|
||||
end
|
||||
|
||||
# Make a post request.
|
||||
#
|
||||
# @example Make post request.
|
||||
# Typhoeus.post("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def post(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :post)).run
|
||||
end
|
||||
|
||||
# Make a put request.
|
||||
#
|
||||
# @example Make put request.
|
||||
# Typhoeus.put("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option options :params [ Hash ] Params hash which
|
||||
# is attached to the base_url.
|
||||
# @option options :body [ Hash ] Body hash which
|
||||
# becomes a PUT request body.
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def put(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :put)).run
|
||||
end
|
||||
|
||||
# Make a delete request.
|
||||
#
|
||||
# @example Make delete request.
|
||||
# Typhoeus.delete("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def delete(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :delete)).run
|
||||
end
|
||||
|
||||
# Make a head request.
|
||||
#
|
||||
# @example Make head request.
|
||||
# Typhoeus.head("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def head(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :head)).run
|
||||
end
|
||||
|
||||
# Make a patch request.
|
||||
#
|
||||
# @example Make patch request.
|
||||
# Typhoeus.patch("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def patch(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :patch)).run
|
||||
end
|
||||
|
||||
# Make a options request.
|
||||
#
|
||||
# @example Make options request.
|
||||
# Typhoeus.options("www.example.com")
|
||||
#
|
||||
# @param (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @option (see Typhoeus::Request#initialize)
|
||||
#
|
||||
# @return (see Typhoeus::Response#initialize)
|
||||
#
|
||||
# @note (see Typhoeus::Request#initialize)
|
||||
def options(base_url, options = {})
|
||||
Request.new(base_url, options.merge(:method => :options)).run
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module provides a way to hook into before
|
||||
# a request runs. This is very powerful
|
||||
# and you should be careful because when you accidently
|
||||
# return a falsy value the request won't be executed.
|
||||
#
|
||||
# @api private
|
||||
module Before
|
||||
|
||||
# Overrride run in order to execute callbacks in
|
||||
# Typhoeus.before. Will break and return when a
|
||||
# callback returns nil or false. Calls super
|
||||
# otherwise.
|
||||
#
|
||||
# @example Run the request.
|
||||
# request.run
|
||||
def run
|
||||
Typhoeus.before.each do |callback|
|
||||
value = callback.call(self)
|
||||
if value.nil? || value == false || value.is_a?(Response)
|
||||
return response
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module handles the blocked connection request mode on
|
||||
# the request side, where only stubbed requests
|
||||
# are allowed.
|
||||
# Connection blocking needs to be turned on:
|
||||
# Typhoeus.configure do |config|
|
||||
# config.block_connection = true
|
||||
# end
|
||||
#
|
||||
# When trying to do real requests a NoStub error
|
||||
# is raised.
|
||||
#
|
||||
# @api private
|
||||
module BlockConnection
|
||||
|
||||
# Overrides run in order to check before if block connection
|
||||
# is turned on. If thats the case a NoStub error is
|
||||
# raised.
|
||||
#
|
||||
# @example Run request.
|
||||
# request.run
|
||||
#
|
||||
# @raise [Typhoeus::Errors::NoStub] If connection is blocked
|
||||
# and no stub defined.
|
||||
def run
|
||||
if blocked?
|
||||
raise Typhoeus::Errors::NoStub.new(self)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Returns wether a request is blocked or not. Takes
|
||||
# request.block_connection and Typhoeus::Config.block_connection
|
||||
# into consideration.
|
||||
#
|
||||
# @example Blocked?
|
||||
# request.blocked?
|
||||
#
|
||||
# @return [ Boolean ] True if blocked, false else.
|
||||
def blocked?
|
||||
if block_connection.nil?
|
||||
Typhoeus::Config.block_connection
|
||||
else
|
||||
block_connection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
module Cacheable
|
||||
def response=(response)
|
||||
cache.set(self, response) if cacheable? && !response.cached?
|
||||
super
|
||||
end
|
||||
|
||||
def cacheable?
|
||||
cache
|
||||
end
|
||||
|
||||
def run
|
||||
if response = cached_response
|
||||
response.cached = true
|
||||
finish(response)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def cached_response
|
||||
cacheable? && cache.get(self)
|
||||
end
|
||||
|
||||
def cache_ttl
|
||||
options[:cache_ttl]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache
|
||||
return nil if options[:cache] === false
|
||||
options[:cache] || Typhoeus::Config.cache
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,151 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module contains the logic for the response callbacks.
|
||||
#
|
||||
# You can set multiple callbacks, which are then executed
|
||||
# in the same order.
|
||||
#
|
||||
# request.on_complete { |response| p 1 }
|
||||
# request.on_complete { |response| p 2 }
|
||||
# request.execute_callbacks
|
||||
# #=> 1
|
||||
# #=> 2
|
||||
#
|
||||
# You can clear the callbacks:
|
||||
#
|
||||
# request.on_complete { |response| p 1 }
|
||||
# request.on_complete { |response| p 2 }
|
||||
# request.on_complete.clear
|
||||
# request.execute_callbacks
|
||||
# #=> nil
|
||||
#
|
||||
# @note If you're using the Hydra to execute multiple
|
||||
# requests, then callbacks are delaying the
|
||||
# request execution.
|
||||
module Callbacks
|
||||
|
||||
module Types # :nodoc:
|
||||
# Set on_complete callback.
|
||||
#
|
||||
# @example Set on_complete.
|
||||
# request.on_complete { |response| p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
#
|
||||
# @yield [ Typhoeus::Response ]
|
||||
#
|
||||
# @return [ Array<Block> ] All on_complete blocks.
|
||||
def on_complete(&block)
|
||||
@on_complete ||= []
|
||||
@on_complete << block if block_given?
|
||||
@on_complete
|
||||
end
|
||||
|
||||
# Set on_success callback.
|
||||
#
|
||||
# @example Set on_success.
|
||||
# request.on_success { |response| p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
#
|
||||
# @yield [ Typhoeus::Response ]
|
||||
#
|
||||
# @return [ Array<Block> ] All on_success blocks.
|
||||
def on_success(&block)
|
||||
@on_success ||= []
|
||||
@on_success << block if block_given?
|
||||
@on_success
|
||||
end
|
||||
|
||||
# Set on_failure callback.
|
||||
#
|
||||
# @example Set on_failure.
|
||||
# request.on_failure { |response| p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
#
|
||||
# @yield [ Typhoeus::Response ]
|
||||
#
|
||||
# @return [ Array<Block> ] All on_failure blocks.
|
||||
def on_failure(&block)
|
||||
@on_failure ||= []
|
||||
@on_failure << block if block_given?
|
||||
@on_failure
|
||||
end
|
||||
|
||||
# Set on_headers callback.
|
||||
#
|
||||
# @example Set on_headers.
|
||||
# request.on_headers { |response| p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
#
|
||||
# @yield [ Typhoeus::Response ]
|
||||
#
|
||||
# @return [ Array<Block> ] All on_headers blocks.
|
||||
def on_headers(&block)
|
||||
@on_headers ||= []
|
||||
@on_headers << block if block_given?
|
||||
@on_headers
|
||||
end
|
||||
|
||||
# Set on_progress callback.
|
||||
#
|
||||
# @example Set on_progress.
|
||||
# request.on_progress do |dltotal, dlnow, ultotal, ulnow|
|
||||
# puts "dltotal (#{dltotal}), dlnow (#{dlnow}), ultotal (#{ultotal}), ulnow (#{ulnow})"
|
||||
# end
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
#
|
||||
# @yield [ Typhoeus::Response ]
|
||||
#
|
||||
# @return [ Array<Block> ] All on_progress blocks.
|
||||
def on_progress(&block)
|
||||
@on_progress ||= []
|
||||
@on_progress << block if block_given?
|
||||
@on_progress
|
||||
end
|
||||
end
|
||||
|
||||
# Execute the headers callbacks and yields response.
|
||||
#
|
||||
# @example Execute callbacks.
|
||||
# request.execute_headers_callbacks
|
||||
#
|
||||
# @return [ Array<Object> ] The results of the on_headers callbacks.
|
||||
#
|
||||
# @api private
|
||||
def execute_headers_callbacks(response)
|
||||
(Typhoeus.on_headers + on_headers).map do |callback|
|
||||
callback.call(response)
|
||||
end
|
||||
end
|
||||
|
||||
# Execute necessary callback and yields response. This
|
||||
# include in every case on_complete and on_progress, on_success
|
||||
# if successful and on_failure if not.
|
||||
#
|
||||
# @example Execute callbacks.
|
||||
# request.execute_callbacks
|
||||
#
|
||||
# @return [ void ]
|
||||
#
|
||||
# @api private
|
||||
def execute_callbacks
|
||||
callbacks = Typhoeus.on_complete + Typhoeus.on_progress + on_complete + on_progress
|
||||
|
||||
if response && response.success?
|
||||
callbacks += Typhoeus.on_success + on_success
|
||||
elsif response
|
||||
callbacks += Typhoeus.on_failure + on_failure
|
||||
end
|
||||
|
||||
callbacks.each do |callback|
|
||||
self.response.handled_response = callback.call(self.response)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module contains custom serializer.
|
||||
module Marshal
|
||||
|
||||
# Return the important data needed to serialize this Request, except the
|
||||
# request callbacks and `hydra`, since they cannot be marshalled.
|
||||
def marshal_dump
|
||||
unmarshallable = %w(@on_complete @on_success @on_failure @on_progress @on_headers @on_body @hydra)
|
||||
(instance_variables - unmarshallable - unmarshallable.map(&:to_sym)).map do |name|
|
||||
[name, instance_variable_get(name)]
|
||||
end
|
||||
end
|
||||
|
||||
# Load.
|
||||
def marshal_load(attributes)
|
||||
attributes.each { |name, value| instance_variable_set(name, value) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module handles the GET request memoization
|
||||
# on the request side. Memoization needs to be turned
|
||||
# on:
|
||||
# Typhoeus.configure do |config|
|
||||
# config.memoize = true
|
||||
# end
|
||||
#
|
||||
# @api private
|
||||
module Memoizable
|
||||
|
||||
# Override response setter and memoizes response
|
||||
# if the request is memoizable.
|
||||
#
|
||||
# @param [ Response ] response The response to set.
|
||||
#
|
||||
# @example Set response.
|
||||
# request.response = response
|
||||
def response=(response)
|
||||
hydra.memory[self] = response if memoizable?
|
||||
super
|
||||
end
|
||||
|
||||
# Return whether a request is memoizable.
|
||||
#
|
||||
# @example Is request memoizable?
|
||||
# request.memoizable?
|
||||
#
|
||||
# @return [ Boolean ] Return true if memoizable, false else.
|
||||
def memoizable?
|
||||
Typhoeus::Config.memoize &&
|
||||
(options[:method].nil? || options[:method] == :get)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module contains everything what is necessary
|
||||
# to make a single request.
|
||||
module Operations
|
||||
|
||||
# Run a request.
|
||||
#
|
||||
# @example Run a request.
|
||||
# Typhoeus::Request.new("www.example.com").run
|
||||
#
|
||||
# @return [ Response ] The response.
|
||||
def run
|
||||
easy = EasyFactory.new(self).get
|
||||
easy.perform
|
||||
response
|
||||
end
|
||||
|
||||
# Sets a response, the request on the response
|
||||
# and executes the callbacks.
|
||||
#
|
||||
# @param [Typhoeus::Response] response The response.
|
||||
# @param [Boolean] bypass_memoization Wether to bypass
|
||||
# memoization or not. Decides how the response is set.
|
||||
#
|
||||
# @return [Typhoeus::Response] The response.
|
||||
def finish(response, bypass_memoization = nil)
|
||||
if bypass_memoization
|
||||
@response = response
|
||||
else
|
||||
self.response = response
|
||||
end
|
||||
self.response.request = self
|
||||
execute_callbacks
|
||||
response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module contains logic for having a reponse
|
||||
# getter and setter.
|
||||
module Responseable
|
||||
|
||||
# Set the response.
|
||||
#
|
||||
# @example Set response.
|
||||
# request.response = response
|
||||
#
|
||||
# @param [ Response ] value The response to set.
|
||||
def response=(value)
|
||||
@response = value
|
||||
end
|
||||
|
||||
# Return the response.
|
||||
#
|
||||
# @example Return response.
|
||||
# request.response
|
||||
#
|
||||
# @return [ Response ] The response.
|
||||
def response
|
||||
@response ||= nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module contians the logic for response streaming.
|
||||
module Streamable
|
||||
|
||||
# Set on_body callback.
|
||||
#
|
||||
# This callback will be called each time a portion of the body is read from the socket.
|
||||
# Setting an on_body callback will cause the response body to be empty.
|
||||
#
|
||||
# @example Set on_body.
|
||||
# request.on_body { |body_chunk, response| puts "Got #{body_chunk.bytesize} bytes" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
#
|
||||
# @yield [ Typhoeus::Response, String ]
|
||||
#
|
||||
# @return [ Array<Block> ] All on_body blocks.
|
||||
def on_body(&block)
|
||||
@on_body ||= []
|
||||
@on_body << block if block_given?
|
||||
@on_body
|
||||
end
|
||||
|
||||
# Is this request using streaming?
|
||||
#
|
||||
# @return [ Boolean ] True if any on_body blocks have been set.
|
||||
def streaming?
|
||||
defined?(@on_body) && @on_body.any?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
module Typhoeus
|
||||
class Request
|
||||
|
||||
# This module handles stubbing on the request side.
|
||||
# It plays well with the block_connection configuration,
|
||||
# which raises when you make a request which is not stubbed.
|
||||
#
|
||||
# @api private
|
||||
module Stubbable
|
||||
|
||||
# Override run in order to check for matching expectations.
|
||||
# When an expectation is found, super is not called. Instead a
|
||||
# canned response is assigned to the request.
|
||||
#
|
||||
# @example Run the request.
|
||||
# request.run
|
||||
#
|
||||
# @return [ Response ] The response.
|
||||
def run
|
||||
if response = Expectation.response_for(self)
|
||||
execute_headers_callbacks(response)
|
||||
self.on_body.each{ |callback| callback.call(response.body, response) }
|
||||
finish(response)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
require 'typhoeus/response/header'
|
||||
require 'typhoeus/response/informations'
|
||||
require 'typhoeus/response/status'
|
||||
require 'typhoeus/response/cacheable'
|
||||
|
||||
module Typhoeus
|
||||
|
||||
# This class represents the response.
|
||||
class Response
|
||||
include Response::Informations
|
||||
include Response::Status
|
||||
include Response::Cacheable
|
||||
|
||||
# Remembers the corresponding request.
|
||||
#
|
||||
# @example Get request.
|
||||
# request = Typhoeus::Request.new("www.example.com")
|
||||
# response = request.run
|
||||
# request == response.request
|
||||
# #=> true
|
||||
#
|
||||
# @return [ Typhoeus::Request ]
|
||||
attr_accessor :request
|
||||
|
||||
# The provided options, which contain all the
|
||||
# informations about the request.
|
||||
#
|
||||
# @return [ Hash ]
|
||||
attr_accessor :options
|
||||
|
||||
# Set the handled response.
|
||||
attr_writer :handled_response
|
||||
|
||||
# @api private
|
||||
attr_writer :mock
|
||||
|
||||
# Create a new response.
|
||||
#
|
||||
# @example Create a response.
|
||||
# Response.new
|
||||
#
|
||||
# @param [ Hash ] options The options hash.
|
||||
#
|
||||
# @return [ Response ] The new response.
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
@headers = Header.new(options[:headers]) if options[:headers]
|
||||
end
|
||||
|
||||
# Returns whether this request is mocked
|
||||
# or not.
|
||||
#
|
||||
# @api private
|
||||
def mock
|
||||
defined?(@mock) ? @mock : options[:mock]
|
||||
end
|
||||
alias :mock? :mock
|
||||
|
||||
# Returns the handled_response if it has
|
||||
# been defined; otherwise, returns the response
|
||||
#
|
||||
# @return [ Object ] The result of callbacks
|
||||
# done on the response or the original response.
|
||||
def handled_response
|
||||
@handled_response || self
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
module Typhoeus
|
||||
class Response
|
||||
module Cacheable
|
||||
|
||||
# Set the cache status, if we got response from cache
|
||||
# it will have cached? == true
|
||||
attr_writer :cached
|
||||
|
||||
def cached?
|
||||
defined?(@cached) ? !!@cached : false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,105 @@
|
||||
require 'delegate'
|
||||
|
||||
module Typhoeus
|
||||
class Response
|
||||
|
||||
# This class represents the response header.
|
||||
# It can be accessed like a hash.
|
||||
# Values can be strings (normal case) or arrays of strings (for duplicates headers)
|
||||
#
|
||||
# @api private
|
||||
class Header < DelegateClass(Hash)
|
||||
|
||||
# Create a new header.
|
||||
#
|
||||
# @example Create new header.
|
||||
# Header.new(raw)
|
||||
#
|
||||
# @param [ String ] raw The raw header.
|
||||
def initialize(raw)
|
||||
super({})
|
||||
@raw = raw
|
||||
@sanitized = {}
|
||||
parse
|
||||
end
|
||||
|
||||
def [](key)
|
||||
fetch(key) { @sanitized[key.to_s.downcase] }
|
||||
end
|
||||
|
||||
# Parses the raw header.
|
||||
#
|
||||
# @example Parse header.
|
||||
# header.parse
|
||||
def parse
|
||||
case @raw
|
||||
when Hash
|
||||
raw.each do |k, v|
|
||||
process_pair(k, v)
|
||||
end
|
||||
when String
|
||||
raw.split(/\r?\n(?!\s)/).each do |header|
|
||||
header.strip!
|
||||
next if header.empty? || header.start_with?( 'HTTP/' )
|
||||
process_line(header)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Processes line and saves the result.
|
||||
#
|
||||
# @return [ void ]
|
||||
def process_line(header)
|
||||
key, value = header.split(':', 2)
|
||||
process_pair(key.strip, (value ? value.strip.gsub(/\r?\n\s*/, ' ') : ''))
|
||||
end
|
||||
|
||||
# Sets key value pair for self and @sanitized.
|
||||
#
|
||||
# @return [ void ]
|
||||
def process_pair(key, value)
|
||||
set_value(key, value, self)
|
||||
@sanitized[key.downcase] = self[key]
|
||||
end
|
||||
|
||||
# Sets value for key in specified hash
|
||||
#
|
||||
# @return [ void ]
|
||||
def set_value(key, value, hash)
|
||||
current_value = hash[key]
|
||||
if current_value
|
||||
if current_value.is_a? Array
|
||||
current_value << value
|
||||
else
|
||||
hash[key] = [current_value, value]
|
||||
end
|
||||
else
|
||||
hash[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the raw header or empty string.
|
||||
#
|
||||
# @example Return raw header.
|
||||
# header.raw
|
||||
#
|
||||
# @return [ String ] The raw header.
|
||||
def raw
|
||||
@raw || ''
|
||||
end
|
||||
|
||||
# Sets the default proc for the specified hash independent of the Ruby version.
|
||||
#
|
||||
# @return [ void ]
|
||||
def set_default_proc_on(hash, default_proc)
|
||||
if hash.respond_to?(:default_proc=)
|
||||
hash.default_proc = default_proc
|
||||
else
|
||||
hash.replace(Hash.new(&default_proc).merge(hash))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,305 @@
|
||||
module Typhoeus
|
||||
class Response
|
||||
|
||||
# This module contains logic about informations
|
||||
# on a response.
|
||||
module Informations
|
||||
|
||||
# Return libcurls return value.
|
||||
#
|
||||
# @example Get return_code.
|
||||
# response.return_code
|
||||
#
|
||||
# @return [ Symbol ] The return_code.
|
||||
def return_code
|
||||
options[:return_code]
|
||||
end
|
||||
|
||||
# Returns a string describing the return.
|
||||
#
|
||||
# @example Get return_message.
|
||||
# response.return_message
|
||||
#
|
||||
# @return [ String ] The return_message.
|
||||
#
|
||||
# @since 0.6.2
|
||||
def return_message
|
||||
Ethon::Curl.easy_strerror(return_code) if return_code
|
||||
end
|
||||
|
||||
# Return the http response body.
|
||||
#
|
||||
# @example Get response_body.
|
||||
# response.response_body
|
||||
#
|
||||
# @return [ String ] The response_body.
|
||||
def response_body
|
||||
options[:response_body] || options[:body]
|
||||
end
|
||||
alias :body :response_body
|
||||
|
||||
# Return the http response headers.
|
||||
#
|
||||
# @example Get response_headers.
|
||||
# response.response_headers
|
||||
#
|
||||
# @return [ String ] The response_headers.
|
||||
def response_headers
|
||||
return options[:response_headers] if options[:response_headers]
|
||||
if mock? && h = options[:headers]
|
||||
status_code = return_code || "200"
|
||||
reason_phrase = status_code == "200" ? "OK" : "Mock Reason Phrase"
|
||||
status_line = "HTTP/1.1 #{status_code} #{reason_phrase}"
|
||||
actual_headers = h.map{ |k,v| [k, v.respond_to?(:join) ? v.join(',') : v] }.
|
||||
map{ |e| "#{e.first}: #{e.last}" }
|
||||
|
||||
[status_line, *actual_headers].join("\r\n")
|
||||
end
|
||||
end
|
||||
|
||||
# Return the last received HTTP, FTP or SMTP response code.
|
||||
# The value will be zero if no server response code has
|
||||
# been received. Note that a proxy's CONNECT response should
|
||||
# be read with http_connect_code and not this.
|
||||
#
|
||||
# @example Get response_code.
|
||||
# response.response_code
|
||||
#
|
||||
# @return [ Integer ] The response_code.
|
||||
def response_code
|
||||
(options[:response_code] || options[:code]).to_i
|
||||
end
|
||||
alias :code :response_code
|
||||
|
||||
# Return the available http auth methods.
|
||||
# Bitmask indicating the authentication method(s)
|
||||
# available.
|
||||
#
|
||||
# @example Get httpauth_avail.
|
||||
# response.httpauth_avail
|
||||
#
|
||||
# @return [ Integer ] The bitmask.
|
||||
def httpauth_avail
|
||||
options[:httpauth_avail]
|
||||
end
|
||||
|
||||
|
||||
# Return the total time in seconds for the previous
|
||||
# transfer, including name resolving, TCP connect etc.
|
||||
#
|
||||
# @example Get total_time.
|
||||
# response.total_time
|
||||
#
|
||||
# @return [ Float ] The total_time.
|
||||
def total_time
|
||||
options[:total_time] || options[:time]
|
||||
end
|
||||
alias :time :total_time
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the first byte is received by libcurl. This
|
||||
# includes pretransfer time and also the time the
|
||||
# server needs to calculate the result.
|
||||
#
|
||||
# @example Get starttransfer_time.
|
||||
# response.starttransfer_time
|
||||
#
|
||||
# @return [ Float ] The starttransfer_time.
|
||||
def starttransfer_time
|
||||
options[:starttransfer_time] || options[:start_transfer_time]
|
||||
end
|
||||
alias :start_transfer_time :starttransfer_time
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the SSL/SSH connect/handshake to the remote
|
||||
# host was completed. This time is most often very near
|
||||
# to the pre transfer time, except for cases such as HTTP
|
||||
# pipelining where the pretransfer time can be delayed
|
||||
# due to waits in line for the pipeline and more.
|
||||
#
|
||||
# @example Get appconnect_time.
|
||||
# response.appconnect_time
|
||||
#
|
||||
# @return [ Float ] The appconnect_time.
|
||||
def appconnect_time
|
||||
options[:appconnect_time] || options[:app_connect_time]
|
||||
end
|
||||
alias :app_connect_time :appconnect_time
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the file transfer is just about to begin. This
|
||||
# includes all pre-transfer commands and negotiations
|
||||
# that are specific to the particular protocol(s) involved.
|
||||
# It does not involve the sending of the protocol-
|
||||
# specific request that triggers a transfer.
|
||||
#
|
||||
# @example Get pretransfer_time.
|
||||
# response.pretransfer_time
|
||||
#
|
||||
# @return [ Float ] The pretransfer_time.
|
||||
def pretransfer_time
|
||||
options[:pretransfer_time]
|
||||
end
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the connect to the remote host (or proxy) was completed.
|
||||
#
|
||||
# @example Get connect_time.
|
||||
# response.connect_time
|
||||
#
|
||||
# @return [ Float ] The connect_time.
|
||||
def connect_time
|
||||
options[:connect_time]
|
||||
end
|
||||
|
||||
# Return the time, in seconds, it took from the
|
||||
# start until the name resolving was completed.
|
||||
#
|
||||
# @example Get namelookup_time.
|
||||
# response.namelookup_time
|
||||
#
|
||||
# @return [ Float ] The namelookup_time.
|
||||
def namelookup_time
|
||||
options[:namelookup_time] || options[:name_lookup_time]
|
||||
end
|
||||
alias :name_lookup_time :namelookup_time
|
||||
|
||||
# Return the time, in seconds, it took for all redirection steps
|
||||
# include name lookup, connect, pretransfer and transfer before the
|
||||
# final transaction was started. time_redirect shows the complete
|
||||
# execution time for multiple redirections.
|
||||
#
|
||||
# @example Get redirect_time.
|
||||
# response.redirect_time
|
||||
#
|
||||
# @return [ Float ] The redirect_time.
|
||||
def redirect_time
|
||||
options[:redirect_time]
|
||||
end
|
||||
|
||||
# Return the last used effective url.
|
||||
#
|
||||
# @example Get effective_url.
|
||||
# response.effective_url
|
||||
#
|
||||
# @return [ String ] The effective_url.
|
||||
def effective_url
|
||||
options[:effective_url]
|
||||
end
|
||||
|
||||
# Return the string holding the IP address of the most recent
|
||||
# connection done with this curl handle. This string
|
||||
# may be IPv6 if that's enabled.
|
||||
#
|
||||
# @example Get primary_ip.
|
||||
# response.primary_ip
|
||||
#
|
||||
# @return [ String ] The primary_ip.
|
||||
def primary_ip
|
||||
options[:primary_ip]
|
||||
end
|
||||
|
||||
# Return the total number of redirections that were
|
||||
# actually followed
|
||||
#
|
||||
# @example Get redirect_count.
|
||||
# response.redirect_count
|
||||
#
|
||||
# @return [ Integer ] The redirect_count.
|
||||
def redirect_count
|
||||
options[:redirect_count]
|
||||
end
|
||||
|
||||
# Return the URL a redirect would take you to, had you enabled redirects.
|
||||
#
|
||||
# @example Get redirect_url.
|
||||
# response.redirect_url
|
||||
#
|
||||
# @return [ String ] The redirect_url.
|
||||
def redirect_url
|
||||
options[:redirect_url]
|
||||
end
|
||||
|
||||
def request_size
|
||||
options[:request_size]
|
||||
end
|
||||
|
||||
# Return the bytes, the total amount of bytes that were uploaded
|
||||
#
|
||||
# @example Get size_upload.
|
||||
# response.size_upload
|
||||
#
|
||||
# @return [ Float ] The size_upload.
|
||||
def size_upload
|
||||
options[:size_upload]
|
||||
end
|
||||
|
||||
|
||||
# Return the bytes, the total amount of bytes that were downloaded.
|
||||
# The amount is only for the latest transfer and will be reset again
|
||||
# for each new transfer. This counts actual payload data, what's
|
||||
# also commonly called body. All meta and header data are excluded
|
||||
# and will not be counted in this number.
|
||||
#
|
||||
# @example Get size_download
|
||||
# response.size_download
|
||||
#
|
||||
# @return [ Float ] The size_download.
|
||||
def size_download
|
||||
options[:size_download]
|
||||
end
|
||||
|
||||
# Return the bytes/second, the average upload speed that curl
|
||||
# measured for the complete upload
|
||||
#
|
||||
# @example Get speed_upload.
|
||||
# response.speed_upload
|
||||
#
|
||||
# @return [ Float ] The speed_upload.
|
||||
def speed_upload
|
||||
options[:speed_upload]
|
||||
end
|
||||
|
||||
# Return the bytes/second, the average download speed that curl
|
||||
# measured for the complete download
|
||||
#
|
||||
# @example Get speed_download.
|
||||
# response.speed_download
|
||||
#
|
||||
# @return [ Float ] The speed_download.
|
||||
def speed_download
|
||||
options[:speed_download]
|
||||
end
|
||||
|
||||
def debug_info
|
||||
options[:debug_info]
|
||||
end
|
||||
|
||||
# Returns the response header.
|
||||
#
|
||||
# @example Return headers.
|
||||
# response.headers
|
||||
#
|
||||
# @return [ Typhoeus::Header ] The response header.
|
||||
def headers
|
||||
return Header.new(options[:headers]) if mock? && options[:headers]
|
||||
return nil if response_headers.nil? && !defined?(@headers)
|
||||
@headers ||= Header.new(response_headers.split("\r\n\r\n").last)
|
||||
end
|
||||
alias :headers_hash :headers
|
||||
|
||||
# Return all redirections in between as multiple
|
||||
# responses with header.
|
||||
#
|
||||
# @example Return redirections.
|
||||
# response.redirections
|
||||
#
|
||||
# @return [ Array<Typhoeus::Response> ] The redirections
|
||||
def redirections
|
||||
return [] unless response_headers
|
||||
response_headers.split("\r\n\r\n")[0..-2].map{ |h| Response.new(:response_headers => h) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
module Typhoeus
|
||||
class Response
|
||||
|
||||
# This module contains logic about the http
|
||||
# status.
|
||||
module Status
|
||||
|
||||
# Return the status message if present.
|
||||
#
|
||||
# @example Return status message.
|
||||
# reesponse.status_message
|
||||
#
|
||||
# @return [ String ] The message.
|
||||
def status_message
|
||||
return @status_message if defined?(@status_message) && @status_message
|
||||
return options[:status_message] unless options[:status_message].nil?
|
||||
|
||||
# HTTP servers can choose not to include the explanation to HTTP codes. The RFC
|
||||
# states this (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4):
|
||||
# Except when responding to a HEAD request, the server SHOULD include an entity containing
|
||||
# an explanation of the error situation [...]
|
||||
# This means 'HTTP/1.1 404' is as valid as 'HTTP/1.1 404 Not Found' and we have to handle it.
|
||||
#
|
||||
# Regexp doc: http://rubular.com/r/eAr1oVYsVa
|
||||
if first_header_line != nil and first_header_line[/\d{3} (.*)$/, 1] != nil
|
||||
@status_message = first_header_line[/\d{3} (.*)$/, 1].chomp
|
||||
else
|
||||
@status_message = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Return the http version.
|
||||
#
|
||||
# @example Return http version.
|
||||
# response.http_version
|
||||
#
|
||||
# @return [ String ] The http version.
|
||||
def http_version
|
||||
@http_version ||= first_header_line ? first_header_line[/HTTP\/(\S+)/, 1] : nil
|
||||
end
|
||||
|
||||
# Return whether the response is a success.
|
||||
#
|
||||
# @example Return if the response was successful.
|
||||
# response.success?
|
||||
#
|
||||
# @return [ Boolean ] Return true if successful, false else.
|
||||
def success?
|
||||
(mock || return_code == :ok) && response_code && has_good_response_code?
|
||||
end
|
||||
|
||||
# Return whether the response is a failure.
|
||||
#
|
||||
# @example Return if the response was failed.
|
||||
# response.failure?
|
||||
#
|
||||
# @return [ Boolean ] Return true if failure, false else.
|
||||
def failure?
|
||||
(mock || return_code == :internal_server_error) && response_code && has_bad_response_code?
|
||||
end
|
||||
|
||||
# Return wether the response is modified.
|
||||
#
|
||||
# @example Return if the response was modified.
|
||||
# response.modified?
|
||||
#
|
||||
# @return [ Boolean ] Return true if modified, false else.
|
||||
def modified?
|
||||
(mock || return_code == :ok) && response_code && response_code != 304
|
||||
end
|
||||
|
||||
# Return whether the response is timed out.
|
||||
#
|
||||
# @example Return if the response timed out.
|
||||
# response.timed_out?
|
||||
#
|
||||
# @return [ Boolean ] Return true if timed out, false else.
|
||||
def timed_out?
|
||||
return_code == :operation_timedout
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# :nodoc:
|
||||
def first_header_line
|
||||
@first_header_line ||= begin
|
||||
if response_headers.to_s.include?("\r\n\r\n")
|
||||
response_headers.to_s.split("\r\n\r\n").last.split("\r\n").first
|
||||
else
|
||||
response_headers.to_s.split("\r\n").first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def has_good_response_code?
|
||||
response_code >= 200 && response_code < 300
|
||||
end
|
||||
|
||||
# :nodoc:
|
||||
def has_bad_response_code?
|
||||
!has_good_response_code?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
module Typhoeus
|
||||
|
||||
# The current Typhoeus version.
|
||||
VERSION = '1.4.1'
|
||||
end
|
||||
Reference in New Issue
Block a user