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,80 @@
1
+ module Ethon
2
+ module Easies
3
+
4
+ # This module contains all the logic around the callbacks,
5
+ # which are needed to interact with libcurl.
6
+ module Callbacks
7
+
8
+ # Set writefunction and headerfunction callback.
9
+ # They are called by libcurl in order to provide the header and
10
+ # the body from the request.
11
+ #
12
+ # @example Set callbacks.
13
+ # easy.set_callbacks
14
+ def set_callbacks
15
+ Curl.set_option(:writefunction, body_write_callback, handle)
16
+ Curl.set_option(:headerfunction, header_write_callback, handle)
17
+ @response_body = ""
18
+ @response_header = ""
19
+ end
20
+
21
+ # Returns the body write callback.
22
+ #
23
+ # @example Return the callback.
24
+ # easy.body_write_callback
25
+ #
26
+ # @return [ Proc ] The callback.
27
+ def body_write_callback
28
+ @body_write_callback ||= proc {|stream, size, num, object|
29
+ @response_body << stream.read_string(size * num)
30
+ size * num
31
+ }
32
+ end
33
+
34
+ # Returns the header write callback.
35
+ #
36
+ # @example Return the callback.
37
+ # easy.header_write_callback
38
+ #
39
+ # @return [ Proc ] The callback.
40
+ def header_write_callback
41
+ @header_write_callback ||= proc {|stream, size, num, object|
42
+ @response_header << stream.read_string(size * num)
43
+ size * num
44
+ }
45
+ end
46
+
47
+ # Set the read callback. This callback is used by libcurl to
48
+ # read data when performing a PUT request.
49
+ #
50
+ # @example Set the callback.
51
+ # easy.set_read_callback("a=1")
52
+ #
53
+ # @param [ String ] body The body.
54
+ def set_read_callback(body)
55
+ @request_body_read = 0
56
+ @read_callback = proc {|stream, size, num, object|
57
+ size = size * num
58
+ left = body.bytesize - @request_body_read
59
+ size = left if size > left
60
+ if size > 0
61
+ stream.write_string(body.byteslice(@request_body_read, size), size)
62
+ @request_body_read += size
63
+ end
64
+ size
65
+ }
66
+ self.readfunction = read_callback
67
+ end
68
+
69
+ # Returns the body read callback.
70
+ #
71
+ # @example Return the callback.
72
+ # easy.read_callback
73
+ #
74
+ # @return [ Proc ] The callback.
75
+ def read_callback
76
+ @read_callback
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,131 @@
1
+ require 'ethon/easies/util'
2
+
3
+ module Ethon
4
+ module Easies
5
+
6
+ # This class represents a form and is used to send a payload in the
7
+ # request body via POST/PUT.
8
+ # It handles multipart forms, too.
9
+ class Form
10
+ include Ethon::Easies::Util
11
+ attr_accessor :escape
12
+
13
+ # Return a new Form.
14
+ #
15
+ # @example Return a new Form.
16
+ # Form.new({})
17
+ #
18
+ # @param [ Hash ] params The parameter to initialize the form with.
19
+ #
20
+ # @return [ Form ] A new Form.
21
+ def initialize(params)
22
+ @params = params || {}
23
+ ObjectSpace.define_finalizer(self, self.class.finalizer(self))
24
+ end
25
+
26
+ # Frees form in libcurl if necessary.
27
+ #
28
+ # @example Free the form
29
+ # Form.finalizer(form)
30
+ #
31
+ # @param [ Form ] form The form to free.
32
+ def self.finalizer(form)
33
+ proc { Curl.formfree(form.first) if form.multipart? }
34
+ end
35
+
36
+ # Return a pointer to the first form element in libcurl.
37
+ #
38
+ # @example Return the first form element.
39
+ # form.first
40
+ #
41
+ # @return [ FFI::Pointer ] The first element.
42
+ def first
43
+ @first ||= FFI::MemoryPointer.new(:pointer)
44
+ end
45
+
46
+ # Return a pointer to the last form element in libcurl.
47
+ #
48
+ # @example Return the last form element.
49
+ # form.last
50
+ #
51
+ # @return [ FFI::Pointer ] The last element.
52
+ def last
53
+ @last ||= FFI::MemoryPointer.new(:pointer)
54
+ end
55
+
56
+ # Return if form is multipart. The form is multipart,
57
+ # when it contains a file.
58
+ #
59
+ # @example Return if form is multipart.
60
+ # form.multipart?
61
+ #
62
+ # @return [ Boolean ] True if form is multipart, else false.
63
+ def multipart?
64
+ query_pairs.any?{|pair| pair.last.is_a?(Array)}
65
+ end
66
+
67
+ # Return the query pairs.
68
+ #
69
+ # @example Return the query pairs.
70
+ # form.query_pairs
71
+ #
72
+ # @return [ Array ] The query pairs.
73
+ def query_pairs
74
+ @query_pairs ||= build_query_pairs_from_hash(@params)
75
+ end
76
+
77
+ # Return the string representation of the form. This makes only
78
+ # sense when the form is not multipart.
79
+ #
80
+ # @example Return string representation.
81
+ # form.to_s
82
+ #
83
+ # @return [ String ] The string representation.
84
+ def to_s
85
+ query_pairs.map{|pair| pair.map{|e| escape ? CGI::escape(e.to_s) : e }.join("=")}.join('&')
86
+ end
87
+
88
+ # Return wether there are elements in the form or not.
89
+ #
90
+ # @example Return if form is empty.
91
+ # form.empty?
92
+ #
93
+ # @return [ Boolean ] True if form is empty, else false.
94
+ def empty?
95
+ @params.empty?
96
+ end
97
+
98
+ # Add form elements to libcurl.
99
+ #
100
+ # @example Add form to libcurl.
101
+ # form.materialize
102
+ def materialize
103
+ query_pairs.each { |pair| form_add(pair.first.to_s, pair.last) }
104
+ end
105
+
106
+ private
107
+
108
+ def form_add(name, content)
109
+ case content
110
+ when Array
111
+ Curl.formadd(first, last,
112
+ :form_option, :copyname, :pointer, name,
113
+ :form_option, :namelength, :long, name.bytesize,
114
+ :form_option, :file, :string, content[2],
115
+ :form_option, :filename, :string, content[0],
116
+ :form_option, :contenttype, :string, content[1],
117
+ :form_option, :end
118
+ )
119
+ else
120
+ Curl.formadd(first, last,
121
+ :form_option, :copyname, :pointer, name,
122
+ :form_option, :namelength, :long, name.bytesize,
123
+ :form_option, :copycontents, :pointer, content,
124
+ :form_option, :contentslength, :long, content.bytesize,
125
+ :form_option, :end
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,65 @@
1
+ module Ethon
2
+ module Easies
3
+
4
+ # This module contains the logic around adding headers to libcurl.
5
+ module Header
6
+
7
+ # Return headers, return empty hash if none.
8
+ #
9
+ # @example Return the headers.
10
+ # easy.headers
11
+ #
12
+ # @return [ Hash ] The headers.
13
+ def headers
14
+ @headers ||= {}
15
+ end
16
+
17
+ # Set the headers.
18
+ #
19
+ # @example Set the headers.
20
+ # easy.headers = {'User-Agent' => 'ethon'}
21
+ #
22
+ # @param [ Hash ] headers The headers.
23
+ def headers=(headers)
24
+ @headers = headers
25
+ end
26
+
27
+ # Return header_list.
28
+ #
29
+ # @example Return header_list.
30
+ # easy.header_list
31
+ #
32
+ # @return [ FFI::Pointer ] The header list.
33
+ def header_list
34
+ @header_list ||= nil
35
+ end
36
+
37
+ # Set previously defined headers in libcurl.
38
+ #
39
+ # @example Set headers in libcurl.
40
+ # easy.set_headers
41
+ #
42
+ # @return [ Symbol ] The return value from Curl.set_option.
43
+ def set_headers
44
+ @header_list = nil
45
+ headers.each {|k, v| @header_list = Curl.slist_append(@header_list, compose_header(k,v)) }
46
+ Curl.set_option(:httpheader, @header_list, handle)
47
+ end
48
+
49
+ # Compose libcurl header string from key and value.
50
+ # Also replaces null bytes, because libcurl will complain about
51
+ # otherwise.
52
+ #
53
+ # @example Compose header.
54
+ # easy.compose_header('User-Agent', 'Ethon')
55
+ #
56
+ # @param [ String ] key The header name.
57
+ # @param [ String ] value The header value.
58
+ #
59
+ # @return [ String ] The composed header.
60
+ def compose_header(key, value)
61
+ "#{key}:#{value.to_s.gsub(0.chr, '\\\0')}"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ require 'ethon/easies/http/actionable'
2
+ require 'ethon/easies/http/post'
3
+ require 'ethon/easies/http/get'
4
+ require 'ethon/easies/http/head'
5
+ require 'ethon/easies/http/put'
6
+ require 'ethon/easies/http/delete'
7
+ require 'ethon/easies/http/patch'
8
+ require 'ethon/easies/http/options'
9
+
10
+ module Ethon
11
+ module Easies
12
+
13
+ # This module contains logic about making valid http requests.
14
+ module Http
15
+
16
+ # Set specified options in order to make a http request.
17
+ #
18
+ # @example Set options for http request.
19
+ # easy.http_request("www.google.com", :get, {})
20
+ #
21
+ # @param [ String ] url The url.
22
+ # @param [ String ] action_name The http action name.
23
+ # @param [ Hash ] options The options hash.
24
+ def http_request(url, action_name, options = {})
25
+ fabricate(action_name).new(url, options).setup(self)
26
+ end
27
+
28
+ private
29
+
30
+ # Return the corresponding action class.
31
+ #
32
+ # @example Return the action.
33
+ # Action.fabricate(:get)
34
+ #
35
+ # @param [ String ] action_name The action name.
36
+ #
37
+ # @return [ Class ] The action class.
38
+ def fabricate(action_name)
39
+ eval("#{action_name.capitalize}")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,99 @@
1
+ require 'ethon/easies/http/putable'
2
+ require 'ethon/easies/http/postable'
3
+
4
+ module Ethon
5
+ module Easies
6
+ module Http
7
+ # This module represents a Http Action and is a factory
8
+ # for more real actions like GET, HEAD, POST and PUT.
9
+ module Actionable
10
+
11
+ def url
12
+ @url
13
+ end
14
+
15
+ def options
16
+ @options
17
+ end
18
+
19
+ def params
20
+ @params ||= Params.new(options[:params])
21
+ end
22
+
23
+ def form
24
+ @form ||= Form.new(options[:body])
25
+ end
26
+
27
+ # Create a new action.
28
+ #
29
+ # @example Create a new action.
30
+ # Action.new("www.example.com", {})
31
+ #
32
+ # @param [ String ] url The url.
33
+ # @param [ Hash ] options The options.
34
+ #
35
+ # @return [ Action ] A new action.
36
+ def initialize(url, options)
37
+ @url = url
38
+ @options = options
39
+ end
40
+
41
+ # Setup everything what is necessary for a proper
42
+ # request.
43
+ #
44
+ # @example setup.
45
+ # action.setup(easy)
46
+ #
47
+ # @param [ easy ] easy the easy to setup.
48
+ def setup(easy)
49
+ set_nothing(easy) if params.empty? && form.empty?
50
+ set_params(easy) unless params.empty?
51
+ set_form(easy) unless form.empty?
52
+ set_customs(easy)
53
+ easy.set_attributes(options)
54
+ end
55
+
56
+ # Setup request as if there were no params and form.
57
+ #
58
+ # @example Setup nothing.
59
+ # action.set_nothing(easy)
60
+ #
61
+ # @param [ Easy ] easy The easy to setup.
62
+ def set_nothing(easy)
63
+ easy.url = url
64
+ end
65
+
66
+ # Setup request as with params.
67
+ #
68
+ # @example Setup nothing.
69
+ # action.set_params(easy)
70
+ #
71
+ # @param [ Easy ] easy The easy to setup.
72
+ def set_params(easy)
73
+ params.escape = true
74
+ easy.url = "#{url}?#{params.to_s}"
75
+ end
76
+
77
+ # Setup request as with form.
78
+ #
79
+ # @example Setup nothing.
80
+ # action.set_form(easy)
81
+ #
82
+ # @param [ Easy ] easy The easy to setup.
83
+ def set_form(easy)
84
+ end
85
+
86
+ # Setup custom things eg. override the i
87
+ # action for delete.
88
+ #
89
+ # @example Setup custom things.
90
+ # action.set_customs(easy)
91
+ #
92
+ # @param [ Easy ] easy The easy to setup.
93
+ def set_customs(easy)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
@@ -0,0 +1,23 @@
1
+ module Ethon
2
+ module Easies
3
+ module Http
4
+
5
+ # This class knows everything about making DELETE requests.
6
+ class Delete
7
+ include Ethon::Easies::Http::Actionable
8
+ include Ethon::Easies::Http::Postable
9
+
10
+ # Setup customrequest in order to make a delete.
11
+ #
12
+ # @example Setup customrequest.
13
+ # delete.setup(easy)
14
+ #
15
+ # @param [ Easy ] easy The easy to setup.
16
+ def set_customs(easy)
17
+ easy.customrequest = "DELETE"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,22 @@
1
+ module Ethon
2
+ module Easies
3
+ module Http
4
+
5
+ # This class knows everything about making GET requests.
6
+ class Get
7
+ include Ethon::Easies::Http::Actionable
8
+ include Ethon::Easies::Http::Postable
9
+
10
+ # Setup url with escaped params and httpget.
11
+ #
12
+ # @example Setup.
13
+ # get.set_params(easy)
14
+ #
15
+ # @param [ Easy ] easy The easy to setup.
16
+ def set_customs(easy)
17
+ easy.customrequest = "GET"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end