Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -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
+143
View File
@@ -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
+28
View File
@@ -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
+28
View File
@@ -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
+35
View File
@@ -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