ethon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|