ethon 0.0.1

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