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