This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains all the logic around the callbacks,
|
||||
# which are needed to interact with libcurl.
|
||||
#
|
||||
# @api private
|
||||
module Callbacks
|
||||
|
||||
# :nodoc:
|
||||
def self.included(base)
|
||||
base.send(:attr_accessor, *[:response_body, :response_headers, :debug_info])
|
||||
end
|
||||
|
||||
# Set writefunction and headerfunction callback.
|
||||
# They are called by libcurl in order to provide the header and
|
||||
# the body from the request.
|
||||
#
|
||||
# @example Set callbacks.
|
||||
# easy.set_callbacks
|
||||
def set_callbacks
|
||||
Curl.set_option(:writefunction, body_write_callback, handle)
|
||||
Curl.set_option(:headerfunction, header_write_callback, handle)
|
||||
Curl.set_option(:debugfunction, debug_callback, handle)
|
||||
@response_body = String.new
|
||||
@response_headers = String.new
|
||||
@headers_called = false
|
||||
@debug_info = Ethon::Easy::DebugInfo.new
|
||||
end
|
||||
|
||||
# Returns the body write callback.
|
||||
#
|
||||
# @example Return the callback.
|
||||
# easy.body_write_callback
|
||||
#
|
||||
# @return [ Proc ] The callback.
|
||||
def body_write_callback
|
||||
@body_write_callback ||= proc do |stream, size, num, object|
|
||||
headers
|
||||
result = body(chunk = stream.read_string(size * num))
|
||||
@response_body << chunk if result == :unyielded
|
||||
result != :abort ? size * num : -1
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the header write callback.
|
||||
#
|
||||
# @example Return the callback.
|
||||
# easy.header_write_callback
|
||||
#
|
||||
# @return [ Proc ] The callback.
|
||||
def header_write_callback
|
||||
@header_write_callback ||= proc {|stream, size, num, object|
|
||||
result = headers
|
||||
@response_headers << stream.read_string(size * num)
|
||||
result != :abort ? size * num : -1
|
||||
}
|
||||
end
|
||||
|
||||
# Returns the debug callback. This callback is currently used
|
||||
# write the raw http request headers.
|
||||
#
|
||||
# @example Return the callback.
|
||||
# easy.debug_callback
|
||||
#
|
||||
# @return [ Proc ] The callback.
|
||||
def debug_callback
|
||||
@debug_callback ||= proc {|handle, type, data, size, udata|
|
||||
message = data.read_string(size)
|
||||
@debug_info.add type, message
|
||||
print message unless [:data_in, :data_out].include?(type)
|
||||
0
|
||||
}
|
||||
end
|
||||
|
||||
def set_progress_callback
|
||||
if Curl.version_info[:version] >= "7.32.0"
|
||||
Curl.set_option(:xferinfofunction, progress_callback, handle)
|
||||
else
|
||||
Curl.set_option(:progressfunction, progress_callback, handle)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the progress callback.
|
||||
#
|
||||
# @example Return the callback.
|
||||
# easy.progress_callback
|
||||
#
|
||||
# @return [ Proc ] The callback.
|
||||
def progress_callback
|
||||
@progress_callback ||= proc { |_, dltotal, dlnow, ultotal, ulnow|
|
||||
progress(dltotal, dlnow, ultotal, ulnow)
|
||||
0
|
||||
}
|
||||
end
|
||||
|
||||
# Set the read callback. This callback is used by libcurl to
|
||||
# read data when performing a PUT request.
|
||||
#
|
||||
# @example Set the callback.
|
||||
# easy.set_read_callback("a=1")
|
||||
#
|
||||
# @param [ String ] body The body.
|
||||
def set_read_callback(body)
|
||||
@request_body_read = 0
|
||||
readfunction do |stream, size, num, object|
|
||||
size = size * num
|
||||
body_size = if body.respond_to?(:bytesize)
|
||||
body.bytesize
|
||||
elsif body.respond_to?(:size)
|
||||
body.size
|
||||
elsif body.is_a?(File)
|
||||
File.size(body.path)
|
||||
end
|
||||
|
||||
left = body_size - @request_body_read
|
||||
size = left if size > left
|
||||
|
||||
if size > 0
|
||||
chunk = if body.respond_to?(:byteslice)
|
||||
body.byteslice(@request_body_read, size)
|
||||
elsif body.respond_to?(:read)
|
||||
body.read(size)
|
||||
else
|
||||
body[@request_body_read, size]
|
||||
end
|
||||
|
||||
stream.write_string(
|
||||
chunk, size
|
||||
)
|
||||
@request_body_read += size
|
||||
end
|
||||
size
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the body read callback.
|
||||
#
|
||||
# @example Return the callback.
|
||||
# easy.read_callback
|
||||
#
|
||||
# @return [ Proc ] The callback.
|
||||
def read_callback
|
||||
@read_callback
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This class is used to store and retreive debug information,
|
||||
# which is only saved when verbose is set to true.
|
||||
#
|
||||
# @api private
|
||||
class DebugInfo
|
||||
|
||||
MESSAGE_TYPES = Ethon::Curl::DebugInfoType.to_h.keys
|
||||
|
||||
class Message
|
||||
attr_reader :type, :message
|
||||
|
||||
def initialize(type, message)
|
||||
@type = type
|
||||
@message = message
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@messages = []
|
||||
end
|
||||
|
||||
def add(type, message)
|
||||
@messages << Message.new(type, message)
|
||||
end
|
||||
|
||||
def messages_for(type)
|
||||
@messages.select {|m| m.type == type }.map(&:message)
|
||||
end
|
||||
|
||||
MESSAGE_TYPES.each do |type|
|
||||
eval %Q|def #{type}; messages_for(:#{type}); end|
|
||||
end
|
||||
|
||||
def to_a
|
||||
@messages.map(&:message)
|
||||
end
|
||||
|
||||
def to_h
|
||||
Hash[MESSAGE_TYPES.map {|k| [k, send(k)] }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains class methods for feature checks
|
||||
module Features
|
||||
# Returns true if this curl version supports zlib.
|
||||
#
|
||||
# @example Return wether zlib is supported.
|
||||
# Ethon::Easy.supports_zlib?
|
||||
#
|
||||
# @return [ Boolean ] True if supported, else false.
|
||||
def supports_zlib?
|
||||
!!(Curl.version_info[:features] & Curl::VERSION_LIBZ)
|
||||
end
|
||||
|
||||
# Returns true if this curl version supports AsynchDNS.
|
||||
#
|
||||
# @example
|
||||
# Ethon::Easy.supports_asynch_dns?
|
||||
#
|
||||
# @return [ Boolean ] True if supported, else false.
|
||||
def supports_asynch_dns?
|
||||
!!(Curl.version_info[:features] & Curl::VERSION_ASYNCHDNS)
|
||||
end
|
||||
|
||||
alias :supports_timeout_ms? :supports_asynch_dns?
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
require 'ethon/easy/util'
|
||||
require 'ethon/easy/queryable'
|
||||
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This class represents a form and is used to send a payload in the
|
||||
# request body via POST/PUT.
|
||||
# It handles multipart forms, too.
|
||||
#
|
||||
# @api private
|
||||
class Form
|
||||
include Ethon::Easy::Util
|
||||
include Ethon::Easy::Queryable
|
||||
|
||||
# Return a new Form.
|
||||
#
|
||||
# @example Return a new Form.
|
||||
# Form.new({})
|
||||
#
|
||||
# @param [ Hash ] params The parameter with which to initialize the form.
|
||||
#
|
||||
# @return [ Form ] A new Form.
|
||||
def initialize(easy, params, multipart = nil)
|
||||
@easy = easy
|
||||
@params = params || {}
|
||||
@multipart = multipart
|
||||
end
|
||||
|
||||
# Return a pointer to the first form element in libcurl.
|
||||
#
|
||||
# @example Return the first form element.
|
||||
# form.first
|
||||
#
|
||||
# @return [ FFI::Pointer ] The first element.
|
||||
def first
|
||||
@first ||= FFI::MemoryPointer.new(:pointer)
|
||||
end
|
||||
|
||||
# Return a pointer to the last form element in libcurl.
|
||||
#
|
||||
# @example Return the last form element.
|
||||
# form.last
|
||||
#
|
||||
# @return [ FFI::Pointer ] The last element.
|
||||
def last
|
||||
@last ||= FFI::MemoryPointer.new(:pointer)
|
||||
end
|
||||
|
||||
# Return if form is multipart. The form is multipart
|
||||
# when it contains a file or multipart option is set on the form during creation.
|
||||
#
|
||||
# @example Return if form is multipart.
|
||||
# form.multipart?
|
||||
#
|
||||
# @return [ Boolean ] True if form is multipart, else false.
|
||||
def multipart?
|
||||
return true if @multipart
|
||||
query_pairs.any?{|pair| pair.respond_to?(:last) && pair.last.is_a?(Array)}
|
||||
end
|
||||
|
||||
# Add form elements to libcurl.
|
||||
#
|
||||
# @example Add form to libcurl.
|
||||
# form.materialize
|
||||
def materialize
|
||||
query_pairs.each { |pair| form_add(pair.first.to_s, pair.last) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_add(name, content)
|
||||
case content
|
||||
when Array
|
||||
Curl.formadd(first, last,
|
||||
:form_option, :copyname, :pointer, name,
|
||||
:form_option, :namelength, :long, name.bytesize,
|
||||
:form_option, :file, :string, content[2],
|
||||
:form_option, :filename, :string, content[0],
|
||||
:form_option, :contenttype, :string, content[1],
|
||||
:form_option, :end
|
||||
)
|
||||
else
|
||||
Curl.formadd(first, last,
|
||||
:form_option, :copyname, :pointer, name,
|
||||
:form_option, :namelength, :long, name.bytesize,
|
||||
:form_option, :copycontents, :pointer, content.to_s,
|
||||
:form_option, :contentslength, :long, content ? content.to_s.bytesize : 0,
|
||||
:form_option, :end
|
||||
)
|
||||
end
|
||||
|
||||
setup_garbage_collection
|
||||
end
|
||||
|
||||
def setup_garbage_collection
|
||||
# first is a pointer to a pointer. Since it's a MemoryPointer it will
|
||||
# auto clean itself up, but we need to clean up the object it points
|
||||
# to. So this results in (pseudo-c):
|
||||
# form_data_cleanup_handler = *first
|
||||
# curl_form_free(form_data_cleanup_handler)
|
||||
@form_data_cleanup_handler ||= FFI::AutoPointer.new(@first.get_pointer(0), Curl.method(:formfree))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
# This module contains the logic around adding headers to libcurl.
|
||||
#
|
||||
# @api private
|
||||
module Header
|
||||
# Return headers, return empty hash if none.
|
||||
#
|
||||
# @example Return the headers.
|
||||
# easy.headers
|
||||
#
|
||||
# @return [ Hash ] The headers.
|
||||
def headers
|
||||
@headers ||= {}
|
||||
end
|
||||
|
||||
# Set the headers.
|
||||
#
|
||||
# @example Set the headers.
|
||||
# easy.headers = {'User-Agent' => 'ethon'}
|
||||
#
|
||||
# @param [ Hash ] headers The headers.
|
||||
def headers=(headers)
|
||||
headers ||= {}
|
||||
header_list = nil
|
||||
headers.each do |k, v|
|
||||
header_list = Curl.slist_append(header_list, compose_header(k,v))
|
||||
end
|
||||
Curl.set_option(:httpheader, header_list, handle)
|
||||
|
||||
@header_list = header_list && FFI::AutoPointer.new(header_list, Curl.method(:slist_free_all))
|
||||
end
|
||||
|
||||
# Return header_list.
|
||||
#
|
||||
# @example Return header_list.
|
||||
# easy.header_list
|
||||
#
|
||||
# @return [ FFI::Pointer ] The header list.
|
||||
def header_list
|
||||
@header_list
|
||||
end
|
||||
|
||||
# Compose libcurl header string from key and value.
|
||||
# Also replaces null bytes, because libcurl will complain
|
||||
# otherwise.
|
||||
#
|
||||
# @example Compose header.
|
||||
# easy.compose_header('User-Agent', 'Ethon')
|
||||
#
|
||||
# @param [ String ] key The header name.
|
||||
# @param [ String ] value The header value.
|
||||
#
|
||||
# @return [ String ] The composed header.
|
||||
def compose_header(key, value)
|
||||
Util.escape_zero_byte("#{key}: #{value}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
require 'ethon/easy/http/actionable'
|
||||
require 'ethon/easy/http/post'
|
||||
require 'ethon/easy/http/get'
|
||||
require 'ethon/easy/http/head'
|
||||
require 'ethon/easy/http/put'
|
||||
require 'ethon/easy/http/delete'
|
||||
require 'ethon/easy/http/patch'
|
||||
require 'ethon/easy/http/options'
|
||||
require 'ethon/easy/http/custom'
|
||||
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains logic about making valid HTTP requests.
|
||||
module Http
|
||||
|
||||
# Set specified options in order to make a HTTP request.
|
||||
# Look at {Ethon::Easy::Options Options} to see what you can
|
||||
# provide in the options hash.
|
||||
#
|
||||
# @example Set options for HTTP request.
|
||||
# easy.http_request("www.google.com", :get, {})
|
||||
#
|
||||
# @param [ String ] url The url.
|
||||
# @param [ String ] action_name The HTTP action name.
|
||||
# @param [ Hash ] options The options hash.
|
||||
#
|
||||
# @option options :params [ Hash ] Params hash which
|
||||
# is attached to the url.
|
||||
# @option options :body [ Hash ] Body hash which
|
||||
# becomes the request body. It is a PUT body for
|
||||
# PUT requests and a POST for everything else.
|
||||
# @option options :headers [ Hash ] Request headers.
|
||||
#
|
||||
# @return [ void ]
|
||||
#
|
||||
# @see Ethon::Easy::Options
|
||||
def http_request(url, action_name, options = {})
|
||||
fabricate(url, action_name, options).setup(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return the corresponding action class.
|
||||
#
|
||||
# @example Return the action.
|
||||
# Action.fabricate(:get)
|
||||
# Action.fabricate(:smash)
|
||||
#
|
||||
# @param [ String ] url The url.
|
||||
# @param [ String ] action_name The HTTP action name.
|
||||
# @param [ Hash ] options The option hash.
|
||||
#
|
||||
# @return [ Easy::Ethon::Actionable ] The request instance.
|
||||
def fabricate(url, action_name, options)
|
||||
constant_name = action_name.to_s.capitalize
|
||||
|
||||
if Ethon::Easy::Http.const_defined?(constant_name)
|
||||
Ethon::Easy::Http.const_get(constant_name).new(url, options)
|
||||
else
|
||||
Ethon::Easy::Http::Custom.new(constant_name.upcase, url, options)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,157 @@
|
||||
# frozen_string_literal: true
|
||||
require 'ethon/easy/http/putable'
|
||||
require 'ethon/easy/http/postable'
|
||||
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
# This module represents a Http Action and is a factory
|
||||
# for more real actions like GET, HEAD, POST and PUT.
|
||||
module Actionable
|
||||
|
||||
QUERY_OPTIONS = [ :params, :body, :params_encoding ]
|
||||
|
||||
# Create a new action.
|
||||
#
|
||||
# @example Create a new action.
|
||||
# Action.new("www.example.com", {})
|
||||
#
|
||||
# @param [ String ] url The url.
|
||||
# @param [ Hash ] options The options.
|
||||
#
|
||||
# @return [ Action ] A new action.
|
||||
def initialize(url, options)
|
||||
@url = url
|
||||
@options, @query_options = parse_options(options)
|
||||
end
|
||||
|
||||
# Return the url.
|
||||
#
|
||||
# @example Return url.
|
||||
# action.url
|
||||
#
|
||||
# @return [ String ] The url.
|
||||
def url
|
||||
@url
|
||||
end
|
||||
|
||||
# Return the options hash.
|
||||
#
|
||||
# @example Return options.
|
||||
# action.options
|
||||
#
|
||||
# @return [ Hash ] The options.
|
||||
def options
|
||||
@options
|
||||
end
|
||||
|
||||
# Returns the query options hash.
|
||||
#
|
||||
# @example Return query options.
|
||||
# action.query_options
|
||||
#
|
||||
# @return [ Hash ] The query options.
|
||||
def query_options
|
||||
@query_options
|
||||
end
|
||||
|
||||
# Return the params.
|
||||
#
|
||||
# @example Return params.
|
||||
# action.params
|
||||
#
|
||||
# @return [ Params ] The params.
|
||||
def params
|
||||
@params ||= Params.new(@easy, query_options.fetch(:params, nil))
|
||||
end
|
||||
|
||||
# Return the form.
|
||||
#
|
||||
# @example Return form.
|
||||
# action.form
|
||||
#
|
||||
# @return [ Form ] The form.
|
||||
def form
|
||||
@form ||= Form.new(@easy, query_options.fetch(:body, nil), options.fetch(:multipart, nil))
|
||||
end
|
||||
|
||||
# Get the requested array encoding. By default it's
|
||||
# :typhoeus, but it can also be set to :rack.
|
||||
#
|
||||
# @example Get encoding from options
|
||||
# action.params_encoding
|
||||
#
|
||||
def params_encoding
|
||||
@params_encoding ||= query_options.fetch(:params_encoding, :typhoeus)
|
||||
end
|
||||
|
||||
# Setup everything necessary for a proper request.
|
||||
#
|
||||
# @example setup.
|
||||
# action.setup(easy)
|
||||
#
|
||||
# @param [ easy ] easy the easy to setup.
|
||||
def setup(easy)
|
||||
@easy = easy
|
||||
|
||||
# Order is important, @easy will be used to provide access to options
|
||||
# relevant to the following operations (like whether or not to escape
|
||||
# values).
|
||||
easy.set_attributes(options)
|
||||
|
||||
set_form(easy) unless form.empty?
|
||||
|
||||
if params.empty?
|
||||
easy.url = url
|
||||
else
|
||||
set_params(easy)
|
||||
end
|
||||
end
|
||||
|
||||
# Setup request with params.
|
||||
#
|
||||
# @example Setup nothing.
|
||||
# action.set_params(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def set_params(easy)
|
||||
params.escape = easy.escape?
|
||||
params.params_encoding = params_encoding
|
||||
|
||||
base_url, base_params = url.split('?')
|
||||
base_url << '?'
|
||||
base_url << base_params.to_s
|
||||
base_url << '&' if base_params
|
||||
base_url << params.to_s
|
||||
|
||||
easy.url = base_url
|
||||
end
|
||||
|
||||
# Setup request with form.
|
||||
#
|
||||
# @example Setup nothing.
|
||||
# action.set_form(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def set_form(easy)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_options(options)
|
||||
query_options = {}
|
||||
options = options.dup
|
||||
|
||||
QUERY_OPTIONS.each do |query_option|
|
||||
if options.key?(query_option)
|
||||
query_options[query_option] = options.delete(query_option)
|
||||
end
|
||||
end
|
||||
|
||||
return options, query_options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making requests for custom HTTP verbs.
|
||||
class Custom
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
def initialize(verb, url, options)
|
||||
@verb = verb
|
||||
super(url, options)
|
||||
end
|
||||
|
||||
# Setup easy to make a request.
|
||||
#
|
||||
# @example Setup.
|
||||
# custom.set_params(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
easy.customrequest = @verb
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making DELETE requests.
|
||||
class Delete
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
# Setup easy to make a DELETE request.
|
||||
#
|
||||
# @example Setup customrequest.
|
||||
# delete.setup(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
easy.customrequest = "DELETE"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making GET requests.
|
||||
class Get
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
# Setup easy to make a GET request.
|
||||
#
|
||||
# @example Setup.
|
||||
# get.set_params(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
easy.customrequest = "GET" unless form.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making HEAD requests.
|
||||
class Head
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
# Setup easy to make a HEAD request.
|
||||
#
|
||||
# @example Setup.
|
||||
# get.set_params(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
easy.nobody = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making OPTIONS requests.
|
||||
class Options
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
# Setup easy to make a OPTIONS request.
|
||||
#
|
||||
# @example Setup.
|
||||
# options.setup(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
easy.customrequest = "OPTIONS"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making PATCH requests.
|
||||
class Patch
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
# Setup easy to make a PATCH request.
|
||||
#
|
||||
# @example Setup.
|
||||
# patch.setup(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
easy.customrequest = "PATCH"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
# This class knows everything about making POST requests.
|
||||
class Post
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Postable
|
||||
|
||||
# Setup easy to make a POST request.
|
||||
#
|
||||
# @example Setup.
|
||||
# post.setup(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
if form.empty?
|
||||
easy.postfieldsize = 0
|
||||
easy.copypostfields = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This module contains logic for setting up a [multipart] POST body.
|
||||
module Postable
|
||||
|
||||
# Set things up when form is provided.
|
||||
# Deals with multipart forms.
|
||||
#
|
||||
# @example Setup.
|
||||
# post.set_form(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def set_form(easy)
|
||||
easy.url ||= url
|
||||
form.params_encoding = params_encoding
|
||||
if form.multipart?
|
||||
form.escape = false
|
||||
form.materialize
|
||||
easy.httppost = form.first.read_pointer
|
||||
else
|
||||
form.escape = easy.escape?
|
||||
easy.postfieldsize = form.to_s.bytesize
|
||||
easy.copypostfields = form.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This class knows everything about making PUT requests.
|
||||
class Put
|
||||
include Ethon::Easy::Http::Actionable
|
||||
include Ethon::Easy::Http::Putable
|
||||
|
||||
# Setup easy to make a PUT request.
|
||||
#
|
||||
# @example Setup.
|
||||
# put.setup(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def setup(easy)
|
||||
super
|
||||
if form.empty?
|
||||
easy.upload = true
|
||||
easy.infilesize = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
module Http
|
||||
|
||||
# This module contains logic about setting up a PUT body.
|
||||
module Putable
|
||||
# Set things up when form is provided.
|
||||
# Deals with multipart forms.
|
||||
#
|
||||
# @example Setup.
|
||||
# put.set_form(easy)
|
||||
#
|
||||
# @param [ Easy ] easy The easy to setup.
|
||||
def set_form(easy)
|
||||
easy.upload = true
|
||||
form.escape = true
|
||||
form.params_encoding = params_encoding
|
||||
easy.infilesize = form.to_s.bytesize
|
||||
easy.set_read_callback(form.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,116 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains the methods to return informations
|
||||
# from the easy handle. See http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
|
||||
# for more information.
|
||||
module Informations
|
||||
|
||||
# Holds available informations and their type, which is needed to
|
||||
# request the informations from libcurl.
|
||||
AVAILABLE_INFORMATIONS = {
|
||||
# Return the available HTTP auth methods.
|
||||
:httpauth_avail => :long,
|
||||
|
||||
# Return the total time in seconds for the previous
|
||||
# transfer, including name resolution, TCP connection, etc.
|
||||
:total_time => :double,
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the first byte was received by libcurl. This
|
||||
# includes pre-transfer time and also the time the
|
||||
# server needs to calculate the result.
|
||||
:starttransfer_time => :double,
|
||||
|
||||
# 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 pre-transfer time can be delayed
|
||||
# due to waits in line for the pipeline and more.
|
||||
:appconnect_time => :double,
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the file transfer was 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.
|
||||
:pretransfer_time => :double,
|
||||
|
||||
# Return the time, in seconds, it took from the start
|
||||
# until the connect to the remote host (or proxy) was completed.
|
||||
:connect_time => :double,
|
||||
|
||||
# Return the time, in seconds, it took from the
|
||||
# start until the name resolution was completed.
|
||||
:namelookup_time => :double,
|
||||
|
||||
# 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. (Added in 7.12.3)
|
||||
:redirect_time => :double,
|
||||
|
||||
# Return the last used effective url.
|
||||
:effective_url => :string,
|
||||
|
||||
# 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.
|
||||
:primary_ip => :string,
|
||||
|
||||
# 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.
|
||||
:response_code => :long,
|
||||
|
||||
:request_size => :long,
|
||||
|
||||
# Return the total number of redirections that were
|
||||
# actually followed.
|
||||
:redirect_count => :long,
|
||||
|
||||
# URL a redirect would take you to, had you enabled redirects (Added in 7.18.2)
|
||||
:redirect_url => :string,
|
||||
|
||||
# Return the bytes, the total amount of bytes that were uploaded
|
||||
:size_upload => :double,
|
||||
|
||||
# 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.
|
||||
:size_download => :double,
|
||||
|
||||
# Return the bytes/second, the average upload speed that curl
|
||||
# measured for the complete upload
|
||||
:speed_upload => :double,
|
||||
|
||||
# Return the bytes/second, the average download speed that curl
|
||||
# measured for the complete download
|
||||
:speed_download => :double
|
||||
}
|
||||
|
||||
AVAILABLE_INFORMATIONS.each do |name, type|
|
||||
eval %Q|def #{name}; Curl.send(:get_info_#{type}, :#{name}, handle); end|
|
||||
end
|
||||
|
||||
# Returns true if this curl version supports zlib.
|
||||
#
|
||||
# @example Return wether zlib is supported.
|
||||
# easy.supports_zlib?
|
||||
#
|
||||
# @return [ Boolean ] True if supported, else false.
|
||||
# @deprecated Please use the static version instead
|
||||
def supports_zlib?
|
||||
Kernel.warn("Ethon: Easy#supports_zlib? is deprecated and will be removed, please use Easy#.")
|
||||
Easy.supports_zlib?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
class Mirror
|
||||
attr_reader :options
|
||||
alias_method :to_hash, :options
|
||||
|
||||
INFORMATIONS_TO_MIRROR = Informations::AVAILABLE_INFORMATIONS.keys +
|
||||
[:return_code, :response_headers, :response_body, :debug_info]
|
||||
|
||||
INFORMATIONS_TO_LOG = [:effective_url, :response_code, :return_code, :total_time]
|
||||
|
||||
def self.from_easy(easy)
|
||||
options = {}
|
||||
INFORMATIONS_TO_MIRROR.each do |info|
|
||||
options[info] = easy.send(info)
|
||||
end
|
||||
new(options)
|
||||
end
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
def log_informations
|
||||
Hash[*INFORMATIONS_TO_LOG.map do |info|
|
||||
[info, options[info]]
|
||||
end.flatten]
|
||||
end
|
||||
|
||||
INFORMATIONS_TO_MIRROR.each do |info|
|
||||
eval %Q|def #{info}; options[#{info}]; end|
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
# This module contains the logic to prepare and perform
|
||||
# an easy.
|
||||
module Operations
|
||||
# Returns a pointer to the curl easy handle.
|
||||
#
|
||||
# @example Return the handle.
|
||||
# easy.handle
|
||||
#
|
||||
# @return [ FFI::Pointer ] A pointer to the curl easy handle.
|
||||
def handle
|
||||
@handle ||= FFI::AutoPointer.new(Curl.easy_init, Curl.method(:easy_cleanup))
|
||||
end
|
||||
|
||||
# Sets a pointer to the curl easy handle.
|
||||
# @param [ ::FFI::Pointer ] Easy handle that will be assigned.
|
||||
def handle=(h)
|
||||
@handle = h
|
||||
end
|
||||
|
||||
# Perform the easy request.
|
||||
#
|
||||
# @example Perform the request.
|
||||
# easy.perform
|
||||
#
|
||||
# @return [ Integer ] The return code.
|
||||
def perform
|
||||
@return_code = Curl.easy_perform(handle)
|
||||
if Ethon.logger.debug?
|
||||
Ethon.logger.debug { "ETHON: performed #{log_inspect}" }
|
||||
end
|
||||
complete
|
||||
@return_code
|
||||
end
|
||||
|
||||
# Clean up the easy.
|
||||
#
|
||||
# @example Perform clean up.
|
||||
# easy.cleanup
|
||||
#
|
||||
# @return the result of the free which is nil
|
||||
def cleanup
|
||||
handle.free
|
||||
end
|
||||
|
||||
# Prepare the easy. Options, headers and callbacks
|
||||
# were set.
|
||||
#
|
||||
# @example Prepare easy.
|
||||
# easy.prepare
|
||||
#
|
||||
# @deprecated It is no longer necessary to call prepare.
|
||||
def prepare
|
||||
Ethon.logger.warn(
|
||||
"ETHON: It is no longer necessary to call "+
|
||||
"Easy#prepare. It's going to be removed "+
|
||||
"in future versions."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains the logic and knowledge about the
|
||||
# available options on easy.
|
||||
module Options
|
||||
attr_reader :url
|
||||
|
||||
def url=(value)
|
||||
@url = value
|
||||
Curl.set_option(:url, value, handle)
|
||||
end
|
||||
|
||||
def escape=( b )
|
||||
@escape = b
|
||||
end
|
||||
|
||||
def escape?
|
||||
return true if !defined?(@escape) || @escape.nil?
|
||||
@escape
|
||||
end
|
||||
|
||||
def multipart=(b)
|
||||
@multipart = b
|
||||
end
|
||||
|
||||
def multipart?
|
||||
!!@multipart
|
||||
end
|
||||
|
||||
Curl.easy_options(nil).each do |opt, props|
|
||||
method_name = "#{opt}=".freeze
|
||||
unless method_defined? method_name
|
||||
define_method(method_name) do |value|
|
||||
Curl.set_option(opt, value, handle)
|
||||
value
|
||||
end
|
||||
end
|
||||
next if props[:type] != :callback || method_defined?(opt)
|
||||
define_method(opt) do |&block|
|
||||
@procs ||= {}
|
||||
@procs[opt.to_sym] = block
|
||||
Curl.set_option(opt, block, handle)
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
require 'ethon/easy/util'
|
||||
require 'ethon/easy/queryable'
|
||||
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This class represents HTTP request parameters.
|
||||
#
|
||||
# @api private
|
||||
class Params
|
||||
include Ethon::Easy::Util
|
||||
include Ethon::Easy::Queryable
|
||||
|
||||
# Create a new Params.
|
||||
#
|
||||
# @example Create a new Params.
|
||||
# Params.new({})
|
||||
#
|
||||
# @param [ Hash ] params The params to use.
|
||||
#
|
||||
# @return [ Params ] A new Params.
|
||||
def initialize(easy, params)
|
||||
@easy = easy
|
||||
@params = params || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,154 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains logic about building
|
||||
# query parameters for url or form.
|
||||
module Queryable
|
||||
|
||||
# :nodoc:
|
||||
def self.included(base)
|
||||
base.send(:attr_accessor, :escape)
|
||||
base.send(:attr_accessor, :params_encoding)
|
||||
end
|
||||
|
||||
# Return wether there are elements in params or not.
|
||||
#
|
||||
# @example Return if params is empty.
|
||||
# form.empty?
|
||||
#
|
||||
# @return [ Boolean ] True if params is empty, else false.
|
||||
def empty?
|
||||
@params.empty?
|
||||
end
|
||||
|
||||
# Return the string representation of params.
|
||||
#
|
||||
# @example Return string representation.
|
||||
# params.to_s
|
||||
#
|
||||
# @return [ String ] The string representation.
|
||||
def to_s
|
||||
@to_s ||= query_pairs.map{ |pair|
|
||||
return pair if pair.is_a?(String)
|
||||
|
||||
if escape && @easy
|
||||
pair.map{ |e| @easy.escape(e.to_s) }.join("=")
|
||||
else
|
||||
pair.join("=")
|
||||
end
|
||||
}.join('&')
|
||||
end
|
||||
|
||||
# Return the query pairs.
|
||||
#
|
||||
# @example Return the query pairs.
|
||||
# params.query_pairs
|
||||
#
|
||||
# @return [ Array ] The query pairs.
|
||||
def query_pairs
|
||||
@query_pairs ||= build_query_pairs(@params)
|
||||
end
|
||||
|
||||
# Return query pairs build from a hash.
|
||||
#
|
||||
# @example Build query pairs.
|
||||
# action.build_query_pairs({a: 1, b: 2})
|
||||
# #=> [[:a, 1], [:b, 2]]
|
||||
#
|
||||
# @param [ Hash ] hash The hash to go through.
|
||||
#
|
||||
# @return [ Array ] The array of query pairs.
|
||||
def build_query_pairs(hash)
|
||||
return [hash] if hash.is_a?(String)
|
||||
|
||||
pairs = []
|
||||
recursively_generate_pairs(hash, nil, pairs)
|
||||
pairs
|
||||
end
|
||||
|
||||
# Return file info for a file.
|
||||
#
|
||||
# @example Return file info.
|
||||
# action.file_info(File.open('fubar', 'r'))
|
||||
#
|
||||
# @param [ File ] file The file to handle.
|
||||
#
|
||||
# @return [ Array ] Array of informations.
|
||||
def file_info(file)
|
||||
filename = File.basename(file.path)
|
||||
[
|
||||
filename,
|
||||
mime_type(filename),
|
||||
File.expand_path(file.path)
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mime_type(filename)
|
||||
if defined?(MIME) && t = MIME::Types.type_for(filename).first
|
||||
t.to_s
|
||||
else
|
||||
'application/octet-stream'
|
||||
end
|
||||
end
|
||||
|
||||
def recursively_generate_pairs(h, prefix, pairs)
|
||||
case h
|
||||
when Hash
|
||||
encode_hash_pairs(h, prefix, pairs)
|
||||
when Array
|
||||
if params_encoding == :rack
|
||||
encode_rack_array_pairs(h, prefix, pairs)
|
||||
elsif params_encoding == :multi
|
||||
encode_multi_array_pairs(h, prefix, pairs)
|
||||
elsif params_encoding == :none
|
||||
pairs << [prefix, h]
|
||||
else
|
||||
encode_indexed_array_pairs(h, prefix, pairs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def encode_hash_pairs(h, prefix, pairs)
|
||||
h.each_pair do |k,v|
|
||||
key = prefix.nil? ? k : "#{prefix}[#{k}]"
|
||||
pairs_for(v, key, pairs)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_indexed_array_pairs(h, prefix, pairs)
|
||||
h.each_with_index do |v, i|
|
||||
key = "#{prefix}[#{i}]"
|
||||
pairs_for(v, key, pairs)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_rack_array_pairs(h, prefix, pairs)
|
||||
h.each do |v|
|
||||
key = "#{prefix}[]"
|
||||
pairs_for(v, key, pairs)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_multi_array_pairs(h, prefix, pairs)
|
||||
h.each_with_index do |v, i|
|
||||
key = prefix
|
||||
pairs_for(v, key, pairs)
|
||||
end
|
||||
end
|
||||
|
||||
def pairs_for(v, key, pairs)
|
||||
case v
|
||||
when Hash, Array
|
||||
recursively_generate_pairs(v, key, pairs)
|
||||
when File, Tempfile
|
||||
pairs << [key, file_info(v)]
|
||||
else
|
||||
pairs << [key, v]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,136 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy
|
||||
|
||||
# This module contains the logic for the response callbacks.
|
||||
# The on_complete callback is the only one at the moment.
|
||||
#
|
||||
# You can set multiple callbacks, which are then executed
|
||||
# in the same order.
|
||||
#
|
||||
# easy.on_complete { p 1 }
|
||||
# easy.on_complete { p 2 }
|
||||
# easy.complete
|
||||
# #=> 1
|
||||
# #=> 2
|
||||
#
|
||||
# You can clear the callbacks:
|
||||
#
|
||||
# easy.on_complete { p 1 }
|
||||
# easy.on_complete { p 2 }
|
||||
# easy.on_complete.clear
|
||||
# easy.on_complete
|
||||
# #=> []
|
||||
module ResponseCallbacks
|
||||
|
||||
# Set on_headers callback.
|
||||
#
|
||||
# @example Set on_headers.
|
||||
# request.on_headers { p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
def on_headers(&block)
|
||||
@on_headers ||= []
|
||||
@on_headers << block if block_given?
|
||||
@on_headers
|
||||
end
|
||||
|
||||
# Execute on_headers callbacks.
|
||||
#
|
||||
# @example Execute on_headers.
|
||||
# request.headers
|
||||
def headers
|
||||
return if @headers_called
|
||||
@headers_called = true
|
||||
if defined?(@on_headers) and not @on_headers.nil?
|
||||
result = nil
|
||||
@on_headers.each do |callback|
|
||||
result = callback.call(self)
|
||||
break if result == :abort
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# Set on_complete callback.
|
||||
#
|
||||
# @example Set on_complete.
|
||||
# request.on_complete { p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
def on_complete(&block)
|
||||
@on_complete ||= []
|
||||
@on_complete << block if block_given?
|
||||
@on_complete
|
||||
end
|
||||
|
||||
# Execute on_complete callbacks.
|
||||
#
|
||||
# @example Execute on_completes.
|
||||
# request.complete
|
||||
def complete
|
||||
headers unless @response_headers.empty?
|
||||
if defined?(@on_complete) and not @on_complete.nil?
|
||||
@on_complete.each{ |callback| callback.call(self) }
|
||||
end
|
||||
end
|
||||
|
||||
# Set on_progress callback.
|
||||
#
|
||||
# @example Set on_progress.
|
||||
# request.on_progress {|dltotal, dlnow, ultotal, ulnow| p "#{dltotal} #{dlnow} #{ultotal} #{ulnow}" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
def on_progress(&block)
|
||||
@on_progress ||= []
|
||||
if block_given?
|
||||
@on_progress << block
|
||||
set_progress_callback
|
||||
self.noprogress = 0
|
||||
end
|
||||
@on_progress
|
||||
end
|
||||
|
||||
# Execute on_progress callbacks.
|
||||
#
|
||||
# @example Execute on_progress.
|
||||
# request.body(1, 1, 1, 1)
|
||||
def progress(dltotal, dlnow, ultotal, ulnow)
|
||||
if defined?(@on_progress) and not @on_progress.nil?
|
||||
@on_progress.each{ |callback| callback.call(dltotal, dlnow, ultotal, ulnow) }
|
||||
end
|
||||
end
|
||||
|
||||
# Set on_body callback.
|
||||
#
|
||||
# @example Set on_body.
|
||||
# request.on_body { |chunk| p "yay" }
|
||||
#
|
||||
# @param [ Block ] block The block to execute.
|
||||
def on_body(&block)
|
||||
@on_body ||= []
|
||||
@on_body << block if block_given?
|
||||
@on_body
|
||||
end
|
||||
|
||||
# Execute on_body callbacks.
|
||||
#
|
||||
# @example Execute on_body.
|
||||
# request.body("This data came from HTTP.")
|
||||
#
|
||||
# @return [ Object ] If there are no on_body callbacks, returns the symbol :unyielded.
|
||||
def body(chunk)
|
||||
if defined?(@on_body) and not @on_body.nil?
|
||||
result = nil
|
||||
@on_body.each do |callback|
|
||||
result = callback.call(chunk, self)
|
||||
break if result == :abort
|
||||
end
|
||||
result
|
||||
else
|
||||
:unyielded
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
module Ethon
|
||||
class Easy # :nodoc:
|
||||
|
||||
# This module contains small helpers.
|
||||
#
|
||||
# @api private
|
||||
module Util
|
||||
|
||||
# Escapes zero bytes in strings.
|
||||
#
|
||||
# @example Escape zero bytes.
|
||||
# Util.escape_zero_byte("1\0")
|
||||
# #=> "1\\0"
|
||||
#
|
||||
# @param [ Object ] value The value to escape.
|
||||
#
|
||||
# @return [ String, Object ] Escaped String if
|
||||
# zero byte found, original object if not.
|
||||
def escape_zero_byte(value)
|
||||
return value unless value.to_s.include?(0.chr)
|
||||
value.to_s.gsub(0.chr, '\\\0')
|
||||
end
|
||||
|
||||
extend self
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user