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