ethon 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +0 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.md +85 -0
- data/Rakefile +39 -0
- data/lib/ethon.rb +18 -0
- data/lib/ethon/curl.rb +586 -0
- data/lib/ethon/easies/callbacks.rb +80 -0
- data/lib/ethon/easies/form.rb +131 -0
- data/lib/ethon/easies/header.rb +65 -0
- data/lib/ethon/easies/http.rb +43 -0
- data/lib/ethon/easies/http/actionable.rb +99 -0
- data/lib/ethon/easies/http/delete.rb +23 -0
- data/lib/ethon/easies/http/get.rb +22 -0
- data/lib/ethon/easies/http/head.rb +22 -0
- data/lib/ethon/easies/http/options.rb +22 -0
- data/lib/ethon/easies/http/patch.rb +22 -0
- data/lib/ethon/easies/http/post.rb +18 -0
- data/lib/ethon/easies/http/postable.rb +27 -0
- data/lib/ethon/easies/http/put.rb +19 -0
- data/lib/ethon/easies/http/putable.rb +22 -0
- data/lib/ethon/easies/informations.rb +83 -0
- data/lib/ethon/easies/operations.rb +31 -0
- data/lib/ethon/easies/options.rb +105 -0
- data/lib/ethon/easies/params.rb +54 -0
- data/lib/ethon/easies/response_callbacks.rb +25 -0
- data/lib/ethon/easies/util.rb +41 -0
- data/lib/ethon/easy.rb +104 -0
- data/lib/ethon/errors.rb +13 -0
- data/lib/ethon/errors/ethon_error.rb +8 -0
- data/lib/ethon/errors/multi_add.rb +12 -0
- data/lib/ethon/errors/multi_fdset.rb +12 -0
- data/lib/ethon/errors/multi_remove.rb +11 -0
- data/lib/ethon/errors/multi_timeout.rb +12 -0
- data/lib/ethon/errors/select.rb +12 -0
- data/lib/ethon/extensions.rb +1 -0
- data/lib/ethon/extensions/string.rb +11 -0
- data/lib/ethon/multi.rb +47 -0
- data/lib/ethon/multies/operations.rb +132 -0
- data/lib/ethon/multies/stack.rb +43 -0
- data/lib/ethon/version.rb +5 -0
- metadata +217 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module Ethon
|
2
|
+
module Easies # :nodoc:
|
3
|
+
|
4
|
+
# This module contains small helpers.
|
5
|
+
module Util
|
6
|
+
|
7
|
+
# Return query pairs from hash.
|
8
|
+
def build_query_pairs_from_hash(hash)
|
9
|
+
pairs = []
|
10
|
+
recursive = Proc.new do |h, prefix|
|
11
|
+
h.each_pair do |k,v|
|
12
|
+
key = prefix == '' ? k : "#{prefix}[#{k}]"
|
13
|
+
case v
|
14
|
+
when Hash
|
15
|
+
recursive.call(v, key)
|
16
|
+
when Array
|
17
|
+
v.each { |x| pairs << [key, x] }
|
18
|
+
when File, Tempfile
|
19
|
+
pairs << [key, file_info(v)]
|
20
|
+
else
|
21
|
+
pairs << [key, v]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
recursive.call(hash, '')
|
26
|
+
pairs
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return file info for a file.
|
30
|
+
def file_info(file)
|
31
|
+
filename = File.basename(file.path)
|
32
|
+
types = MIME::Types.type_for(filename)
|
33
|
+
[
|
34
|
+
filename,
|
35
|
+
types.empty? ? 'application/octet-stream' : types[0].to_s,
|
36
|
+
File.expand_path(file.path)
|
37
|
+
]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/ethon/easy.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'ethon/easies/informations'
|
2
|
+
require 'ethon/easies/callbacks'
|
3
|
+
require 'ethon/easies/options'
|
4
|
+
require 'ethon/easies/header'
|
5
|
+
require 'ethon/easies/util'
|
6
|
+
require 'ethon/easies/params'
|
7
|
+
require 'ethon/easies/form'
|
8
|
+
require 'ethon/easies/http'
|
9
|
+
require 'ethon/easies/operations'
|
10
|
+
require 'ethon/easies/response_callbacks'
|
11
|
+
|
12
|
+
module Ethon
|
13
|
+
|
14
|
+
# This is the class representing the libcurl easy interface
|
15
|
+
# See http://curl.haxx.se/libcurl/c/libcurl-easy.html for more informations.
|
16
|
+
class Easy
|
17
|
+
include Ethon::Easies::Informations
|
18
|
+
include Ethon::Easies::Callbacks
|
19
|
+
include Ethon::Easies::Options
|
20
|
+
include Ethon::Easies::Header
|
21
|
+
include Ethon::Easies::Http
|
22
|
+
include Ethon::Easies::Operations
|
23
|
+
include Ethon::Easies::ResponseCallbacks
|
24
|
+
|
25
|
+
attr_reader :response_body, :response_header
|
26
|
+
attr_accessor :return_code
|
27
|
+
|
28
|
+
class << self
|
29
|
+
|
30
|
+
# Free libcurl representation from an easy handle.
|
31
|
+
#
|
32
|
+
# @example Free easy handle.
|
33
|
+
# Easy.finalizer(easy)
|
34
|
+
#
|
35
|
+
# @param [ Easy ] easy The easy to free.
|
36
|
+
def finalizer(easy)
|
37
|
+
proc {
|
38
|
+
Curl.slist_free_all(easy.header_list) if easy.header_list
|
39
|
+
Curl.easy_cleanup(easy.handle)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Initialize a new Easy.
|
45
|
+
# It initializes curl, if not already done and applies the provided options.
|
46
|
+
#
|
47
|
+
# @example Create a new Easy.
|
48
|
+
# Easy.new(:url => "www.google.de")
|
49
|
+
#
|
50
|
+
# @param [ Hash ] options The options to set.
|
51
|
+
#
|
52
|
+
# @return [ Easy ] A new Easy.
|
53
|
+
def initialize(options = {})
|
54
|
+
Curl.init
|
55
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(self))
|
56
|
+
set_attributes(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set given options.
|
60
|
+
#
|
61
|
+
# @example Set options.
|
62
|
+
# easy.set_attributes(options)
|
63
|
+
#
|
64
|
+
# @param [ Hash ] options The options.
|
65
|
+
def set_attributes(options)
|
66
|
+
options.each_pair do |key, value|
|
67
|
+
method("#{key}=").call(value) if respond_to?("#{key}=")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Reset easy. This means resetting all options and instance variables.
|
72
|
+
# Also the easy handle is resetted.
|
73
|
+
#
|
74
|
+
# @example Reset.
|
75
|
+
# easy.reset
|
76
|
+
def reset
|
77
|
+
(instance_variables - [:@handle, :@header_list]).each do |ivar|
|
78
|
+
instance_variable_set(ivar, nil)
|
79
|
+
end
|
80
|
+
Curl.easy_reset(handle)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a pointer to the curl easy handle.
|
84
|
+
#
|
85
|
+
# @example Return the handle.
|
86
|
+
# easy.handle
|
87
|
+
#
|
88
|
+
# @return [ FFI::Pointer ] A pointer to the curl easy handle.
|
89
|
+
def handle
|
90
|
+
@handle ||= Curl.easy_init
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_hash
|
94
|
+
hash = {}
|
95
|
+
hash[:return_code] = return_code
|
96
|
+
hash[:response_header] = response_header
|
97
|
+
hash[:response_body] = response_body
|
98
|
+
Easies::Informations::AVAILABLE_INFORMATIONS.keys.each do |info|
|
99
|
+
hash[info] = method(info).call
|
100
|
+
end
|
101
|
+
hash
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/ethon/errors.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ethon/errors/ethon_error'
|
2
|
+
require 'ethon/errors/multi_timeout'
|
3
|
+
require 'ethon/errors/multi_fdset'
|
4
|
+
require 'ethon/errors/multi_add'
|
5
|
+
require 'ethon/errors/multi_remove'
|
6
|
+
require 'ethon/errors/select'
|
7
|
+
|
8
|
+
module Ethon
|
9
|
+
|
10
|
+
# This namespace contains all errors raised by ethon.
|
11
|
+
module Errors
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Ethon
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
# Raises when multi_add_handle failed.
|
5
|
+
class MultiAdd < EthonError
|
6
|
+
def initialize(code, easy)
|
7
|
+
super("An error occured adding the easy handle: #{easy} to the multi: #{code}")
|
8
|
+
# "an error occured getting the fdset: #{code}: #{Curl.multi_strerror(code)}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Ethon
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
# Raises when multi_fdset failed.
|
5
|
+
class MultiFdset < EthonError
|
6
|
+
def initialize(code)
|
7
|
+
super("An error occured getting the fdset: #{code}")
|
8
|
+
# "an error occured getting the fdset: #{code}: #{Curl.multi_strerror(code)}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Ethon
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
# Raised when multi_timeout failed.
|
5
|
+
class MultiTimeout < EthonError
|
6
|
+
def initialize(code)
|
7
|
+
super("An error occured getting the timeout: #{code}")
|
8
|
+
# "An error occured getting the timeout: #{code}: #{Curl.multi_strerror(code)}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'ethon/extensions/string.rb' unless ''.respond_to?(:byteslice)
|
data/lib/ethon/multi.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'ethon/multies/stack'
|
2
|
+
require 'ethon/multies/operations'
|
3
|
+
|
4
|
+
module Ethon
|
5
|
+
|
6
|
+
# This class represents libcurl multi.
|
7
|
+
class Multi
|
8
|
+
include Ethon::Multies::Stack
|
9
|
+
include Ethon::Multies::Operations
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Frees the libcurl multi handle.
|
14
|
+
#
|
15
|
+
# @example Free multi.
|
16
|
+
# Multi.finalizer(multi)
|
17
|
+
#
|
18
|
+
# @param [ Multi ] multi The multi to free.
|
19
|
+
def finalizer(multi)
|
20
|
+
proc {
|
21
|
+
Curl.multi_cleanup(multi.handle)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create a new multi. Initialize curl in case
|
27
|
+
# it didn't happen before.
|
28
|
+
#
|
29
|
+
# @return [ Multi ] The new multi.
|
30
|
+
def initialize
|
31
|
+
Curl.init
|
32
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(self))
|
33
|
+
init_vars
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the multi handle. Inititialize multi handle,
|
37
|
+
# in case it didn't happened already.
|
38
|
+
#
|
39
|
+
# @example Return multi handle.
|
40
|
+
# multi.handle
|
41
|
+
#
|
42
|
+
# @return [ ::FFI::Pointer ] The multi handle.
|
43
|
+
def handle
|
44
|
+
@handle ||= Curl.multi_init
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Ethon
|
2
|
+
module Multies # :nodoc
|
3
|
+
|
4
|
+
# This module contains logic to run a multi.
|
5
|
+
module Operations
|
6
|
+
|
7
|
+
# Initialize variables.
|
8
|
+
#
|
9
|
+
# @example Initialize variables.
|
10
|
+
# multi.init_vars
|
11
|
+
def init_vars
|
12
|
+
@timeout = ::FFI::MemoryPointer.new(:long)
|
13
|
+
@timeval = Curl::Timeval.new
|
14
|
+
@fd_read = Curl::FDSet.new
|
15
|
+
@fd_write = Curl::FDSet.new
|
16
|
+
@fd_excep = Curl::FDSet.new
|
17
|
+
@max_fd = ::FFI::MemoryPointer.new(:int)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return wether the multi still requests or not.
|
21
|
+
#
|
22
|
+
# @example Return if ongoing.
|
23
|
+
# multi.ongoing?
|
24
|
+
#
|
25
|
+
# @return [ Boolean ] True if ongoing, else false.
|
26
|
+
def ongoing?
|
27
|
+
easy_handles.size > 0 || (!defined?(@running_count) || running_count > 0)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Perform multi.
|
31
|
+
#
|
32
|
+
# @example Perform multi.
|
33
|
+
# multi.perform
|
34
|
+
def perform
|
35
|
+
while ongoing?
|
36
|
+
run
|
37
|
+
timeout = get_timeout
|
38
|
+
next if timeout == 0
|
39
|
+
reset_fds
|
40
|
+
set_fds(timeout)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get timeout.
|
45
|
+
#
|
46
|
+
# @example Get timeout.
|
47
|
+
# multi.get_timeout
|
48
|
+
def get_timeout
|
49
|
+
code = Curl.multi_timeout(handle, @timeout)
|
50
|
+
raise Errors::MultiTimeout.new(code) unless code == :ok
|
51
|
+
timeout = @timeout.read_long
|
52
|
+
timeout = 1 if timeout < 0
|
53
|
+
timeout
|
54
|
+
end
|
55
|
+
|
56
|
+
# Reset file describtors.
|
57
|
+
#
|
58
|
+
# @example Reset fds.
|
59
|
+
# multi.reset_fds
|
60
|
+
def reset_fds
|
61
|
+
@fd_read.clear
|
62
|
+
@fd_write.clear
|
63
|
+
@fd_excep.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set fds.
|
67
|
+
#
|
68
|
+
# @example Set fds.
|
69
|
+
# multi.set_fds
|
70
|
+
def set_fds(timeout)
|
71
|
+
code = Curl.multi_fdset(handle, @fd_read, @fd_write, @fd_excep, @max_fd)
|
72
|
+
raise Errors::MultiFdset.new(code) unless code == :ok
|
73
|
+
max_fd = @max_fd.read_int
|
74
|
+
if max_fd == -1
|
75
|
+
sleep(0.001)
|
76
|
+
else
|
77
|
+
@timeval[:sec] = timeout / 1000
|
78
|
+
@timeval[:usec] = (timeout * 1000) % 1000000
|
79
|
+
code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval)
|
80
|
+
raise Errors::Select.new(::FFI.errno) if code < 0
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check.
|
85
|
+
#
|
86
|
+
# @example Check.
|
87
|
+
# multi.check
|
88
|
+
def check
|
89
|
+
msgs_left = ::FFI::MemoryPointer.new(:int)
|
90
|
+
while true
|
91
|
+
msg = Curl.multi_info_read(handle, msgs_left)
|
92
|
+
break if msg.null?
|
93
|
+
next if msg[:code] != :done
|
94
|
+
easy = easy_handles.find{ |e| e.handle == msg[:easy_handle] }
|
95
|
+
easy.return_code = msg[:data][:code]
|
96
|
+
delete(easy)
|
97
|
+
easy.complete
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Run.
|
102
|
+
#
|
103
|
+
# @example Run
|
104
|
+
# multi.run
|
105
|
+
def run
|
106
|
+
begin code = trigger end while code == :call_multi_perform
|
107
|
+
check
|
108
|
+
end
|
109
|
+
|
110
|
+
# Trigger.
|
111
|
+
#
|
112
|
+
# @example Trigger.
|
113
|
+
# multi.trigger
|
114
|
+
def trigger
|
115
|
+
running_count = FFI::MemoryPointer.new(:int)
|
116
|
+
code = Curl.multi_perform(handle, running_count)
|
117
|
+
@running_count = running_count.read_int
|
118
|
+
code
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return number of running requests.
|
122
|
+
#
|
123
|
+
# @example Return count.
|
124
|
+
# multi.running_count
|
125
|
+
#
|
126
|
+
# @return [ Integer ] Number running requests.
|
127
|
+
def running_count
|
128
|
+
@running_count ||= nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Ethon
|
2
|
+
module Multies
|
3
|
+
|
4
|
+
# This module provides the multi stack behaviour.
|
5
|
+
module Stack
|
6
|
+
|
7
|
+
# Return easy handles.
|
8
|
+
#
|
9
|
+
# @example Return easy handles.
|
10
|
+
# multi.easy_handles
|
11
|
+
#
|
12
|
+
# @return [ Array ] The easy handles.
|
13
|
+
def easy_handles
|
14
|
+
@easy_handles ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add an easy to the stack.
|
18
|
+
#
|
19
|
+
# @example Add easy.
|
20
|
+
# multi.add(easy)
|
21
|
+
#
|
22
|
+
# @param [ Easy ] easy The easy to add.
|
23
|
+
def add(easy)
|
24
|
+
return nil if easy_handles.include?(easy)
|
25
|
+
code = Curl.multi_add_handle(handle, easy.handle)
|
26
|
+
raise Errors::MultiAdd.new(code, easy) unless code == :ok
|
27
|
+
easy_handles << easy
|
28
|
+
end
|
29
|
+
|
30
|
+
# Delete an easy from stack.
|
31
|
+
#
|
32
|
+
# @example Delete easy from stack.
|
33
|
+
#
|
34
|
+
# @param [ Easy ] easy The easy to delete.
|
35
|
+
def delete(easy)
|
36
|
+
if easy_handles.delete(easy)
|
37
|
+
code = Curl.multi_remove_handle(handle, easy.handle)
|
38
|
+
raise Errors::MultiRemove.new(code, handle) unless code == :ok
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|