faraday 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +48 -61
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/faraday.gemspec +31 -14
- data/lib/faraday.rb +33 -26
- data/lib/faraday/adapter/net_http.rb +17 -31
- data/lib/faraday/adapter/patron.rb +33 -0
- data/lib/faraday/adapter/test.rb +96 -0
- data/lib/faraday/adapter/typhoeus.rb +43 -59
- data/lib/faraday/builder.rb +57 -0
- data/lib/faraday/connection.rb +112 -105
- data/lib/faraday/middleware.rb +54 -0
- data/lib/faraday/request.rb +77 -0
- data/lib/faraday/request/active_support_json.rb +20 -0
- data/lib/faraday/request/yajl.rb +18 -0
- data/lib/faraday/response.rb +41 -26
- data/lib/faraday/response/active_support_json.rb +22 -0
- data/lib/faraday/response/yajl.rb +20 -0
- data/test/adapters/live_test.rb +157 -0
- data/test/adapters/test_middleware_test.rb +28 -0
- data/test/adapters/typhoeus_test.rb +28 -0
- data/test/connection_app_test.rb +49 -0
- data/test/connection_test.rb +47 -18
- data/test/env_test.rb +35 -0
- data/test/helper.rb +5 -2
- data/test/live_server.rb +2 -15
- data/test/request_middleware_test.rb +19 -0
- data/test/response_middleware_test.rb +21 -0
- metadata +46 -16
- data/lib/faraday/adapter/mock_request.rb +0 -120
- data/lib/faraday/loadable.rb +0 -13
- data/lib/faraday/request/post_request.rb +0 -30
- data/lib/faraday/request/yajl_request.rb +0 -27
- data/lib/faraday/response/yajl_response.rb +0 -35
- data/lib/faraday/test_connection.rb +0 -5
- data/test/adapter/typhoeus_test.rb +0 -26
- data/test/adapter_test.rb +0 -118
- data/test/response_test.rb +0 -34
@@ -1,75 +1,59 @@
|
|
1
1
|
module Faraday
|
2
2
|
module Adapter
|
3
|
-
|
4
|
-
|
3
|
+
class Typhoeus < Middleware
|
4
|
+
self.supports_parallel_requests = true
|
5
|
+
|
6
|
+
def self.setup_parallel_manager(options = {})
|
7
|
+
options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
|
8
|
+
end
|
5
9
|
|
6
10
|
begin
|
7
11
|
require 'typhoeus'
|
12
|
+
rescue LoadError => e
|
13
|
+
self.load_error = e
|
14
|
+
end
|
8
15
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def in_parallel(options = {})
|
14
|
-
setup_parallel_manager(options)
|
15
|
-
yield
|
16
|
-
run_parallel_requests
|
17
|
-
end
|
18
|
-
|
19
|
-
def setup_parallel_manager(options = {})
|
20
|
-
@parallel_manager ||= ::Typhoeus::Hydra.new(options)
|
21
|
-
end
|
22
|
-
|
23
|
-
def run_parallel_requests
|
24
|
-
@parallel_manager.run
|
25
|
-
@parallel_manager = nil
|
26
|
-
end
|
27
|
-
|
28
|
-
def _post(uri, data, request_headers)
|
29
|
-
request = request_class.new(data, request_headers)
|
30
|
-
_perform(:post, uri, :headers => request.headers, :body => request.body)
|
31
|
-
end
|
16
|
+
def call(env)
|
17
|
+
process_body_for_request(env)
|
32
18
|
|
33
|
-
|
34
|
-
|
35
|
-
|
19
|
+
hydra = env[:parallel_manager] || self.class.setup_parallel_manager
|
20
|
+
req = ::Typhoeus::Request.new env[:url].to_s,
|
21
|
+
:method => env[:method],
|
22
|
+
:body => env[:body],
|
23
|
+
:headers => env[:request_headers]
|
36
24
|
|
37
|
-
|
38
|
-
|
39
|
-
|
25
|
+
req.on_complete do |resp|
|
26
|
+
env.update \
|
27
|
+
:status => resp.code,
|
28
|
+
:response_headers => parse_response_headers(resp.headers),
|
29
|
+
:body => resp.body
|
30
|
+
env[:response].finish(env)
|
40
31
|
end
|
41
32
|
|
42
|
-
|
43
|
-
_perform(:delete, uri, :headers => request_headers)
|
44
|
-
end
|
33
|
+
hydra.queue req
|
45
34
|
|
46
|
-
|
47
|
-
|
48
|
-
is_async = in_parallel?
|
49
|
-
setup_parallel_manager
|
50
|
-
params[:method] = method
|
51
|
-
req = ::Typhoeus::Request.new(uri.to_s, params)
|
52
|
-
req.on_complete do |response|
|
53
|
-
raise Faraday::Error::ResourceNotFound if response.code == 404
|
54
|
-
resp.process!(response.body)
|
55
|
-
resp.headers = parse_response_headers(response.headers)
|
56
|
-
end
|
57
|
-
@parallel_manager.queue(req)
|
58
|
-
if !is_async then run_parallel_requests end
|
59
|
-
end
|
60
|
-
rescue Errno::ECONNREFUSED
|
61
|
-
raise Faraday::Error::ConnectionFailed, "connection refused"
|
35
|
+
if !env[:parallel_manager]
|
36
|
+
hydra.run
|
62
37
|
end
|
63
38
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
39
|
+
@app.call env
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
raise Error::ConnectionFailed, "connection refused"
|
42
|
+
end
|
43
|
+
|
44
|
+
def in_parallel(options = {})
|
45
|
+
@hydra = ::Typhoeus::Hydra.new(options)
|
46
|
+
yield
|
47
|
+
@hydra.run
|
48
|
+
@hydra = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_response_headers(header_string)
|
52
|
+
return {} unless header_string && !header_string.empty?
|
53
|
+
Hash[*header_string.split(/\r\n/).
|
54
|
+
tap { |a| a.shift }. # drop the HTTP status line
|
55
|
+
map! { |h| h.split(/:\s+/,2) }. # split key and value
|
56
|
+
map! { |(k, v)| [k.downcase, v] }.flatten!]
|
73
57
|
end
|
74
58
|
end
|
75
59
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Faraday
|
2
|
+
# Possibly going to extend this a bit.
|
3
|
+
#
|
4
|
+
# Faraday::Connection.new(:url => 'http://sushi.com') do
|
5
|
+
# request :yajl # Faraday::Request::Yajl
|
6
|
+
# adapter :logger # Faraday::Adapter::Logger
|
7
|
+
# response :yajl # Faraday::Response::Yajl
|
8
|
+
# end
|
9
|
+
class Builder
|
10
|
+
attr_accessor :handlers
|
11
|
+
|
12
|
+
def self.create_with_inner_app(&block)
|
13
|
+
inner = lambda do |env|
|
14
|
+
if !env[:parallel_manager]
|
15
|
+
env[:response].finish(env)
|
16
|
+
else
|
17
|
+
env[:response]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
Builder.new(&block).tap { |builder| builder.run(inner) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@handlers = []
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
28
|
+
def run app
|
29
|
+
@handlers.unshift app
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_app
|
33
|
+
inner_app = @handlers.first
|
34
|
+
@handlers[1..-1].inject(inner_app) { |app, middleware| middleware.call(app) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def use klass, *args, &block
|
38
|
+
@handlers.unshift(lambda { |app| klass.new(app, *args, &block) })
|
39
|
+
end
|
40
|
+
|
41
|
+
def request(key, *args, &block)
|
42
|
+
use_symbol Faraday::Request, key, *args, &block
|
43
|
+
end
|
44
|
+
|
45
|
+
def response(key, *args, &block)
|
46
|
+
use_symbol Faraday::Response, key, *args, &block
|
47
|
+
end
|
48
|
+
|
49
|
+
def adapter(key, *args, &block)
|
50
|
+
use_symbol Faraday::Adapter, key, *args, &block
|
51
|
+
end
|
52
|
+
|
53
|
+
def use_symbol(mod, key, *args, &block)
|
54
|
+
use mod.lookup_module(key), *args, &block
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/faraday/connection.rb
CHANGED
@@ -1,119 +1,115 @@
|
|
1
1
|
require 'addressable/uri'
|
2
|
+
require 'set'
|
3
|
+
|
2
4
|
module Faraday
|
3
5
|
class Connection
|
4
|
-
|
5
|
-
def load_error() @load_error end
|
6
|
-
def load_error=(v) @load_error = v end
|
7
|
-
def supports_async() @supports_async end
|
8
|
-
def supports_async=(v) @supports_async = v end
|
9
|
-
def loaded?() !@load_error end
|
10
|
-
alias supports_async? supports_async
|
11
|
-
end
|
6
|
+
include Addressable, Rack::Utils
|
12
7
|
|
13
|
-
|
8
|
+
HEADERS = Hash.new { |h, k| k.respond_to?(:to_str) ? k : k.to_s.capitalize }.update \
|
9
|
+
:content_type => "Content-Type",
|
10
|
+
:content_length => "Content-Length",
|
11
|
+
:accept_charset => "Accept-Charset",
|
12
|
+
:accept_encoding => "Accept-Encoding"
|
13
|
+
HEADERS.values.each { |v| v.freeze }
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
METHODS = Set.new [:get, :post, :put, :delete, :head]
|
16
|
+
METHODS_WITH_BODIES = Set.new [:post, :put]
|
17
|
+
|
18
|
+
attr_accessor :host, :port, :scheme, :params, :headers, :parallel_manager
|
19
|
+
attr_reader :path_prefix, :builder
|
17
20
|
|
18
21
|
# :url
|
19
22
|
# :params
|
20
23
|
# :headers
|
21
|
-
|
22
|
-
def initialize(url = nil, options = {})
|
24
|
+
def initialize(url = nil, options = {}, &block)
|
23
25
|
if url.is_a?(Hash)
|
24
26
|
options = url
|
25
27
|
url = options[:url]
|
26
28
|
end
|
27
|
-
@
|
28
|
-
@params
|
29
|
-
@
|
29
|
+
@headers = HeaderHash.new
|
30
|
+
@params = {}
|
31
|
+
@parallel_manager = options[:parallel]
|
30
32
|
self.url_prefix = url if url
|
33
|
+
merge_params @params, options[:params] if options[:params]
|
34
|
+
merge_headers @headers, options[:headers] if options[:headers]
|
35
|
+
if block
|
36
|
+
@builder = Builder.create_with_inner_app(&block)
|
37
|
+
end
|
31
38
|
end
|
32
39
|
|
33
|
-
def
|
34
|
-
|
35
|
-
self.scheme = uri.scheme
|
36
|
-
self.host = uri.host
|
37
|
-
self.port = uri.port
|
38
|
-
self.path_prefix = uri.path
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
# Override in a subclass, or include an adapter
|
43
|
-
#
|
44
|
-
# def _post(uri, post_params, headers)
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
def post(uri, params = {}, headers = {})
|
48
|
-
_post build_uri(uri), build_params(params), build_headers(headers)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Override in a subclass, or include an adapter
|
52
|
-
#
|
53
|
-
# def _put(uri, post_params, headers)
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
def put(uri, params = {}, headers = {})
|
57
|
-
_put build_uri(uri), build_params(params), build_headers(headers)
|
40
|
+
def get(url = nil, headers = nil, &block)
|
41
|
+
run_request :get, url, nil, headers, &block
|
58
42
|
end
|
59
43
|
|
60
|
-
|
61
|
-
|
62
|
-
# def _delete(uri, headers)
|
63
|
-
# end
|
64
|
-
#
|
65
|
-
def delete(uri, params = {}, headers = {})
|
66
|
-
_delete build_uri(uri, build_params(params)), build_headers(headers)
|
44
|
+
def post(url = nil, body = nil, headers = nil, &block)
|
45
|
+
run_request :post, url, body, headers, &block
|
67
46
|
end
|
68
47
|
|
69
|
-
|
70
|
-
|
71
|
-
# def _get(uri, headers)
|
72
|
-
# end
|
73
|
-
#
|
74
|
-
def get(url, params = nil, headers = nil)
|
75
|
-
uri = build_uri(url, build_params(params))
|
76
|
-
_get(uri, build_headers(headers))
|
48
|
+
def put(url = nil, body = nil, headers = nil, &block)
|
49
|
+
run_request :put, url, body, headers, &block
|
77
50
|
end
|
78
51
|
|
79
|
-
def
|
80
|
-
|
52
|
+
def head(url = nil, headers = nil, &block)
|
53
|
+
run_request :head, url, nil, headers, &block
|
81
54
|
end
|
82
55
|
|
83
|
-
def
|
84
|
-
|
56
|
+
def delete(url = nil, headers = nil, &block)
|
57
|
+
run_request :delete, url, nil, headers, &block
|
85
58
|
end
|
86
59
|
|
87
|
-
def
|
88
|
-
if
|
89
|
-
raise ArgumentError, "
|
60
|
+
def run_request(method, url, body, headers)
|
61
|
+
if !METHODS.include?(method)
|
62
|
+
raise ArgumentError, "unknown http method: #{method}"
|
90
63
|
end
|
91
|
-
@response_class = v
|
92
|
-
end
|
93
64
|
|
94
|
-
|
95
|
-
|
96
|
-
|
65
|
+
Request.run(self, method) do |req|
|
66
|
+
req.url(url) if url
|
67
|
+
req.headers.update(headers) if headers
|
68
|
+
req.body = body if body
|
69
|
+
yield req if block_given?
|
97
70
|
end
|
98
|
-
@request_class = v
|
99
71
|
end
|
100
72
|
|
101
73
|
def in_parallel?
|
102
74
|
!!@parallel_manager
|
103
75
|
end
|
104
76
|
|
105
|
-
def in_parallel(
|
106
|
-
@parallel_manager =
|
77
|
+
def in_parallel(manager)
|
78
|
+
@parallel_manager = manager
|
107
79
|
yield
|
108
|
-
@parallel_manager
|
80
|
+
@parallel_manager && @parallel_manager.run
|
81
|
+
ensure
|
82
|
+
@parallel_manager = nil
|
109
83
|
end
|
110
84
|
|
111
|
-
|
85
|
+
# return the assembled Rack application for this instance.
|
86
|
+
def to_app
|
87
|
+
@builder.to_app
|
112
88
|
end
|
113
89
|
|
114
|
-
|
90
|
+
# Parses the giving url with Addressable::URI and stores the individual
|
91
|
+
# components in this connection. These components serve as defaults for
|
92
|
+
# requests made by this connection.
|
93
|
+
#
|
94
|
+
# conn = Faraday::Connection.new { ... }
|
95
|
+
# conn.url_prefix = "https://sushi.com/api"
|
96
|
+
# conn.scheme # => https
|
97
|
+
# conn.path_prefix # => "/api"
|
98
|
+
#
|
99
|
+
# conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
|
100
|
+
#
|
101
|
+
def url_prefix=(url)
|
102
|
+
uri = URI.parse(url)
|
103
|
+
self.scheme = uri.scheme
|
104
|
+
self.host = uri.host
|
105
|
+
self.port = uri.port
|
106
|
+
self.path_prefix = uri.path
|
107
|
+
if uri.query && !uri.query.empty?
|
108
|
+
merge_params @params, parse_query(uri.query)
|
109
|
+
end
|
115
110
|
end
|
116
111
|
|
112
|
+
# Ensures that the path prefix always has a leading / and no trailing /
|
117
113
|
def path_prefix=(value)
|
118
114
|
if value
|
119
115
|
value.chomp! "/"
|
@@ -122,54 +118,65 @@ module Faraday
|
|
122
118
|
@path_prefix = value
|
123
119
|
end
|
124
120
|
|
125
|
-
|
126
|
-
|
121
|
+
# Takes a relative url for a request and combines it with the defaults
|
122
|
+
# set on the connection instance.
|
123
|
+
#
|
124
|
+
# conn = Faraday::Connection.new { ... }
|
125
|
+
# conn.url_prefix = "https://sushi.com/api?token=abc"
|
126
|
+
# conn.scheme # => https
|
127
|
+
# conn.path_prefix # => "/api"
|
128
|
+
#
|
129
|
+
# conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
|
130
|
+
# conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
|
131
|
+
#
|
132
|
+
def build_url(url, params = nil)
|
133
|
+
uri = URI.parse(url.to_s)
|
127
134
|
uri.scheme ||= @scheme
|
128
135
|
uri.host ||= @host
|
129
136
|
uri.port ||= @port
|
130
137
|
if @path_prefix && uri.path !~ /^\//
|
131
138
|
uri.path = "#{@path_prefix.size > 1 ? @path_prefix : nil}/#{uri.path}"
|
132
139
|
end
|
133
|
-
|
134
|
-
uri.query = params_to_query(params)
|
135
|
-
end
|
140
|
+
replace_query(uri, params)
|
136
141
|
uri
|
137
142
|
end
|
138
143
|
|
139
|
-
def
|
140
|
-
|
141
|
-
|
142
|
-
|
144
|
+
def replace_query(uri, params)
|
145
|
+
url_params = @params.dup
|
146
|
+
if uri.query && !uri.query.empty?
|
147
|
+
merge_params url_params, parse_query(uri.query)
|
143
148
|
end
|
144
|
-
|
145
|
-
|
146
|
-
def build_params(existing)
|
147
|
-
build_hash :params, existing
|
148
|
-
end
|
149
|
-
|
150
|
-
def build_headers(existing)
|
151
|
-
build_hash(:headers, existing).tap do |headers|
|
152
|
-
headers.keys.each do |key|
|
153
|
-
headers[key] = headers.delete(key).to_s
|
154
|
-
end
|
149
|
+
if params && !params.empty?
|
150
|
+
merge_params url_params, params
|
155
151
|
end
|
152
|
+
uri.query = url_params.empty? ? nil : build_query(url_params)
|
153
|
+
uri
|
156
154
|
end
|
157
155
|
|
158
|
-
|
159
|
-
|
156
|
+
# turns param keys into strings
|
157
|
+
def merge_params(existing_params, new_params)
|
158
|
+
new_params.each do |key, value|
|
159
|
+
existing_params[key.to_s] = value
|
160
|
+
end
|
160
161
|
end
|
161
162
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
163
|
+
# turns headers keys and values into strings. Look up symbol keys in the
|
164
|
+
# the HEADERS hash.
|
165
|
+
#
|
166
|
+
# h = merge_headers(HeaderHash.new, :content_type => 'text/plain')
|
167
|
+
# h['Content-Type'] # = 'text/plain'
|
168
|
+
#
|
169
|
+
def merge_headers(existing_headers, new_headers)
|
170
|
+
new_headers.each do |key, value|
|
171
|
+
existing_headers[HEADERS[key]] = value.to_s
|
172
|
+
end
|
166
173
|
end
|
167
174
|
|
168
|
-
#
|
169
|
-
#
|
170
|
-
def
|
171
|
-
|
172
|
-
|
175
|
+
# Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
|
176
|
+
# as spaces.
|
177
|
+
def escape(s)
|
178
|
+
s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
|
179
|
+
'%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
|
173
180
|
end
|
174
181
|
end
|
175
182
|
end
|