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,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