faraday 0.7.6 → 0.8.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -4
- data/README.md +148 -131
- data/Rakefile +12 -60
- data/faraday.gemspec +11 -32
- data/lib/faraday.rb +59 -12
- data/lib/faraday/adapter.rb +17 -1
- data/lib/faraday/adapter/em_http.rb +204 -0
- data/lib/faraday/adapter/em_synchrony.rb +66 -16
- data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
- data/lib/faraday/adapter/net_http.rb +1 -1
- data/lib/faraday/adapter/test.rb +13 -6
- data/lib/faraday/adapter/typhoeus.rb +64 -29
- data/lib/faraday/builder.rb +12 -17
- data/lib/faraday/connection.rb +131 -102
- data/lib/faraday/middleware.rb +17 -9
- data/lib/faraday/request.rb +40 -25
- data/lib/faraday/request/basic_authentication.rb +17 -0
- data/lib/faraday/request/retry.rb +21 -0
- data/lib/faraday/request/token_authentication.rb +21 -0
- data/lib/faraday/response.rb +2 -1
- data/lib/faraday/utils.rb +122 -18
- data/test/adapters/live_test.rb +35 -30
- data/test/adapters/logger_test.rb +2 -2
- data/test/adapters/typhoeus_test.rb +1 -1
- data/test/authentication_middleware_test.rb +48 -0
- data/test/connection_test.rb +171 -83
- data/test/env_test.rb +28 -3
- data/test/helper.rb +1 -1
- data/test/middleware/retry_test.rb +25 -0
- data/test/middleware_stack_test.rb +56 -2
- data/test/request_middleware_test.rb +2 -47
- data/test/response_middleware_test.rb +4 -4
- metadata +25 -39
- data/lib/faraday/request/json.rb +0 -35
data/faraday.gemspec
CHANGED
@@ -1,47 +1,22 @@
|
|
1
|
-
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
-
## all of the comments. Some sections require modification, and others can
|
3
|
-
## be deleted if you don't need them. Once you understand the contents of
|
4
|
-
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
-
## You can find comprehensive Gem::Specification documentation, at
|
6
|
-
## http://docs.rubygems.org/read/chapter/20
|
7
1
|
Gem::Specification.new do |s|
|
8
2
|
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version=
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
## the sub! line in the Rakefile
|
14
|
-
s.name = 'faraday'
|
15
|
-
s.version = '0.7.6'
|
16
|
-
s.date = '2012-01-21'
|
17
|
-
s.rubyforge_project = 'faraday'
|
5
|
+
s.name = 'faraday'
|
6
|
+
s.version = '0.8.0.rc2'
|
18
7
|
|
19
|
-
## Make sure your summary is short. The description may be as long
|
20
|
-
## as you like.
|
21
8
|
s.summary = "HTTP/REST API client library."
|
22
|
-
s.description
|
9
|
+
# TODO: s.description
|
23
10
|
|
24
|
-
## List the primary authors. If there are a bunch of authors, it's probably
|
25
|
-
## better to set the email to an email list or something. If you don't have
|
26
|
-
## a custom homepage, consider using your GitHub URL or the like.
|
27
11
|
s.authors = ["Rick Olson"]
|
28
12
|
s.email = 'technoweenie@gmail.com'
|
29
|
-
s.homepage = '
|
13
|
+
s.homepage = 'https://github.com/technoweenie/faraday'
|
30
14
|
|
31
|
-
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
32
|
-
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
33
|
-
s.require_paths = %w[lib]
|
34
|
-
|
35
|
-
s.add_dependency 'addressable', '~> 2.2'
|
36
15
|
s.add_dependency 'multipart-post', '~> 1.1'
|
37
|
-
s.add_dependency 'rack', '~> 1.1'
|
38
16
|
s.add_development_dependency 'rake'
|
39
17
|
s.add_development_dependency 'test-unit'
|
40
18
|
s.add_development_dependency 'webmock'
|
41
19
|
|
42
|
-
## Leave this section as-is. It will be automatically generated from the
|
43
|
-
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
44
|
-
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
45
20
|
# = MANIFEST =
|
46
21
|
s.files = %w[
|
47
22
|
Gemfile
|
@@ -53,7 +28,9 @@ Gem::Specification.new do |s|
|
|
53
28
|
lib/faraday.rb
|
54
29
|
lib/faraday/adapter.rb
|
55
30
|
lib/faraday/adapter/action_dispatch.rb
|
31
|
+
lib/faraday/adapter/em_http.rb
|
56
32
|
lib/faraday/adapter/em_synchrony.rb
|
33
|
+
lib/faraday/adapter/em_synchrony/parallel_manager.rb
|
57
34
|
lib/faraday/adapter/excon.rb
|
58
35
|
lib/faraday/adapter/net_http.rb
|
59
36
|
lib/faraday/adapter/patron.rb
|
@@ -64,8 +41,10 @@ Gem::Specification.new do |s|
|
|
64
41
|
lib/faraday/error.rb
|
65
42
|
lib/faraday/middleware.rb
|
66
43
|
lib/faraday/request.rb
|
67
|
-
lib/faraday/request/
|
44
|
+
lib/faraday/request/basic_authentication.rb
|
68
45
|
lib/faraday/request/multipart.rb
|
46
|
+
lib/faraday/request/retry.rb
|
47
|
+
lib/faraday/request/token_authentication.rb
|
69
48
|
lib/faraday/request/url_encoded.rb
|
70
49
|
lib/faraday/response.rb
|
71
50
|
lib/faraday/response/logger.rb
|
@@ -77,17 +56,17 @@ Gem::Specification.new do |s|
|
|
77
56
|
test/adapters/net_http_test.rb
|
78
57
|
test/adapters/test_middleware_test.rb
|
79
58
|
test/adapters/typhoeus_test.rb
|
59
|
+
test/authentication_middleware_test.rb
|
80
60
|
test/connection_test.rb
|
81
61
|
test/env_test.rb
|
82
62
|
test/helper.rb
|
83
63
|
test/live_server.rb
|
64
|
+
test/middleware/retry_test.rb
|
84
65
|
test/middleware_stack_test.rb
|
85
66
|
test/request_middleware_test.rb
|
86
67
|
test/response_middleware_test.rb
|
87
68
|
]
|
88
69
|
# = MANIFEST =
|
89
70
|
|
90
|
-
## Test files will be grabbed from the file list. Make sure the path glob
|
91
|
-
## matches what you actually use.
|
92
71
|
s.test_files = s.files.select { |path| path =~ %r{^test/*/.+\.rb} }
|
93
72
|
end
|
data/lib/faraday.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Faraday
|
2
|
-
VERSION = "0.
|
2
|
+
VERSION = "0.8.0.rc2"
|
3
3
|
|
4
4
|
class << self
|
5
|
+
attr_accessor :root_path, :lib_path
|
5
6
|
attr_accessor :default_adapter
|
6
7
|
attr_writer :default_connection
|
7
8
|
|
@@ -10,29 +11,54 @@ module Faraday
|
|
10
11
|
Faraday::Connection.new(url, options, &block)
|
11
12
|
end
|
12
13
|
|
14
|
+
def require_libs(*libs)
|
15
|
+
libs.each do |lib|
|
16
|
+
require "#{lib_path}/#{lib}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
alias require_lib require_libs
|
21
|
+
|
13
22
|
private
|
14
23
|
def method_missing(name, *args, &block)
|
15
24
|
default_connection.send(name, *args, &block)
|
16
25
|
end
|
17
26
|
end
|
18
27
|
|
28
|
+
self.root_path = File.expand_path "..", __FILE__
|
29
|
+
self.lib_path = File.expand_path "../faraday", __FILE__
|
19
30
|
self.default_adapter = :net_http
|
20
31
|
|
21
32
|
def self.default_connection
|
22
33
|
@default_connection ||= Connection.new
|
23
34
|
end
|
24
35
|
|
25
|
-
module
|
26
|
-
|
27
|
-
|
36
|
+
module MiddlewareRegistry
|
37
|
+
# Internal: Register middleware class(es) on the current module.
|
38
|
+
#
|
39
|
+
# mapping - A Hash mapping Symbol keys to classes. See
|
40
|
+
# Faraday.register_middleware for more details.
|
41
|
+
def register_middleware(mapping)
|
42
|
+
(@registered_middleware ||= {}).update(mapping)
|
28
43
|
end
|
29
44
|
|
30
|
-
|
31
|
-
|
32
|
-
|
45
|
+
# Internal: Lookup middleware class with a registered Symbol shortcut.
|
46
|
+
#
|
47
|
+
# Returns a middleware Class.
|
48
|
+
def lookup_middleware(key)
|
49
|
+
unless defined? @registered_middleware and found = @registered_middleware[key]
|
50
|
+
raise "#{key.inspect} is not registered on #{self}"
|
51
|
+
end
|
52
|
+
found = @registered_middleware[key] = found.call if found.is_a? Proc
|
53
|
+
found.is_a?(Module) ? found : const_get(found)
|
33
54
|
end
|
55
|
+
end
|
34
56
|
|
57
|
+
module AutoloadHelper
|
35
58
|
def autoload_all(prefix, options)
|
59
|
+
if prefix =~ /^faraday(\/|$)/i
|
60
|
+
prefix = File.join(Faraday.root_path, prefix)
|
61
|
+
end
|
36
62
|
options.each do |const_name, path|
|
37
63
|
autoload const_name, File.join(prefix, path)
|
38
64
|
end
|
@@ -54,7 +80,30 @@ module Faraday
|
|
54
80
|
|
55
81
|
extend AutoloadHelper
|
56
82
|
|
57
|
-
|
83
|
+
# Public: register middleware classes under a short name.
|
84
|
+
#
|
85
|
+
# type - A Symbol specifying the kind of middleware (default: :middleware)
|
86
|
+
# mapping - A Hash mapping Symbol keys to classes. Classes can be expressed
|
87
|
+
# as fully qualified constant, or a Proc that will be lazily called
|
88
|
+
# to return the former.
|
89
|
+
#
|
90
|
+
# Examples
|
91
|
+
#
|
92
|
+
# Faraday.register_middleware :aloha => MyModule::Aloha
|
93
|
+
# Faraday.register_middleware :response, :boom => MyModule::Boom
|
94
|
+
#
|
95
|
+
# # shortcuts are now available in Builder:
|
96
|
+
# builder.use :aloha
|
97
|
+
# builder.response :boom
|
98
|
+
#
|
99
|
+
# Returns nothing.
|
100
|
+
def self.register_middleware type, mapping = nil
|
101
|
+
type, mapping = :middleware, type if mapping.nil?
|
102
|
+
component = self.const_get(type.to_s.capitalize)
|
103
|
+
component.register_middleware(mapping)
|
104
|
+
end
|
105
|
+
|
106
|
+
autoload_all "faraday",
|
58
107
|
:Middleware => 'middleware',
|
59
108
|
:Builder => 'builder',
|
60
109
|
:Request => 'request',
|
@@ -62,12 +111,10 @@ module Faraday
|
|
62
111
|
:CompositeReadIO => 'upload_io',
|
63
112
|
:UploadIO => 'upload_io',
|
64
113
|
:Parts => 'upload_io'
|
114
|
+
|
115
|
+
require_libs "utils", "connection", "adapter", "error"
|
65
116
|
end
|
66
117
|
|
67
|
-
require 'faraday/utils'
|
68
|
-
require 'faraday/connection'
|
69
|
-
require 'faraday/adapter'
|
70
|
-
require 'faraday/error'
|
71
118
|
|
72
119
|
# not pulling in active-support JUST for this method.
|
73
120
|
class Object
|
data/lib/faraday/adapter.rb
CHANGED
@@ -3,25 +3,41 @@ module Faraday
|
|
3
3
|
CONTENT_LENGTH = 'Content-Length'.freeze
|
4
4
|
|
5
5
|
extend AutoloadHelper
|
6
|
+
extend MiddlewareRegistry
|
6
7
|
|
7
8
|
autoload_all 'faraday/adapter',
|
8
9
|
:ActionDispatch => 'action_dispatch',
|
9
10
|
:NetHttp => 'net_http',
|
10
11
|
:Typhoeus => 'typhoeus',
|
11
12
|
:EMSynchrony => 'em_synchrony',
|
13
|
+
:EMHttp => 'em_http',
|
12
14
|
:Patron => 'patron',
|
13
15
|
:Excon => 'excon',
|
14
16
|
:Test => 'test'
|
15
17
|
|
16
|
-
|
18
|
+
register_middleware \
|
17
19
|
:action_dispatch => :ActionDispatch,
|
18
20
|
:test => :Test,
|
19
21
|
:net_http => :NetHttp,
|
20
22
|
:typhoeus => :Typhoeus,
|
21
23
|
:patron => :Patron,
|
22
24
|
:em_synchrony => :EMSynchrony,
|
25
|
+
:em_http => :EMHttp,
|
23
26
|
:excon => :Excon
|
24
27
|
|
28
|
+
module Parallelism
|
29
|
+
attr_writer :supports_parallel
|
30
|
+
def supports_parallel?() @supports_parallel end
|
31
|
+
|
32
|
+
def inherited(subclass)
|
33
|
+
super
|
34
|
+
subclass.supports_parallel = self.supports_parallel?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
extend Parallelism
|
39
|
+
self.supports_parallel = false
|
40
|
+
|
25
41
|
def call(env)
|
26
42
|
if !env[:body] and Connection::METHODS_WITH_BODIES.include? env[:method]
|
27
43
|
# play nice and indicate we're sending an empty body
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Faraday
|
2
|
+
class Adapter
|
3
|
+
# EventMachine adapter is useful for either asynchronous requests
|
4
|
+
# when in EM reactor loop or for making parallel requests in
|
5
|
+
# synchronous code.
|
6
|
+
class EMHttp < Faraday::Adapter
|
7
|
+
|
8
|
+
dependency 'em-http'
|
9
|
+
|
10
|
+
self.supports_parallel = true
|
11
|
+
|
12
|
+
def self.setup_parallel_manager(options = nil)
|
13
|
+
Manager.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
super
|
18
|
+
perform_request env
|
19
|
+
@app.call env
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform_request(env)
|
23
|
+
if parallel?(env)
|
24
|
+
manager = env[:parallel_manager]
|
25
|
+
manager.add {
|
26
|
+
perform_single_request(env).
|
27
|
+
callback { env[:response].finish(env) }
|
28
|
+
}
|
29
|
+
else
|
30
|
+
unless EventMachine.reactor_running?
|
31
|
+
error = nil
|
32
|
+
# start EM, block until request is completed
|
33
|
+
EventMachine.run do
|
34
|
+
perform_single_request(env).
|
35
|
+
callback { EventMachine.stop }.
|
36
|
+
errback { |client|
|
37
|
+
error = error_message(client)
|
38
|
+
EventMachine.stop
|
39
|
+
}
|
40
|
+
end
|
41
|
+
raise_error(error) if error
|
42
|
+
else
|
43
|
+
# EM is running: instruct upstream that this is an async request
|
44
|
+
env[:parallel_manager] = true
|
45
|
+
perform_single_request(env).
|
46
|
+
callback { env[:response].finish(env) }.
|
47
|
+
errback {
|
48
|
+
# TODO: no way to communicate the error in async mode
|
49
|
+
raise NotImplementedError
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: reuse the connection to support pipelining
|
56
|
+
def perform_single_request(env)
|
57
|
+
req = EventMachine::HttpRequest.new(env[:url], connection_config(env))
|
58
|
+
req.setup_request(env[:method], request_config(env)).callback { |client|
|
59
|
+
save_response(env, client.response_header.status, client.response) do |resp_headers|
|
60
|
+
client.response_header.each do |name, value|
|
61
|
+
resp_headers[name.to_sym] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def error_message(client)
|
68
|
+
client.error or "request failed"
|
69
|
+
end
|
70
|
+
|
71
|
+
def raise_error(msg)
|
72
|
+
errklass = Faraday::Error::ClientError
|
73
|
+
if msg == Errno::ETIMEDOUT
|
74
|
+
errklass = Faraday::Error::TimeoutError
|
75
|
+
msg = "request timed out"
|
76
|
+
elsif msg == Errno::ECONNREFUSED
|
77
|
+
errklass = Faraday::Error::ConnectionFailed
|
78
|
+
msg = "connection refused"
|
79
|
+
end
|
80
|
+
raise errklass, msg
|
81
|
+
end
|
82
|
+
|
83
|
+
def connection_config(env)
|
84
|
+
options = {}
|
85
|
+
configure_ssl(options, env)
|
86
|
+
configure_proxy(options, env)
|
87
|
+
configure_timeout(options, env)
|
88
|
+
options
|
89
|
+
end
|
90
|
+
|
91
|
+
def request_config(env)
|
92
|
+
options = {
|
93
|
+
:body => read_body(env),
|
94
|
+
:head => env[:request_headers],
|
95
|
+
# :keepalive => true,
|
96
|
+
# :file => 'path/to/file', # stream data off disk
|
97
|
+
}
|
98
|
+
configure_compression(options, env)
|
99
|
+
# configure_proxy_auth
|
100
|
+
# :proxy => {:authorization => [user, pass]}
|
101
|
+
# proxy[:username] && proxy[:password]
|
102
|
+
options
|
103
|
+
end
|
104
|
+
|
105
|
+
def read_body(env)
|
106
|
+
body = env[:body]
|
107
|
+
body.respond_to?(:read) ? body.read : body
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure_ssl(options, env)
|
111
|
+
if ssl = env[:ssl]
|
112
|
+
# :ssl => {
|
113
|
+
# :private_key_file => '/tmp/server.key',
|
114
|
+
# :cert_chain_file => '/tmp/server.crt',
|
115
|
+
# :verify_peer => false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def configure_proxy(options, env)
|
120
|
+
if proxy = request_options(env)[:proxy]
|
121
|
+
options[:proxy] = {
|
122
|
+
:host => proxy[:uri].host,
|
123
|
+
:port => proxy[:uri].port
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def configure_timeout(options, env)
|
129
|
+
timeout, open_timeout = request_options(env).values_at(:timeout, :open_timeout)
|
130
|
+
options[:connect_timeout] = options[:inactivity_timeout] = timeout
|
131
|
+
options[:connect_timeout] = open_timeout if open_timeout
|
132
|
+
end
|
133
|
+
|
134
|
+
def configure_compression(options, env)
|
135
|
+
if env[:method] == :get and not options[:head].key? 'accept-encoding'
|
136
|
+
options[:head]['accept-encoding'] = 'gzip, compressed'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def request_options(env)
|
141
|
+
env[:request]
|
142
|
+
end
|
143
|
+
|
144
|
+
def parallel?(env)
|
145
|
+
!!env[:parallel_manager]
|
146
|
+
end
|
147
|
+
|
148
|
+
# The parallel manager is designed to start an EventMachine loop
|
149
|
+
# and block until all registered requests have been completed.
|
150
|
+
class Manager
|
151
|
+
def initialize
|
152
|
+
reset
|
153
|
+
end
|
154
|
+
|
155
|
+
def reset
|
156
|
+
@registered_procs = []
|
157
|
+
@num_registered = 0
|
158
|
+
@num_succeeded = 0
|
159
|
+
@errors = []
|
160
|
+
@running = false
|
161
|
+
end
|
162
|
+
|
163
|
+
def running?() @running end
|
164
|
+
|
165
|
+
def add
|
166
|
+
if running?
|
167
|
+
perform_request { yield }
|
168
|
+
else
|
169
|
+
@registered_procs << Proc.new
|
170
|
+
end
|
171
|
+
@num_registered += 1
|
172
|
+
end
|
173
|
+
|
174
|
+
def run
|
175
|
+
if @num_registered > 0
|
176
|
+
@running = true
|
177
|
+
EventMachine.run do
|
178
|
+
@registered_procs.each do |proc|
|
179
|
+
perform_request(&proc)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
if @errors.size > 0
|
183
|
+
raise Faraday::Error::ClientError, @errors.first || "connection failed"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
ensure
|
187
|
+
reset
|
188
|
+
end
|
189
|
+
|
190
|
+
def perform_request
|
191
|
+
client = yield
|
192
|
+
client.callback { @num_succeeded += 1; check_finished }
|
193
|
+
client.errback { @errors << client.error; check_finished }
|
194
|
+
end
|
195
|
+
|
196
|
+
def check_finished
|
197
|
+
if @num_succeeded + @errors.size == @num_registered
|
198
|
+
EventMachine.stop
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|