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.
Files changed (42) hide show
  1. data/CHANGELOG.md +0 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE +20 -0
  4. data/README.md +85 -0
  5. data/Rakefile +39 -0
  6. data/lib/ethon.rb +18 -0
  7. data/lib/ethon/curl.rb +586 -0
  8. data/lib/ethon/easies/callbacks.rb +80 -0
  9. data/lib/ethon/easies/form.rb +131 -0
  10. data/lib/ethon/easies/header.rb +65 -0
  11. data/lib/ethon/easies/http.rb +43 -0
  12. data/lib/ethon/easies/http/actionable.rb +99 -0
  13. data/lib/ethon/easies/http/delete.rb +23 -0
  14. data/lib/ethon/easies/http/get.rb +22 -0
  15. data/lib/ethon/easies/http/head.rb +22 -0
  16. data/lib/ethon/easies/http/options.rb +22 -0
  17. data/lib/ethon/easies/http/patch.rb +22 -0
  18. data/lib/ethon/easies/http/post.rb +18 -0
  19. data/lib/ethon/easies/http/postable.rb +27 -0
  20. data/lib/ethon/easies/http/put.rb +19 -0
  21. data/lib/ethon/easies/http/putable.rb +22 -0
  22. data/lib/ethon/easies/informations.rb +83 -0
  23. data/lib/ethon/easies/operations.rb +31 -0
  24. data/lib/ethon/easies/options.rb +105 -0
  25. data/lib/ethon/easies/params.rb +54 -0
  26. data/lib/ethon/easies/response_callbacks.rb +25 -0
  27. data/lib/ethon/easies/util.rb +41 -0
  28. data/lib/ethon/easy.rb +104 -0
  29. data/lib/ethon/errors.rb +13 -0
  30. data/lib/ethon/errors/ethon_error.rb +8 -0
  31. data/lib/ethon/errors/multi_add.rb +12 -0
  32. data/lib/ethon/errors/multi_fdset.rb +12 -0
  33. data/lib/ethon/errors/multi_remove.rb +11 -0
  34. data/lib/ethon/errors/multi_timeout.rb +12 -0
  35. data/lib/ethon/errors/select.rb +12 -0
  36. data/lib/ethon/extensions.rb +1 -0
  37. data/lib/ethon/extensions/string.rb +11 -0
  38. data/lib/ethon/multi.rb +47 -0
  39. data/lib/ethon/multies/operations.rb +132 -0
  40. data/lib/ethon/multies/stack.rb +43 -0
  41. data/lib/ethon/version.rb +5 -0
  42. 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
@@ -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
@@ -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,8 @@
1
+ module Ethon
2
+ module Errors
3
+
4
+ # Default Ethon error class for all custom errors.
5
+ class EthonError < StandardError
6
+ end
7
+ end
8
+ 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,11 @@
1
+ module Ethon
2
+ module Errors
3
+
4
+ # Raises when multi_remove_handle failed.
5
+ class MultiRemove < EthonError
6
+ def initialize(code, easy)
7
+ super("An error occured removing the easy handle: #{easy} from the multi: #{code}")
8
+ end
9
+ end
10
+ end
11
+ 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,12 @@
1
+ module Ethon
2
+ module Errors
3
+
4
+ # Raised when select failed.
5
+ class Select < EthonError
6
+ def initialize(errno)
7
+ super("An error occured on select: #{errno}")
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1 @@
1
+ require 'ethon/extensions/string.rb' unless ''.respond_to?(:byteslice)
@@ -0,0 +1,11 @@
1
+ module Ethon
2
+ module Extensions
3
+ module String
4
+ def byteslice(*args)
5
+ self[*args]
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ ::String.__send__(:include, Ethon::Extensions::String)
@@ -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