faraday 0.5.7 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Gemfile +9 -8
  2. data/README.md +99 -20
  3. data/faraday.gemspec +9 -11
  4. data/lib/faraday.rb +1 -1
  5. data/lib/faraday/adapter.rb +11 -86
  6. data/lib/faraday/adapter/action_dispatch.rb +3 -12
  7. data/lib/faraday/adapter/em_synchrony.rb +9 -24
  8. data/lib/faraday/adapter/excon.rb +7 -19
  9. data/lib/faraday/adapter/net_http.rb +37 -35
  10. data/lib/faraday/adapter/patron.rb +16 -23
  11. data/lib/faraday/adapter/test.rb +4 -10
  12. data/lib/faraday/adapter/typhoeus.rb +11 -39
  13. data/lib/faraday/builder.rb +82 -33
  14. data/lib/faraday/connection.rb +3 -10
  15. data/lib/faraday/error.rb +20 -15
  16. data/lib/faraday/middleware.rb +7 -2
  17. data/lib/faraday/request.rb +13 -10
  18. data/lib/faraday/request/json.rb +31 -0
  19. data/lib/faraday/request/multipart.rb +63 -0
  20. data/lib/faraday/request/url_encoded.rb +37 -0
  21. data/lib/faraday/response.rb +72 -30
  22. data/lib/faraday/response/logger.rb +34 -0
  23. data/lib/faraday/response/raise_error.rb +16 -0
  24. data/lib/faraday/upload_io.rb +14 -6
  25. data/lib/faraday/utils.rb +54 -17
  26. data/test/adapters/live_test.rb +36 -14
  27. data/test/adapters/logger_test.rb +1 -1
  28. data/test/adapters/net_http_test.rb +33 -0
  29. data/test/connection_test.rb +0 -39
  30. data/test/env_test.rb +84 -6
  31. data/test/helper.rb +17 -8
  32. data/test/live_server.rb +19 -17
  33. data/test/middleware_stack_test.rb +91 -0
  34. data/test/request_middleware_test.rb +75 -21
  35. data/test/response_middleware_test.rb +34 -31
  36. metadata +21 -17
  37. data/lib/faraday/adapter/logger.rb +0 -32
  38. data/lib/faraday/request/active_support_json.rb +0 -21
  39. data/lib/faraday/request/yajl.rb +0 -18
  40. data/lib/faraday/response/active_support_json.rb +0 -30
  41. data/lib/faraday/response/yajl.rb +0 -26
  42. data/test/adapters/typhoeus_test.rb +0 -31
  43. data/test/connection_app_test.rb +0 -60
  44. data/test/form_post_test.rb +0 -58
  45. data/test/multipart_test.rb +0 -48
data/Gemfile CHANGED
@@ -4,15 +4,16 @@ source "http://rubygems.org"
4
4
  # put test-only gems in this group so their generators
5
5
  # and rake tasks are available in development mode:
6
6
  group :development, :test do
7
- gem 'patron', '~> 0.4'
7
+ gem 'patron', '~> 0.4', :platforms => :ruby
8
8
  gem 'sinatra', '~> 1.1'
9
- gem 'typhoeus', '~> 0.2'
10
- gem 'eventmachine', '~> 0.12'
11
- gem 'em-http-request', '~> 0.3', :require => 'em-http'
12
- major, minor, patch = RUBY_VERSION.split('.')
13
- if major.to_i >= 1 && minor.to_i >= 9
14
- gem 'em-synchrony', '~> 0.2', :require => ['em-synchrony', 'em-synchrony/em-http']
15
- end
9
+ gem 'typhoeus', '~> 0.2', :platforms => :ruby
10
+ gem 'excon', '~> 0.5.8'
11
+ gem 'em-http-request', '~> 0.3', :require => 'em-http', :platforms => :ruby
12
+ gem 'em-synchrony', '~> 0.2', :require => ['em-synchrony', 'em-synchrony/em-http'], :platforms => :ruby_19
13
+ gem 'webmock'
14
+ # ActiveSupport::JSON will be used in ruby 1.8 and Yajl in 1.9; this is to test against both adapters
15
+ gem 'activesupport', '~> 2.3.8', :require => nil, :platforms => [:ruby_18, :jruby]
16
+ gem 'yajl-ruby', :require => 'yajl', :platforms => :ruby_19
16
17
  end
17
18
 
18
19
  gemspec
data/README.md CHANGED
@@ -7,30 +7,109 @@ This mess is gonna get raw, like sushi. So, haters to the left.
7
7
  ## Usage
8
8
 
9
9
  conn = Faraday.new(:url => 'http://sushi.com') do |builder|
10
- builder.use Faraday::Request::Yajl # convert body to json with Yajl lib
11
- builder.use Faraday::Adapter::Logger # log the request somewhere?
12
- builder.use Faraday::Adapter::Typhoeus # make http request with typhoeus
13
- builder.use Faraday::Adapter::EMSynchrony # make http request with eventmachine and synchrony
14
- builder.use Faraday::Response::Yajl # parse body with yajl
15
-
16
- # or use shortcuts
17
- builder.request :yajl # Faraday::Request::Yajl
18
- builder.adapter :logger # Faraday::Adapter::Logger
19
- builder.adapter :typhoeus # Faraday::Adapter::Typhoeus
20
- builder.adapter :em_synchrony # Faraday::Adapter::EMSynchrony
21
- builder.response :yajl # Faraday::Response::Yajl
10
+ builder.use Faraday::Request::UrlEncoded # convert request params as "www-form-urlencoded"
11
+ builder.use Faraday::Request::JSON # encode request params as json
12
+ builder.use Faraday::Response::Logger # log the request to STDOUT
13
+ builder.use Faraday::Adapter::NetHttp # make http requests with Net::HTTP
14
+
15
+ # or, use shortcuts:
16
+ builder.request :url_encoded
17
+ builder.request :json
18
+ builder.response :logger
19
+ builder.adapter :net_http
22
20
  end
21
+
22
+ ## GET ##
23
23
 
24
- resp1 = conn.get '/nigiri/sake.json'
25
- resp2 = conn.post do |req|
26
- req.url "/nigiri.json", :page => 2
27
- req.params['limit'] = 100 # &limit=100
28
- req.headers["Content-Type"] = 'application/json'
29
- req.body = {:name => 'Unagi'}
24
+ response = conn.get '/nigiri/sake.json' # GET http://sushi.com/nigiri/sake.json
25
+ response.body
26
+
27
+ conn.get '/nigiri', 'X-Awesome' => true # custom request header
28
+
29
+ conn.get do |req| # GET http://sushi.com/search?page=2&limit=100
30
+ req.url '/search', :page => 2
31
+ req.params['limit'] = 100
32
+ end
33
+
34
+ ## POST ##
35
+
36
+ conn.post '/nigiri', { :name => 'Maguro' } # POST "name=maguro" to http://sushi.com/nigiri
37
+
38
+ # post payload as JSON instead of "www-form-urlencoded" encoding:
39
+ conn.post '/nigiri', payload, 'Content-Type' => 'application/json'
40
+
41
+ # a more verbose way:
42
+ conn.post do |req|
43
+ req.url '/nigiri'
44
+ req.headers['Content-Type'] = 'application/json'
45
+ req.body = { :name => 'Unagi' }
46
+ end
47
+
48
+ If you're ready to roll with just the bare minimum:
49
+
50
+ # default stack (net/http), no extra middleware:
51
+ response = Faraday.get 'http://sushi.com/nigiri/sake.json'
52
+
53
+ ## Advanced middleware usage
54
+
55
+ The order in which middleware is stacked is important. Like with Rack, the first middleware on the list wraps all others, while the last middleware is the innermost one, so that's usually the adapter.
56
+
57
+ conn = Faraday.new(:url => 'http://sushi.com') do |builder|
58
+ # POST/PUT params encoders:
59
+ builder.request :multipart
60
+ builder.request :url_encoded
61
+ builder.request :json
62
+
63
+ builder.adapter :net_http
64
+ end
65
+
66
+ This request middleware setup affects POST/PUT requests in the following way:
67
+
68
+ 1. `Request::Multipart` checks for files in the payload, otherwise leaves everything untouched;
69
+ 2. `Request::UrlEncoded` encodes as "application/x-www-form-urlencoded" if not already encoded or of another type
70
+ 2. `Request::JSON` encodes as "application/json" if not already encoded or of another type
71
+
72
+ Because "UrlEncoded" is higher on the stack than JSON encoder, it will get to process the request first. Swapping them means giving the other priority. Specifying the "Content-Type" for the request is explicitly stating which middleware should process it.
73
+
74
+ Examples:
75
+
76
+ payload = { :name => 'Maguro' }
77
+
78
+ # post payload as JSON instead of urlencoded:
79
+ conn.post '/nigiri', payload, 'Content-Type' => 'application/json'
80
+
81
+ # uploading a file:
82
+ payload = { :profile_pic => Faraday::UploadIO.new('avatar.jpg', 'image/jpeg') }
83
+
84
+ # "Multipart" middleware detects files and encodes with "multipart/form-data":
85
+ conn.put '/profile', payload
86
+
87
+ ## Writing middleware
88
+
89
+ Middleware are classes that respond to `call()`. They wrap the request/response cycle.
90
+
91
+ def call(env)
92
+ # do something with the request
93
+
94
+ @app.call(env).on_complete do
95
+ # do something with the response
96
+ end
30
97
  end
31
98
 
32
- # If you're ready to roll with just the bare minimum (net/http):
33
- resp1 = Faraday.get 'http://sushi.com/nigiri/sake.json'
99
+ It's important to do all processing of the response only in the `on_complete` block. This enables middleware to work in parallel mode where requests are asynchronous.
100
+
101
+ The `env` is a hash with symbol keys that contains info about the request and, later, response. Some keys are:
102
+
103
+ # request phase
104
+ :method - :get, :post, ...
105
+ :url - URI for the current request; also contains GET parameters
106
+ :body - POST parameters for :post/:put requests
107
+ :request_headers
108
+
109
+ # response phase
110
+ :status - HTTP response status code, such as 200
111
+ :body - the response body
112
+ :response_headers
34
113
 
35
114
  ## Testing
36
115
 
data/faraday.gemspec CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
12
12
  ## If your rubyforge_project name is different, then edit it and comment out
13
13
  ## the sub! line in the Rakefile
14
14
  s.name = 'faraday'
15
- s.version = '0.5.7'
16
- s.date = '2011-02-27'
15
+ s.version = '0.6.0'
16
+ s.date = '2011-03-31'
17
17
  s.rubyforge_project = 'faraday'
18
18
 
19
19
  ## Make sure your summary is short. The description may be as long
@@ -52,7 +52,6 @@ Gem::Specification.new do |s|
52
52
  lib/faraday/adapter/action_dispatch.rb
53
53
  lib/faraday/adapter/em_synchrony.rb
54
54
  lib/faraday/adapter/excon.rb
55
- lib/faraday/adapter/logger.rb
56
55
  lib/faraday/adapter/net_http.rb
57
56
  lib/faraday/adapter/patron.rb
58
57
  lib/faraday/adapter/test.rb
@@ -62,24 +61,23 @@ Gem::Specification.new do |s|
62
61
  lib/faraday/error.rb
63
62
  lib/faraday/middleware.rb
64
63
  lib/faraday/request.rb
65
- lib/faraday/request/active_support_json.rb
66
- lib/faraday/request/yajl.rb
64
+ lib/faraday/request/json.rb
65
+ lib/faraday/request/multipart.rb
66
+ lib/faraday/request/url_encoded.rb
67
67
  lib/faraday/response.rb
68
- lib/faraday/response/active_support_json.rb
69
- lib/faraday/response/yajl.rb
68
+ lib/faraday/response/logger.rb
69
+ lib/faraday/response/raise_error.rb
70
70
  lib/faraday/upload_io.rb
71
71
  lib/faraday/utils.rb
72
72
  test/adapters/live_test.rb
73
73
  test/adapters/logger_test.rb
74
+ test/adapters/net_http_test.rb
74
75
  test/adapters/test_middleware_test.rb
75
- test/adapters/typhoeus_test.rb
76
- test/connection_app_test.rb
77
76
  test/connection_test.rb
78
77
  test/env_test.rb
79
- test/form_post_test.rb
80
78
  test/helper.rb
81
79
  test/live_server.rb
82
- test/multipart_test.rb
80
+ test/middleware_stack_test.rb
83
81
  test/request_middleware_test.rb
84
82
  test/response_middleware_test.rb
85
83
  ]
data/lib/faraday.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Faraday
2
- VERSION = "0.5.7"
2
+ VERSION = "0.6.0"
3
3
 
4
4
  class << self
5
5
  attr_accessor :default_adapter
@@ -1,11 +1,9 @@
1
1
  module Faraday
2
2
  class Adapter < Middleware
3
- FORM_TYPE = 'application/x-www-form-urlencoded'.freeze
4
- MULTIPART_TYPE = 'multipart/form-data'.freeze
5
- CONTENT_TYPE = 'Content-Type'.freeze
6
- DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze
3
+ CONTENT_LENGTH = 'Content-Length'.freeze
7
4
 
8
5
  extend AutoloadHelper
6
+
9
7
  autoload_all 'faraday/adapter',
10
8
  :ActionDispatch => 'action_dispatch',
11
9
  :NetHttp => 'net_http',
@@ -13,8 +11,7 @@ module Faraday
13
11
  :EMSynchrony => 'em_synchrony',
14
12
  :Patron => 'patron',
15
13
  :Excon => 'excon',
16
- :Test => 'test',
17
- :Logger => 'logger'
14
+ :Test => 'test'
18
15
 
19
16
  register_lookup_modules \
20
17
  :action_dispatch => :ActionDispatch,
@@ -23,91 +20,19 @@ module Faraday
23
20
  :typhoeus => :Typhoeus,
24
21
  :patron => :Patron,
25
22
  :em_synchrony => :EMSynchrony,
26
- :excon => :Excon,
27
- :logger => :Logger
23
+ :excon => :Excon
28
24
 
29
25
  def call(env)
30
- process_body_for_request(env)
31
- end
32
-
33
- # Converts a body hash into encoded form params. This is done as late
34
- # as possible in the request cycle in case some other middleware wants to
35
- # act on the request before sending it out.
36
- #
37
- # env - The current request environment Hash.
38
- # body - A Hash of keys/values. Strings and empty values will be
39
- # ignored. Default: env[:body]
40
- # headers - The Hash of request headers. Default: env[:request_headers]
41
- #
42
- # Returns nothing. If the body is processed, it is replaced in the
43
- # environment for you.
44
- def process_body_for_request(env, body = env[:body], headers = env[:request_headers])
45
- return if body.nil? || body.empty? || !body.respond_to?(:each_key)
46
- if has_multipart?(body)
47
- env[:request] ||= {}
48
- env[:request][:boundary] ||= DEFAULT_BOUNDARY
49
- headers[CONTENT_TYPE] = MULTIPART_TYPE + ";boundary=#{env[:request][:boundary]}"
50
- env[:body] = create_multipart(env, body)
51
- else
52
- type = headers[CONTENT_TYPE]
53
- headers[CONTENT_TYPE] = FORM_TYPE if type.nil? || type.empty?
54
- parts = []
55
- process_to_params(parts, env[:body]) do |key, value|
56
- "#{key}=#{escape(value.to_s)}"
57
- end
58
- env[:body] = parts * "&"
59
- end
60
- end
61
-
62
- def has_multipart?(body)
63
- body.values.each do |v|
64
- if v.respond_to?(:content_type)
65
- return true
66
- elsif v.respond_to?(:values)
67
- return true if has_multipart?(v)
68
- end
26
+ if !env[:body] and Connection::METHODS_WITH_BODIES.include? env[:method]
27
+ # play nice and indicate we're sending an empty body
28
+ env[:request_headers][CONTENT_LENGTH] = "0"
29
+ # Typhoeus hangs on PUT requests if body is nil
30
+ env[:body] = ''
69
31
  end
70
- false
71
32
  end
72
33
 
73
- def create_multipart(env, params, boundary = nil)
74
- boundary ||= env[:request][:boundary]
75
- parts = []
76
- process_to_params(parts, params) do |key, value|
77
- Faraday::Parts::Part.new(boundary, key, value)
78
- end
79
- parts << Faraday::Parts::EpiloguePart.new(boundary)
80
- env[:request_headers]['Content-Length'] = parts.inject(0) {|sum,i| sum + i.length }.to_s
81
- Faraday::CompositeReadIO.new(*parts.map{|p| p.to_io })
82
- end
83
-
84
- def process_to_params(pieces, params, base = nil)
85
- params.to_a.each do |key, value|
86
- key_str = base ? "#{base}[#{key}]" : key
87
-
88
- block = block_given? ? Proc.new : nil
89
- case value
90
- when Array
91
- values = value.inject([]) { |a,v| a << [nil, v] }
92
- process_to_params(pieces, values, key_str, &block)
93
- when Hash
94
- process_to_params(pieces, value, key_str, &block)
95
- else
96
- pieces << block.call(key_str, value)
97
- end
98
- end
99
- end
100
-
101
- # assume that query and fragment are already encoded properly
102
- def full_path_for(path, query = nil, fragment = nil)
103
- full_path = path.dup
104
- if query && !query.empty?
105
- full_path << "?#{query}"
106
- end
107
- if fragment && !fragment.empty?
108
- full_path << "##{fragment}"
109
- end
110
- full_path
34
+ def response_headers(env)
35
+ env[:response_headers] ||= Utils::Headers.new
111
36
  end
112
37
  end
113
38
  end
@@ -19,21 +19,12 @@ module Faraday
19
19
 
20
20
  def call(env)
21
21
  super
22
- full_path = full_path_for(env[:url].path, env[:url].query, env[:url].fragment)
23
- @session.__send__(env[:method], full_path, env[:body], env[:request_headers])
22
+ @session.__send__(env[:method], env[:url].request_uri, env[:body], env[:request_headers])
24
23
  resp = @session.response
25
- env.update \
26
- :status => resp.status,
27
- :response_headers => resp.headers,
28
- :body => resp.body
24
+ env.update :status => resp.status, :body => resp.body
25
+ response_headers(env).update resp.headers
29
26
  @app.call env
30
27
  end
31
-
32
- # TODO: build in support for multipart streaming if action dispatch supports it.
33
- def create_multipart(env, params, boundary = nil)
34
- stream = super
35
- stream.read
36
- end
37
28
  end
38
29
  end
39
30
  end
@@ -1,33 +1,17 @@
1
1
  module Faraday
2
2
  class Adapter
3
3
  class EMSynchrony < Faraday::Adapter
4
- begin
4
+ dependency do
5
5
  require 'em-synchrony/em-http'
6
6
  require 'fiber'
7
- rescue LoadError, NameError => e
8
- self.load_error = e
9
- end
10
-
11
- class Header
12
- include Net::HTTPHeader
13
- def initialize response
14
- @header = {}
15
- response.response_header.each do |key, value|
16
- case key
17
- when "CONTENT_TYPE"; self.content_type = value
18
- when "CONTENT_LENGTH"; self.content_length = value
19
- else; self[key] = value
20
- end
21
- end
22
- end
23
7
  end
24
8
 
25
9
  def call(env)
26
- process_body_for_request(env)
10
+ super
27
11
  request = EventMachine::HttpRequest.new(URI::parse(env[:url].to_s))
28
12
  options = {:head => env[:request_headers]}
29
13
  options[:ssl] = env[:ssl] if env[:ssl]
30
-
14
+
31
15
  if env[:body]
32
16
  if env[:body].respond_to? :read
33
17
  options[:body] = env[:body].read
@@ -41,7 +25,7 @@ module Faraday
41
25
  uri = Addressable::URI.parse(proxy[:uri])
42
26
  options[:proxy] = {
43
27
  :host => uri.host,
44
- :port => uri.port
28
+ :port => uri.inferred_port
45
29
  }
46
30
  if proxy[:username] && proxy[:password]
47
31
  options[:proxy][:authorization] = [proxy[:username], proxy[:password]]
@@ -67,13 +51,14 @@ module Faraday
67
51
  client = block.call
68
52
  end
69
53
 
70
- env.update(:status => client.response_header.http_status.to_i,
71
- :response_headers => Header.new(client),
72
- :body => client.response)
54
+ client.response_header.each do |name, value|
55
+ response_headers(env)[name.to_sym] = value
56
+ end
57
+ env.update :status => client.response_header.status, :body => client.response
73
58
 
74
59
  @app.call env
75
60
  rescue Errno::ECONNREFUSED
76
- raise Error::ConnectionFailed, "connection refused"
61
+ raise Error::ConnectionFailed, $!
77
62
  end
78
63
  end
79
64
  end
@@ -1,21 +1,15 @@
1
1
  module Faraday
2
2
  class Adapter
3
3
  class Excon < Faraday::Adapter
4
- begin
5
- require 'excon'
6
- rescue LoadError, NameError => e
7
- self.load_error = e
8
- end
4
+ dependency 'excon'
9
5
 
10
6
  def call(env)
11
7
  super
12
8
 
13
9
  conn = ::Excon.new(env[:url].to_s)
14
10
  if ssl = (env[:url].scheme == 'https' && env[:ssl])
15
- ::Excon.ssl_verify_peer = !!ssl[:verify] if ssl.key?(:verify)
16
- if ca_file = ssl[:ca_file]
17
- ::Excon.ssl_ca_path = ca_file
18
- end
11
+ ::Excon.ssl_verify_peer = !!ssl.fetch(:verify, true)
12
+ ::Excon.ssl_ca_path = ssl[:ca_file] if ssl[:ca_file]
19
13
  end
20
14
 
21
15
  resp = conn.request \
@@ -23,18 +17,12 @@ module Faraday
23
17
  :headers => env[:request_headers],
24
18
  :body => env[:body]
25
19
 
26
- env.update \
27
- :status => resp.status.to_i,
28
- :response_headers => {},
29
- :body => resp.body
30
-
31
- resp.headers.each do |key, value|
32
- env[:response_headers][key.downcase] = value
33
- end
20
+ env.update :status => resp.status.to_i, :body => resp.body
21
+ response_headers(env).update resp.headers
34
22
 
35
23
  @app.call env
36
- rescue ::Excon::Errors::SocketError => e
37
- raise Error::ConnectionFailed.new(e)
24
+ rescue ::Excon::Errors::SocketError
25
+ raise Error::ConnectionFailed, $!
38
26
  end
39
27
  end
40
28
  end