faraday 0.17.6 → 1.10.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +156 -8
  3. data/LICENSE.md +1 -1
  4. data/README.md +16 -358
  5. data/Rakefile +1 -7
  6. data/examples/client_spec.rb +97 -0
  7. data/examples/client_test.rb +118 -0
  8. data/lib/faraday/adapter/test.rb +118 -69
  9. data/lib/faraday/adapter/typhoeus.rb +4 -1
  10. data/lib/faraday/adapter.rb +72 -22
  11. data/lib/faraday/adapter_registry.rb +30 -0
  12. data/lib/faraday/autoload.rb +39 -36
  13. data/lib/faraday/connection.rb +343 -185
  14. data/lib/faraday/dependency_loader.rb +39 -0
  15. data/lib/faraday/deprecate.rb +7 -6
  16. data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  17. data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
  18. data/lib/faraday/error.rb +28 -40
  19. data/lib/faraday/logging/formatter.rb +105 -0
  20. data/lib/faraday/methods.rb +6 -0
  21. data/lib/faraday/middleware.rb +19 -25
  22. data/lib/faraday/middleware_registry.rb +129 -0
  23. data/lib/faraday/options/connection_options.rb +22 -0
  24. data/lib/faraday/options/env.rb +181 -0
  25. data/lib/faraday/options/proxy_options.rb +32 -0
  26. data/lib/faraday/options/request_options.rb +22 -0
  27. data/lib/faraday/options/ssl_options.rb +59 -0
  28. data/lib/faraday/options.rb +36 -191
  29. data/lib/faraday/parameters.rb +4 -197
  30. data/lib/faraday/rack_builder.rb +76 -64
  31. data/lib/faraday/request/authorization.rb +51 -30
  32. data/lib/faraday/request/basic_authentication.rb +14 -7
  33. data/lib/faraday/request/instrumentation.rb +45 -27
  34. data/lib/faraday/request/json.rb +55 -0
  35. data/lib/faraday/request/token_authentication.rb +15 -10
  36. data/lib/faraday/request/url_encoded.rb +43 -23
  37. data/lib/faraday/request.rb +82 -44
  38. data/lib/faraday/response/json.rb +54 -0
  39. data/lib/faraday/response/logger.rb +20 -69
  40. data/lib/faraday/response/raise_error.rb +49 -18
  41. data/lib/faraday/response.rb +26 -20
  42. data/lib/faraday/utils/headers.rb +139 -0
  43. data/lib/faraday/utils/params_hash.rb +61 -0
  44. data/lib/faraday/utils.rb +38 -247
  45. data/lib/faraday/version.rb +5 -0
  46. data/lib/faraday.rb +134 -188
  47. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  48. data/spec/faraday/adapter/em_http_spec.rb +49 -0
  49. data/spec/faraday/adapter/em_synchrony_spec.rb +18 -0
  50. data/spec/faraday/adapter/excon_spec.rb +49 -0
  51. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  52. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  53. data/spec/faraday/adapter/patron_spec.rb +18 -0
  54. data/spec/faraday/adapter/rack_spec.rb +8 -0
  55. data/spec/faraday/adapter/test_spec.rb +377 -0
  56. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  57. data/spec/faraday/adapter_registry_spec.rb +28 -0
  58. data/spec/faraday/adapter_spec.rb +55 -0
  59. data/spec/faraday/composite_read_io_spec.rb +80 -0
  60. data/spec/faraday/connection_spec.rb +736 -0
  61. data/spec/faraday/deprecate_spec.rb +17 -17
  62. data/spec/faraday/error_spec.rb +12 -54
  63. data/spec/faraday/middleware_spec.rb +52 -0
  64. data/spec/faraday/options/env_spec.rb +70 -0
  65. data/spec/faraday/options/options_spec.rb +297 -0
  66. data/spec/faraday/options/proxy_options_spec.rb +44 -0
  67. data/spec/faraday/options/request_options_spec.rb +19 -0
  68. data/spec/faraday/params_encoders/flat_spec.rb +42 -0
  69. data/spec/faraday/params_encoders/nested_spec.rb +142 -0
  70. data/spec/faraday/rack_builder_spec.rb +345 -0
  71. data/spec/faraday/request/authorization_spec.rb +96 -0
  72. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  73. data/spec/faraday/request/json_spec.rb +111 -0
  74. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  75. data/spec/faraday/request_spec.rb +120 -0
  76. data/spec/faraday/response/json_spec.rb +119 -0
  77. data/spec/faraday/response/logger_spec.rb +220 -0
  78. data/spec/faraday/response/middleware_spec.rb +68 -0
  79. data/spec/faraday/response/raise_error_spec.rb +78 -15
  80. data/spec/faraday/response_spec.rb +75 -0
  81. data/spec/faraday/utils/headers_spec.rb +82 -0
  82. data/spec/faraday/utils_spec.rb +56 -0
  83. data/spec/faraday_spec.rb +37 -0
  84. data/spec/spec_helper.rb +65 -36
  85. data/spec/support/disabling_stub.rb +14 -0
  86. data/spec/support/fake_safe_buffer.rb +15 -0
  87. data/spec/support/helper_methods.rb +133 -0
  88. data/spec/support/shared_examples/adapter.rb +105 -0
  89. data/spec/support/shared_examples/params_encoder.rb +18 -0
  90. data/spec/support/shared_examples/request_method.rb +262 -0
  91. data/spec/support/streaming_response_checker.rb +35 -0
  92. data/spec/support/webmock_rack_app.rb +68 -0
  93. metadata +210 -56
  94. data/lib/faraday/adapter/em_http.rb +0 -243
  95. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -56
  96. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -66
  97. data/lib/faraday/adapter/em_synchrony.rb +0 -106
  98. data/lib/faraday/adapter/excon.rb +0 -82
  99. data/lib/faraday/adapter/httpclient.rb +0 -128
  100. data/lib/faraday/adapter/net_http.rb +0 -153
  101. data/lib/faraday/adapter/net_http_persistent.rb +0 -68
  102. data/lib/faraday/adapter/patron.rb +0 -95
  103. data/lib/faraday/adapter/rack.rb +0 -58
  104. data/lib/faraday/request/multipart.rb +0 -68
  105. data/lib/faraday/request/retry.rb +0 -213
  106. data/lib/faraday/upload_io.rb +0 -77
  107. data/test/adapters/default_test.rb +0 -14
  108. data/test/adapters/em_http_test.rb +0 -30
  109. data/test/adapters/em_synchrony_test.rb +0 -32
  110. data/test/adapters/excon_test.rb +0 -30
  111. data/test/adapters/httpclient_test.rb +0 -34
  112. data/test/adapters/integration.rb +0 -263
  113. data/test/adapters/logger_test.rb +0 -136
  114. data/test/adapters/net_http_persistent_test.rb +0 -114
  115. data/test/adapters/net_http_test.rb +0 -79
  116. data/test/adapters/patron_test.rb +0 -40
  117. data/test/adapters/rack_test.rb +0 -38
  118. data/test/adapters/test_middleware_test.rb +0 -157
  119. data/test/adapters/typhoeus_test.rb +0 -38
  120. data/test/authentication_middleware_test.rb +0 -65
  121. data/test/composite_read_io_test.rb +0 -109
  122. data/test/connection_test.rb +0 -738
  123. data/test/env_test.rb +0 -268
  124. data/test/helper.rb +0 -75
  125. data/test/live_server.rb +0 -67
  126. data/test/middleware/instrumentation_test.rb +0 -88
  127. data/test/middleware/retry_test.rb +0 -282
  128. data/test/middleware_stack_test.rb +0 -260
  129. data/test/multibyte.txt +0 -1
  130. data/test/options_test.rb +0 -333
  131. data/test/parameters_test.rb +0 -157
  132. data/test/request_middleware_test.rb +0 -126
  133. data/test/response_middleware_test.rb +0 -72
  134. data/test/strawberry.rb +0 -2
  135. data/test/utils_test.rb +0 -98
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
- # Used to setup urls, params, headers, and the request body in a sane manner.
4
+ # Used to setup URLs, params, headers, and the request body in a sane manner.
3
5
  #
6
+ # @example
4
7
  # @connection.post do |req|
5
8
  # req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
6
9
  # req.headers['b'] = '2' # Header
@@ -9,27 +12,61 @@ module Faraday
9
12
  # req.body = 'abc'
10
13
  # end
11
14
  #
12
- class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
13
- extend MiddlewareRegistry
15
+ # @!attribute http_method
16
+ # @return [Symbol] the HTTP method of the Request
17
+ # @!attribute path
18
+ # @return [URI, String] the path
19
+ # @!attribute params
20
+ # @return [Hash] query parameters
21
+ # @!attribute headers
22
+ # @return [Faraday::Utils::Headers] headers
23
+ # @!attribute body
24
+ # @return [Hash] body
25
+ # @!attribute options
26
+ # @return [RequestOptions] options
27
+ #
28
+ # rubocop:disable Style/StructInheritance
29
+ class Request < Struct.new(
30
+ :http_method, :path, :params, :headers, :body, :options
31
+ )
32
+ # rubocop:enable Style/StructInheritance
14
33
 
15
- alias_method :http_method, :method
34
+ extend MiddlewareRegistry
16
35
 
17
- register_middleware File.expand_path('../request', __FILE__),
18
- :url_encoded => [:UrlEncoded, 'url_encoded'],
19
- :multipart => [:Multipart, 'multipart'],
20
- :retry => [:Retry, 'retry'],
21
- :authorization => [:Authorization, 'authorization'],
22
- :basic_auth => [:BasicAuthentication, 'basic_authentication'],
23
- :token_auth => [:TokenAuthentication, 'token_authentication'],
24
- :instrumentation => [:Instrumentation, 'instrumentation']
36
+ register_middleware File.expand_path('request', __dir__),
37
+ url_encoded: [:UrlEncoded, 'url_encoded'],
38
+ authorization: [:Authorization, 'authorization'],
39
+ basic_auth: [
40
+ :BasicAuthentication,
41
+ 'basic_authentication'
42
+ ],
43
+ token_auth: [
44
+ :TokenAuthentication,
45
+ 'token_authentication'
46
+ ],
47
+ instrumentation: [:Instrumentation, 'instrumentation'],
48
+ json: [:Json, 'json']
25
49
 
50
+ # @param request_method [String]
51
+ # @yield [request] for block customization, if block given
52
+ # @yieldparam request [Request]
53
+ # @return [Request]
26
54
  def self.create(request_method)
27
55
  new(request_method).tap do |request|
28
56
  yield(request) if block_given?
29
57
  end
30
58
  end
31
59
 
32
- # Public: Replace params, preserving the existing hash type
60
+ def method
61
+ http_method
62
+ end
63
+
64
+ extend Faraday::Deprecate
65
+ deprecate :method, :http_method, '2.0'
66
+
67
+ # Replace params, preserving the existing hash type.
68
+ #
69
+ # @param hash [Hash] new params
33
70
  def params=(hash)
34
71
  if params
35
72
  params.replace hash
@@ -38,7 +75,9 @@ module Faraday
38
75
  end
39
76
  end
40
77
 
41
- # Public: Replace request headers, preserving the existing hash type
78
+ # Replace request headers, preserving the existing hash type.
79
+ #
80
+ # @param hash [Hash] new headers
42
81
  def headers=(hash)
43
82
  if headers
44
83
  headers.replace hash
@@ -47,9 +86,14 @@ module Faraday
47
86
  end
48
87
  end
49
88
 
89
+ # Update path and params.
90
+ #
91
+ # @param path [URI, String]
92
+ # @param params [Hash, nil]
93
+ # @return [void]
50
94
  def url(path, params = nil)
51
95
  if path.respond_to? :query
52
- if query = path.query
96
+ if (query = path.query)
53
97
  path = path.dup
54
98
  path.query = nil
55
99
  end
@@ -63,54 +107,48 @@ module Faraday
63
107
  self.params.update(params) if params
64
108
  end
65
109
 
110
+ # @param key [Object] key to look up in headers
111
+ # @return [Object] value of the given header name
66
112
  def [](key)
67
113
  headers[key]
68
114
  end
69
115
 
116
+ # @param key [Object] key of header to write
117
+ # @param value [Object] value of header
70
118
  def []=(key, value)
71
119
  headers[key] = value
72
120
  end
73
121
 
122
+ # Marshal serialization support.
123
+ #
124
+ # @return [Hash] the hash ready to be serialized in Marshal.
74
125
  def marshal_dump
75
126
  {
76
- :method => method,
77
- :body => body,
78
- :headers => headers,
79
- :path => path,
80
- :params => params,
81
- :options => options
127
+ http_method: http_method,
128
+ body: body,
129
+ headers: headers,
130
+ path: path,
131
+ params: params,
132
+ options: options
82
133
  }
83
134
  end
84
135
 
136
+ # Marshal serialization support.
137
+ # Restores the instance variables according to the +serialised+.
138
+ # @param serialised [Hash] the serialised object.
85
139
  def marshal_load(serialised)
86
- self.method = serialised[:method]
87
- self.body = serialised[:body]
140
+ self.http_method = serialised[:http_method]
141
+ self.body = serialised[:body]
88
142
  self.headers = serialised[:headers]
89
- self.path = serialised[:path]
90
- self.params = serialised[:params]
143
+ self.path = serialised[:path]
144
+ self.params = serialised[:params]
91
145
  self.options = serialised[:options]
92
146
  end
93
147
 
94
- # ENV Keys
95
- # :method - a symbolized request method (:get, :post)
96
- # :body - the request body that will eventually be converted to a string.
97
- # :url - URI instance for the current request.
98
- # :status - HTTP response status code
99
- # :request_headers - hash of HTTP Headers to be sent to the server
100
- # :response_headers - Hash of HTTP headers from the server
101
- # :parallel_manager - sent if the connection is in parallel mode
102
- # :request - Hash of options for configuring the request.
103
- # :timeout - open/read timeout Integer in seconds
104
- # :open_timeout - read timeout Integer in seconds
105
- # :proxy - Hash of proxy options
106
- # :uri - Proxy Server URI
107
- # :user - Proxy server username
108
- # :password - Proxy server password
109
- # :ssl - Hash of options for configuring SSL requests.
148
+ # @return [Env] the Env for this Request
110
149
  def to_env(connection)
111
- Env.new(method, body, connection.build_exclusive_url(path, params),
112
- options, headers, connection.ssl, connection.parallel_manager)
150
+ Env.new(http_method, body, connection.build_exclusive_url(path, params),
151
+ options, headers, connection.ssl, connection.parallel_manager)
113
152
  end
114
153
  end
115
154
  end
116
-
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Faraday
6
+ class Response
7
+ # Parse response bodies as JSON.
8
+ class Json < Middleware
9
+ def initialize(app = nil, options = {})
10
+ super(app)
11
+ @parser_options = options[:parser_options]
12
+ @content_types = Array(options[:content_type] || /\bjson$/)
13
+ @preserve_raw = options[:preserve_raw]
14
+ end
15
+
16
+ def on_complete(env)
17
+ process_response(env) if parse_response?(env)
18
+ end
19
+
20
+ private
21
+
22
+ def process_response(env)
23
+ env[:raw_body] = env[:body] if @preserve_raw
24
+ env[:body] = parse(env[:body])
25
+ rescue StandardError, SyntaxError => e
26
+ raise Faraday::ParsingError.new(e, env[:response])
27
+ end
28
+
29
+ def parse(body)
30
+ ::JSON.parse(body, @parser_options || {}) unless body.strip.empty?
31
+ end
32
+
33
+ def parse_response?(env)
34
+ process_response_type?(env) &&
35
+ env[:body].respond_to?(:to_str)
36
+ end
37
+
38
+ def process_response_type?(env)
39
+ type = response_type(env)
40
+ @content_types.empty? || @content_types.any? do |pattern|
41
+ pattern.is_a?(Regexp) ? type.match?(pattern) : type == pattern
42
+ end
43
+ end
44
+
45
+ def response_type(env)
46
+ type = env[:response_headers][CONTENT_TYPE].to_s
47
+ type = type.split(';', 2).first if type.index(';')
48
+ type
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ Faraday::Response.register_middleware(json: Faraday::Response::Json)
@@ -1,80 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
4
+ require 'logger'
5
+ require 'faraday/logging/formatter'
2
6
 
3
7
  module Faraday
4
- class Response::Logger < Response::Middleware
5
- extend Forwardable
6
-
7
- DEFAULT_OPTIONS = { :headers => true, :bodies => false }
8
-
9
- def initialize(app, logger = nil, options = {})
10
- super(app)
11
- @logger = logger || begin
12
- require 'logger'
13
- ::Logger.new($stdout)
14
- end
15
- @filter = []
16
- @options = DEFAULT_OPTIONS.merge(options)
17
- yield self if block_given?
18
- end
19
-
20
- def_delegators :@logger, :debug, :info, :warn, :error, :fatal
21
-
22
- def call(env)
23
- info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
24
- debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
- debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
26
- super
27
- end
28
-
29
- def on_complete(env)
30
- info('response') { "Status #{env.status.to_s}" }
31
- debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
- debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
- end
34
-
35
- def filter(filter_word, filter_replacement)
36
- @filter.push([ filter_word, filter_replacement ])
37
- end
38
-
39
- private
40
-
41
- def dump_headers(headers)
42
- headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
43
- end
44
-
45
- def dump_body(body)
46
- if body.respond_to?(:to_str)
47
- body.to_str
48
- else
49
- pretty_inspect(body)
50
- end
51
- end
52
-
53
- def pretty_inspect(body)
54
- require 'pp' unless body.respond_to?(:pretty_inspect)
55
- body.pretty_inspect
56
- end
57
-
58
- def log_headers?(type)
59
- case @options[:headers]
60
- when Hash then @options[:headers][type]
61
- else @options[:headers]
8
+ class Response
9
+ # Logger is a middleware that logs internal events in the HTTP request
10
+ # lifecycle to a given Logger object. By default, this logs to STDOUT. See
11
+ # Faraday::Logging::Formatter to see specifically what is logged.
12
+ class Logger < Middleware
13
+ def initialize(app, logger = nil, options = {})
14
+ super(app)
15
+ logger ||= ::Logger.new($stdout)
16
+ formatter_class = options.delete(:formatter) || Logging::Formatter
17
+ @formatter = formatter_class.new(logger: logger, options: options)
18
+ yield @formatter if block_given?
62
19
  end
63
- end
64
20
 
65
- def log_body?(type)
66
- case @options[:bodies]
67
- when Hash then @options[:bodies][type]
68
- else @options[:bodies]
21
+ def call(env)
22
+ @formatter.request(env)
23
+ super
69
24
  end
70
- end
71
25
 
72
- def apply_filters(output)
73
- @filter.each do |pattern, replacement|
74
- output = output.to_s.gsub(pattern, replacement)
26
+ def on_complete(env)
27
+ @formatter.response(env)
75
28
  end
76
- output
77
29
  end
78
-
79
30
  end
80
31
  end
@@ -1,25 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
- class Response::RaiseError < Response::Middleware
3
- ClientErrorStatuses = 400...600
4
+ class Response
5
+ # RaiseError is a Faraday middleware that raises exceptions on common HTTP
6
+ # client or server error responses.
7
+ class RaiseError < Middleware
8
+ # rubocop:disable Naming/ConstantName
9
+ ClientErrorStatuses = (400...500).freeze
10
+ ServerErrorStatuses = (500...600).freeze
11
+ # rubocop:enable Naming/ConstantName
4
12
 
5
- def on_complete(env)
6
- case env[:status]
7
- when 404
8
- raise Faraday::ResourceNotFound, response_values(env)
9
- when 407
10
- # mimic the behavior that we get with proxy requests with HTTPS
11
- raise Faraday::ConnectionFailed.new(
12
- %{407 "Proxy Authentication Required "},
13
- response_values(env))
14
- when ClientErrorStatuses
15
- raise Faraday::ClientError, response_values(env)
16
- when nil
17
- raise Faraday::NilStatusError, response_values(env)
13
+ def on_complete(env)
14
+ case env[:status]
15
+ when 400
16
+ raise Faraday::BadRequestError, response_values(env)
17
+ when 401
18
+ raise Faraday::UnauthorizedError, response_values(env)
19
+ when 403
20
+ raise Faraday::ForbiddenError, response_values(env)
21
+ when 404
22
+ raise Faraday::ResourceNotFound, response_values(env)
23
+ when 407
24
+ # mimic the behavior that we get with proxy requests with HTTPS
25
+ msg = %(407 "Proxy Authentication Required")
26
+ raise Faraday::ProxyAuthError.new(msg, response_values(env))
27
+ when 409
28
+ raise Faraday::ConflictError, response_values(env)
29
+ when 422
30
+ raise Faraday::UnprocessableEntityError, response_values(env)
31
+ when ClientErrorStatuses
32
+ raise Faraday::ClientError, response_values(env)
33
+ when ServerErrorStatuses
34
+ raise Faraday::ServerError, response_values(env)
35
+ when nil
36
+ raise Faraday::NilStatusError, response_values(env)
37
+ end
18
38
  end
19
- end
20
39
 
21
- def response_values(env)
22
- {:status => env.status, :headers => env.response_headers, :body => env.body}
40
+ def response_values(env)
41
+ {
42
+ status: env.status,
43
+ headers: env.response_headers,
44
+ body: env.body,
45
+ request: {
46
+ method: env.method,
47
+ url_path: env.url.path,
48
+ params: env.params,
49
+ headers: env.request_headers,
50
+ body: env.request_body
51
+ }
52
+ }
53
+ end
23
54
  end
24
55
  end
25
56
  end
@@ -1,28 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Faraday
6
+ # Response represents an HTTP response from making an HTTP request.
4
7
  class Response
5
8
  # Used for simple response middleware.
6
9
  class Middleware < Faraday::Middleware
7
- def call(env)
8
- @app.call(env).on_complete do |environment|
9
- on_complete(environment)
10
- end
11
- end
12
-
13
10
  # Override this to modify the environment after the response has finished.
14
11
  # Calls the `parse` method if defined
12
+ # `parse` method can be defined as private, public and protected
15
13
  def on_complete(env)
16
- env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
14
+ return unless respond_to?(:parse, true) && env.parse_body?
15
+
16
+ env.body = parse(env.body)
17
17
  end
18
18
  end
19
19
 
20
20
  extend Forwardable
21
21
  extend MiddlewareRegistry
22
22
 
23
- register_middleware File.expand_path('../response', __FILE__),
24
- :raise_error => [:RaiseError, 'raise_error'],
25
- :logger => [:Logger, 'logger']
23
+ register_middleware File.expand_path('response', __dir__),
24
+ raise_error: [:RaiseError, 'raise_error'],
25
+ logger: [:Logger, 'logger'],
26
+ json: [:Json, 'json']
26
27
 
27
28
  def initialize(env = nil)
28
29
  @env = Env.from(env) if env
@@ -31,8 +32,6 @@ module Faraday
31
32
 
32
33
  attr_reader :env
33
34
 
34
- def_delegators :env, :to_hash
35
-
36
35
  def status
37
36
  finished? ? env.status : nil
38
37
  end
@@ -44,6 +43,7 @@ module Faraday
44
43
  def headers
45
44
  finished? ? env.response_headers : {}
46
45
  end
46
+
47
47
  def_delegator :headers, :[]
48
48
 
49
49
  def body
@@ -60,26 +60,31 @@ module Faraday
60
60
  else
61
61
  yield(env)
62
62
  end
63
- return self
63
+ self
64
64
  end
65
65
 
66
66
  def finish(env)
67
- raise "response already finished" if finished?
67
+ raise 'response already finished' if finished?
68
+
68
69
  @env = env.is_a?(Env) ? env : Env.from(env)
69
70
  @on_complete_callbacks.each { |callback| callback.call(@env) }
70
- return self
71
+ self
71
72
  end
72
73
 
73
74
  def success?
74
75
  finished? && env.success?
75
76
  end
76
77
 
78
+ def to_hash
79
+ {
80
+ status: env.status, body: env.body,
81
+ response_headers: env.response_headers
82
+ }
83
+ end
84
+
77
85
  # because @on_complete_callbacks cannot be marshalled
78
86
  def marshal_dump
79
- !finished? ? nil : {
80
- :status => @env.status, :body => @env.body,
81
- :response_headers => @env.response_headers
82
- }
87
+ finished? ? to_hash : nil
83
88
  end
84
89
 
85
90
  def marshal_load(env)
@@ -90,8 +95,9 @@ module Faraday
90
95
  # Useful for applying request params after restoring a marshalled Response.
91
96
  def apply_request(request_env)
92
97
  raise "response didn't finish yet" unless finished?
98
+
93
99
  @env = Env.from(request_env).update(@env)
94
- return self
100
+ self
95
101
  end
96
102
  end
97
103
  end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ module Utils
5
+ # A case-insensitive Hash that preserves the original case of a header
6
+ # when set.
7
+ #
8
+ # Adapted from Rack::Utils::HeaderHash
9
+ class Headers < ::Hash
10
+ def self.from(value)
11
+ new(value)
12
+ end
13
+
14
+ def self.allocate
15
+ new_self = super
16
+ new_self.initialize_names
17
+ new_self
18
+ end
19
+
20
+ def initialize(hash = nil)
21
+ super()
22
+ @names = {}
23
+ update(hash || {})
24
+ end
25
+
26
+ def initialize_names
27
+ @names = {}
28
+ end
29
+
30
+ # on dup/clone, we need to duplicate @names hash
31
+ def initialize_copy(other)
32
+ super
33
+ @names = other.names.dup
34
+ end
35
+
36
+ # need to synchronize concurrent writes to the shared KeyMap
37
+ keymap_mutex = Mutex.new
38
+
39
+ # symbol -> string mapper + cache
40
+ KeyMap = Hash.new do |map, key|
41
+ value = if key.respond_to?(:to_str)
42
+ key
43
+ else
44
+ key.to_s.split('_') # user_agent: %w(user agent)
45
+ .each(&:capitalize!) # => %w(User Agent)
46
+ .join('-') # => "User-Agent"
47
+ end
48
+ keymap_mutex.synchronize { map[key] = value }
49
+ end
50
+ KeyMap[:etag] = 'ETag'
51
+
52
+ def [](key)
53
+ key = KeyMap[key]
54
+ super(key) || super(@names[key.downcase])
55
+ end
56
+
57
+ def []=(key, val)
58
+ key = KeyMap[key]
59
+ key = (@names[key.downcase] ||= key)
60
+ # join multiple values with a comma
61
+ val = val.to_ary.join(', ') if val.respond_to?(:to_ary)
62
+ super(key, val)
63
+ end
64
+
65
+ def fetch(key, *args, &block)
66
+ key = KeyMap[key]
67
+ key = @names.fetch(key.downcase, key)
68
+ super(key, *args, &block)
69
+ end
70
+
71
+ def delete(key)
72
+ key = KeyMap[key]
73
+ key = @names[key.downcase]
74
+ return unless key
75
+
76
+ @names.delete key.downcase
77
+ super(key)
78
+ end
79
+
80
+ def include?(key)
81
+ @names.include? key.downcase
82
+ end
83
+
84
+ alias has_key? include?
85
+ alias member? include?
86
+ alias key? include?
87
+
88
+ def merge!(other)
89
+ other.each { |k, v| self[k] = v }
90
+ self
91
+ end
92
+
93
+ alias update merge!
94
+
95
+ def merge(other)
96
+ hash = dup
97
+ hash.merge! other
98
+ end
99
+
100
+ def replace(other)
101
+ clear
102
+ @names.clear
103
+ update other
104
+ self
105
+ end
106
+
107
+ def to_hash
108
+ {}.update(self)
109
+ end
110
+
111
+ def parse(header_string)
112
+ return unless header_string && !header_string.empty?
113
+
114
+ headers = header_string.split(/\r\n/)
115
+
116
+ # Find the last set of response headers.
117
+ start_index = headers.rindex { |x| x.start_with?('HTTP/') } || 0
118
+ last_response = headers.slice(start_index, headers.size)
119
+
120
+ last_response
121
+ .tap { |a| a.shift if a.first.start_with?('HTTP/') }
122
+ .map { |h| h.split(/:\s*/, 2) } # split key and value
123
+ .reject { |p| p[0].nil? } # ignore blank lines
124
+ .each { |key, value| add_parsed(key, value) }
125
+ end
126
+
127
+ protected
128
+
129
+ attr_reader :names
130
+
131
+ private
132
+
133
+ # Join multiple values with a comma.
134
+ def add_parsed(key, value)
135
+ self[key] ? self[key] << ', ' << value : self[key] = value
136
+ end
137
+ end
138
+ end
139
+ end