rest-man 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{multi-matrix-test.yml → ci.yml} +10 -1
  3. data/.github/workflows/single-matrix-test.yml +1 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop-disables.yml +4 -29
  6. data/AUTHORS +5 -1
  7. data/CHANGELOG.md +14 -0
  8. data/Gemfile +3 -0
  9. data/README.md +30 -37
  10. data/Rakefile +1 -59
  11. data/_doc/lib/restman/abstract_response/_follow_redirection.rdoc +7 -0
  12. data/_doc/lib/restman/abstract_response/beautify_headers.rdoc +24 -0
  13. data/_doc/lib/restman/abstract_response/cookie_jar.rdoc +4 -0
  14. data/_doc/lib/restman/abstract_response/cookies.rdoc +12 -0
  15. data/_doc/lib/restman/abstract_response/follow_get_redirection.rdoc +2 -0
  16. data/_doc/lib/restman/abstract_response/follow_redirection.rdoc +2 -0
  17. data/_doc/lib/restman/abstract_response/headers.rdoc +2 -0
  18. data/_doc/lib/restman/abstract_response/response_set_vars.rdoc +5 -0
  19. data/_doc/lib/restman/abstract_response/return.rdoc +9 -0
  20. data/_doc/lib/restman/add_before_execution_proc.rdoc +2 -0
  21. data/_doc/lib/restman/create_log.rdoc +2 -0
  22. data/_doc/lib/restman/exception.rdoc +6 -0
  23. data/_doc/lib/restman/exceptions/timeout.rdoc +4 -0
  24. data/_doc/lib/restman/exceptions.rdoc +4 -0
  25. data/_doc/lib/restman/log=.rdoc +3 -0
  26. data/_doc/lib/restman/params_array/new.rdoc +20 -0
  27. data/_doc/lib/restman/params_array/process_pair.rdoc +4 -0
  28. data/_doc/lib/restman/params_array.rdoc +11 -0
  29. data/_doc/lib/restman/platform/jruby?.rdoc +4 -0
  30. data/_doc/lib/restman/platform/mac_mri?.rdoc +5 -0
  31. data/_doc/lib/restman/proxy.rdoc +2 -0
  32. data/_doc/lib/restman/proxy_set?.rdoc +5 -0
  33. data/_doc/lib/restman/raw_response/new.rdoc +6 -0
  34. data/_doc/lib/restman/raw_response.rdoc +10 -0
  35. data/_doc/lib/restman/request/cookie_jar.rdoc +3 -0
  36. data/_doc/lib/restman/request/cookies.rdoc +11 -0
  37. data/_doc/lib/restman/request/default_headers.rdoc +5 -0
  38. data/_doc/lib/restman/request/default_ssl_cert_store.rdoc +8 -0
  39. data/_doc/lib/restman/request/init/cookie_jar.rdoc +55 -0
  40. data/_doc/lib/restman/request/init/http_method.rdoc +15 -0
  41. data/_doc/lib/restman/request/make_cookie_header.rdoc +8 -0
  42. data/_doc/lib/restman/request/make_headers.rdoc +25 -0
  43. data/_doc/lib/restman/request/maybe_convert_extension.rdoc +18 -0
  44. data/_doc/lib/restman/request/process_result.rdoc +4 -0
  45. data/_doc/lib/restman/request/proxy_uri.rdoc +7 -0
  46. data/_doc/lib/restman/request/stringify_headers.rdoc +9 -0
  47. data/_doc/lib/restman/request/use_ssl.rdoc +4 -0
  48. data/_doc/lib/restman/request.rdoc +46 -0
  49. data/_doc/lib/restman/reset_before_execution_procs.rdoc +1 -0
  50. data/_doc/lib/restman/resource/[].rdoc +25 -0
  51. data/_doc/lib/restman/resource.rdoc +33 -0
  52. data/_doc/lib/restman/response/body.rdoc +7 -0
  53. data/_doc/lib/restman/response/create.rdoc +10 -0
  54. data/_doc/lib/restman/response/fix_encoding.rdoc +2 -0
  55. data/_doc/lib/restman/statuses.rdoc +11 -0
  56. data/_doc/lib/restman/utils/cgi_parse_header.rdoc +6 -0
  57. data/_doc/lib/restman/utils/encode_query_string.rdoc +90 -0
  58. data/_doc/lib/restman/utils/escape.rdoc +11 -0
  59. data/_doc/lib/restman/utils/flatten_params.rdoc +16 -0
  60. data/_doc/lib/restman/utils/get_encoding_from_headers.rdoc +24 -0
  61. data/_doc/lib/restman.rdoc +43 -0
  62. data/bin/console +15 -0
  63. data/lib/restman/abstract_response.rb +13 -60
  64. data/lib/restman/exception.rb +43 -0
  65. data/lib/restman/exceptions/exception_with_response.rb +7 -0
  66. data/lib/restman/exceptions/exceptions_map.rb +26 -0
  67. data/lib/restman/exceptions/request_failed.rb +15 -0
  68. data/lib/restman/exceptions/server_broke_connection.rb +13 -0
  69. data/lib/restman/exceptions/timeout.rb +37 -0
  70. data/lib/restman/params_array/process_pair.rb +39 -0
  71. data/lib/restman/params_array.rb +3 -48
  72. data/lib/restman/payload/base.rb +57 -0
  73. data/lib/restman/payload/multipart/write_content_disposition.rb +88 -0
  74. data/lib/restman/payload/multipart.rb +56 -0
  75. data/lib/restman/payload/streamed.rb +22 -0
  76. data/lib/restman/payload/url_encoded.rb +14 -0
  77. data/lib/restman/payload.rb +14 -196
  78. data/lib/restman/platform.rb +2 -18
  79. data/lib/restman/raw_response.rb +2 -14
  80. data/lib/restman/request/default_ssl_cert_store.rb +13 -0
  81. data/lib/restman/request/fetch_body_to_tempfile.rb +58 -0
  82. data/lib/restman/request/init/cookie_jar.rb +65 -0
  83. data/lib/restman/request/init/ssl_opts.rb +70 -0
  84. data/lib/restman/request/init/url/add_query_from_headers.rb +51 -0
  85. data/lib/restman/request/init/url/normalize_url.rb +19 -0
  86. data/lib/restman/request/init/url.rb +40 -0
  87. data/lib/restman/request/init.rb +106 -0
  88. data/lib/restman/request/log_request.rb +46 -0
  89. data/lib/restman/request/make_cookie_header.rb +16 -0
  90. data/lib/restman/request/make_headers.rb +39 -0
  91. data/lib/restman/request/maybe_convert_extension.rb +28 -0
  92. data/lib/restman/request/net_http_object.rb +25 -0
  93. data/lib/restman/request/process_result.rb +36 -0
  94. data/lib/restman/request/proxy_uri.rb +31 -0
  95. data/lib/restman/request/stringify_headers.rb +36 -0
  96. data/lib/restman/request/transmit.rb +152 -0
  97. data/lib/restman/request.rb +60 -745
  98. data/lib/restman/resource.rb +2 -60
  99. data/lib/restman/response.rb +3 -21
  100. data/lib/restman/statuses.rb +75 -0
  101. data/lib/restman/statuses_compatibility.rb +18 -0
  102. data/lib/restman/utils.rb +10 -206
  103. data/lib/restman/version.rb +1 -1
  104. data/lib/restman.rb +24 -62
  105. data/matrixeval.yml +19 -1
  106. data/rest-man.gemspec +4 -10
  107. data/spec/integration/capath_digicert/ce5e74ef.0 +1 -1
  108. data/spec/integration/request_spec.rb +13 -1
  109. data/spec/spec_helper.rb +11 -0
  110. data/spec/unit/abstract_response_spec.rb +14 -0
  111. data/spec/unit/exception_spec.rb +64 -0
  112. data/spec/unit/exceptions/backwards_campatibility_spec.rb +29 -0
  113. data/spec/unit/exceptions/exceptions_map_spec.rb +89 -0
  114. data/spec/unit/exceptions/request_failed_spec.rb +51 -0
  115. data/spec/unit/exceptions/server_broke_connection_spec.rb +8 -0
  116. data/spec/unit/exceptions/timeout_spec.rb +59 -0
  117. data/spec/unit/params_array/process_pair_spec.rb +59 -0
  118. data/spec/unit/params_array_spec.rb +15 -10
  119. data/spec/unit/payload/multipart_spec.rb +116 -0
  120. data/spec/unit/payload/streamed_spec.rb +48 -0
  121. data/spec/unit/payload/url_encoded_spec.rb +65 -0
  122. data/spec/unit/payload_spec.rb +0 -208
  123. data/spec/unit/request/init/url/add_query_from_headers_spec.rb +40 -0
  124. data/spec/unit/request/init/url/normalize_url_spec.rb +25 -0
  125. data/spec/unit/request/init_spec.rb +83 -0
  126. data/spec/unit/request_spec.rb +143 -151
  127. data/spec/unit/utils_spec.rb +96 -104
  128. metadata +132 -16
  129. data/lib/restman/exceptions.rb +0 -238
  130. data/lib/restman/windows/root_certs.rb +0 -105
  131. data/lib/restman/windows.rb +0 -8
  132. data/spec/unit/exceptions_spec.rb +0 -108
  133. data/spec/unit/windows/root_certs_spec.rb +0 -22
@@ -1,60 +1,32 @@
1
1
  require 'tempfile'
2
- require 'cgi'
3
- require 'netrc'
4
2
  require 'set'
5
-
6
- begin
7
- # Use mime/types/columnar if available, for reduced memory usage
8
- require 'mime/types/columnar'
9
- rescue LoadError
10
- require 'mime/types'
11
- end
3
+ require 'mime/types/columnar'
12
4
 
13
5
  module RestMan
14
- # This class is used internally by RestMan to send the request, but you can also
15
- # call it directly if you'd like to use a method not supported by the
16
- # main API. For example:
17
- #
18
- # RestMan::Request.execute(:method => :head, :url => 'http://example.com')
19
- #
20
- # Mandatory parameters:
21
- # * :method
22
- # * :url
23
- # Optional parameters (have a look at ssl and/or uri for some explanations):
24
- # * :headers a hash containing the request headers
25
- # * :cookies may be a Hash{String/Symbol => String} of cookie values, an
26
- # Array<HTTP::Cookie>, or an HTTP::CookieJar containing cookies. These
27
- # will be added to a cookie jar before the request is sent.
28
- # * :user and :password for basic auth, will be replaced by a user/password available in the :url
29
- # * :block_response call the provided block with the HTTPResponse as parameter
30
- # * :raw_response return a low-level RawResponse instead of a Response
31
- # * :log Set the log for this request only, overriding RestMan.log, if
32
- # any.
33
- # * :stream_log_percent (Only relevant with :raw_response => true) Customize
34
- # the interval at which download progress is logged. Defaults to every
35
- # 10% complete.
36
- # * :max_redirects maximum number of redirections (default to 10)
37
- # * :proxy An HTTP proxy URI to use for this request. Any value here
38
- # (including nil) will override RestMan.proxy.
39
- # * :verify_ssl enable ssl verification, possible values are constants from
40
- # OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER
41
- # * :read_timeout and :open_timeout are how long to wait for a response and
42
- # to open a connection, in seconds. Pass nil to disable the timeout.
43
- # * :timeout can be used to set both timeouts
44
- # * :ssl_client_cert, :ssl_client_key, :ssl_ca_file, :ssl_ca_path,
45
- # :ssl_cert_store, :ssl_verify_callback, :ssl_verify_callback_warnings
46
- # * :ssl_version specifies the SSL version for the underlying Net::HTTP connection
47
- # * :ssl_ciphers sets SSL ciphers for the connection. See
48
- # OpenSSL::SSL::SSLContext#ciphers=
49
- # * :before_execution_proc a Proc to call before executing the request. This
50
- # proc, like procs from RestMan.before_execution_procs, will be
51
- # called with the HTTP request and request params.
6
+ # :include: _doc/lib/restman/request.rdoc
52
7
  class Request
53
8
 
9
+ autoload :MaybeConvertExtension, 'restman/request/maybe_convert_extension'
10
+ autoload :StringifyHeaders, 'restman/request/stringify_headers'
11
+ autoload :MakeCookieHeader, 'restman/request/make_cookie_header'
12
+ autoload :MakeHeaders, 'restman/request/make_headers'
13
+ autoload :ProxyURI, 'restman/request/proxy_uri'
14
+ autoload :NetHTTPObject, 'restman/request/net_http_object'
15
+ autoload :DefaultSSLCertStore, 'restman/request/default_ssl_cert_store'
16
+ autoload :LogRequest, 'restman/request/log_request'
17
+ autoload :FetchBodyToTempfile, 'restman/request/fetch_body_to_tempfile'
18
+ autoload :ProcessResult, 'restman/request/process_result'
19
+
20
+ include ActiveMethod
21
+ include Init
22
+
54
23
  attr_reader :method, :uri, :url, :headers, :payload, :proxy,
55
24
  :user, :password, :read_timeout, :max_redirects,
56
25
  :open_timeout, :raw_response, :processed_headers, :args,
57
- :ssl_opts
26
+ :ssl_opts, :write_timeout, :max_retries, :keep_alive_timeout,
27
+ :close_on_empty_response, :local_host, :local_port,
28
+
29
+ :before_execution_proc, :block_response
58
30
 
59
31
  # An array of previous redirection responses
60
32
  attr_accessor :redirection_history
@@ -64,92 +36,37 @@ module RestMan
64
36
  end
65
37
 
66
38
  SSLOptionList = %w{client_cert client_key ca_file ca_path cert_store
67
- version ciphers verify_callback verify_callback_warnings}
39
+ version ciphers verify_callback verify_callback_warnings
40
+ min_version max_version timeout}
68
41
 
69
42
  def inspect
70
43
  "<RestMan::Request @method=#{@method.inspect}, @url=#{@url.inspect}>"
71
44
  end
72
45
 
73
46
  def initialize args
74
- @method = normalize_method(args[:method])
75
- @headers = (args[:headers] || {}).dup
76
- if args[:url]
77
- @url = process_url_params(normalize_url(args[:url]), headers)
78
- else
79
- raise ArgumentError, "must pass :url"
80
- end
81
-
82
- @user = @password = nil
83
- parse_url_with_auth!(url)
84
-
85
- # process cookie arguments found in headers or args
86
- @cookie_jar = process_cookie_args!(@uri, @headers, args)
87
-
47
+ @method = Init.http_method(args)
48
+ @headers = Init.headers(args)
49
+ @url = Init.url(args, headers)
50
+ @uri = Init.uri(url)
51
+ @user, @password = Init.auth(uri, args)
52
+ @cookie_jar = Init.cookie_jar(uri, headers, args)
88
53
  @payload = Payload.generate(args[:payload])
89
-
90
- @user = args[:user] if args.include?(:user)
91
- @password = args[:password] if args.include?(:password)
92
-
93
- if args.include?(:timeout)
94
- @read_timeout = args[:timeout]
95
- @open_timeout = args[:timeout]
96
- end
97
- if args.include?(:read_timeout)
98
- @read_timeout = args[:read_timeout]
99
- end
100
- if args.include?(:open_timeout)
101
- @open_timeout = args[:open_timeout]
102
- end
54
+ Init.read_timeout(args) {|value| @read_timeout = value}
55
+ Init.open_timeout(args) {|value| @open_timeout = value}
56
+ Init.write_timeout(args) {|value| @write_timeout = value}
103
57
  @block_response = args[:block_response]
104
58
  @raw_response = args[:raw_response] || false
105
-
106
- @stream_log_percent = args[:stream_log_percent] || 10
107
- if @stream_log_percent <= 0 || @stream_log_percent > 100
108
- raise ArgumentError.new(
109
- "Invalid :stream_log_percent #{@stream_log_percent.inspect}")
110
- end
111
-
59
+ @local_host = args[:local_host]
60
+ @local_port = args[:local_port]
61
+ Init.keep_alive_timeout(args) {|value| @keep_alive_timeout = value}
62
+ @close_on_empty_response = args[:close_on_empty_response]
63
+ @stream_log_percent = Init.stream_log_percent(args)
112
64
  @proxy = args.fetch(:proxy) if args.include?(:proxy)
113
-
114
- @ssl_opts = {}
115
-
116
- if args.include?(:verify_ssl)
117
- v_ssl = args.fetch(:verify_ssl)
118
- if v_ssl
119
- if v_ssl == true
120
- # interpret :verify_ssl => true as VERIFY_PEER
121
- @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
122
- else
123
- # otherwise pass through any truthy values
124
- @ssl_opts[:verify_ssl] = v_ssl
125
- end
126
- else
127
- # interpret all falsy :verify_ssl values as VERIFY_NONE
128
- @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
129
- end
130
- else
131
- # if :verify_ssl was not passed, default to VERIFY_PEER
132
- @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
133
- end
134
-
135
- SSLOptionList.each do |key|
136
- source_key = ('ssl_' + key).to_sym
137
- if args.has_key?(source_key)
138
- @ssl_opts[key.to_sym] = args.fetch(source_key)
139
- end
140
- end
141
-
142
- # Set some other default SSL options, but only if we have an HTTPS URI.
143
- if use_ssl?
144
-
145
- # If there's no CA file, CA path, or cert store provided, use default
146
- if !ssl_ca_file && !ssl_ca_path && !@ssl_opts.include?(:cert_store)
147
- @ssl_opts[:cert_store] = self.class.default_ssl_cert_store
148
- end
149
- end
65
+ @ssl_opts = Init.ssl_opts(args, uri)
150
66
 
151
67
  @log = args[:log]
152
68
  @max_redirects = args[:max_redirects] || 10
69
+ @max_retries = args[:max_retries] || 1
153
70
  @processed_headers = make_headers headers
154
71
  @processed_headers_lowercase = Hash[@processed_headers.map {|k, v| [k.downcase, v]}]
155
72
  @args = args
@@ -175,68 +92,12 @@ module RestMan
175
92
  end
176
93
  end
177
94
 
178
- # Return true if the request URI will use HTTPS.
179
- #
180
- # @return [Boolean]
181
- #
95
+ # :include: _doc/lib/restman/request/use_ssl.rdoc
182
96
  def use_ssl?
183
97
  uri.is_a?(URI::HTTPS)
184
98
  end
185
99
 
186
- # Extract the query parameters and append them to the url
187
- #
188
- # Look through the headers hash for a :params option (case-insensitive,
189
- # may be string or symbol). If present and the value is a Hash or
190
- # RestMan::ParamsArray, *delete* the key/value pair from the headers
191
- # hash and encode the value into a query string. Append this query string
192
- # to the URL and return the resulting URL.
193
- #
194
- # @param [String] url
195
- # @param [Hash] headers An options/headers hash to process. Mutation
196
- # warning: the params key may be removed if present!
197
- #
198
- # @return [String] resulting url with query string
199
- #
200
- def process_url_params(url, headers)
201
- url_params = nil
202
-
203
- # find and extract/remove "params" key if the value is a Hash/ParamsArray
204
- headers.delete_if do |key, value|
205
- if key.to_s.downcase == 'params' &&
206
- (value.is_a?(Hash) || value.is_a?(RestMan::ParamsArray))
207
- if url_params
208
- raise ArgumentError.new("Multiple 'params' options passed")
209
- end
210
- url_params = value
211
- true
212
- else
213
- false
214
- end
215
- end
216
-
217
- # build resulting URL with query string
218
- if url_params && !url_params.empty?
219
- query_string = RestMan::Utils.encode_query_string(url_params)
220
-
221
- if url.include?('?')
222
- url + '&' + query_string
223
- else
224
- url + '?' + query_string
225
- end
226
- else
227
- url
228
- end
229
- end
230
-
231
- # Render a hash of key => value pairs for cookies in the Request#cookie_jar
232
- # that are valid for the Request#uri. This will not necessarily include all
233
- # cookies if there are duplicate keys. It's safer to use the cookie_jar
234
- # directly if that's a concern.
235
- #
236
- # @see Request#cookie_jar
237
- #
238
- # @return [Hash]
239
- #
100
+ # :include: _doc/lib/restman/request/cookies.rdoc
240
101
  def cookies
241
102
  hash = {}
242
103
 
@@ -247,289 +108,29 @@ module RestMan
247
108
  hash
248
109
  end
249
110
 
250
- # @return [HTTP::CookieJar]
111
+ # :include: _doc/lib/restman/request/cookie_jar.rdoc
251
112
  def cookie_jar
252
113
  @cookie_jar
253
114
  end
254
115
 
255
- # Render a Cookie HTTP request header from the contents of the @cookie_jar,
256
- # or nil if the jar is empty.
257
- #
258
- # @see Request#cookie_jar
259
- #
260
- # @return [String, nil]
261
- #
262
- def make_cookie_header
263
- return nil if cookie_jar.nil?
264
-
265
- arr = cookie_jar.cookies(url)
266
- return nil if arr.empty?
267
-
268
- return HTTP::Cookie.cookie_value(arr)
269
- end
270
-
271
- # Process cookies passed as hash or as HTTP::CookieJar. For backwards
272
- # compatibility, these may be passed as a :cookies option masquerading
273
- # inside the headers hash. To avoid confusion, if :cookies is passed in
274
- # both headers and Request#initialize, raise an error.
275
- #
276
- # :cookies may be a:
277
- # - Hash{String/Symbol => String}
278
- # - Array<HTTP::Cookie>
279
- # - HTTP::CookieJar
280
- #
281
- # Passing as a hash:
282
- # Keys may be symbols or strings. Values must be strings.
283
- # Infer the domain name from the request URI and allow subdomains (as
284
- # though '.example.com' had been set in a Set-Cookie header). Assume a
285
- # path of '/'.
286
- #
287
- # RestMan::Request.new(url: 'http://example.com', method: :get,
288
- # :cookies => {:foo => 'Value', 'bar' => '123'}
289
- # )
290
- #
291
- # results in cookies as though set from the server by:
292
- # Set-Cookie: foo=Value; Domain=.example.com; Path=/
293
- # Set-Cookie: bar=123; Domain=.example.com; Path=/
294
- #
295
- # which yields a client cookie header of:
296
- # Cookie: foo=Value; bar=123
297
- #
298
- # Passing as HTTP::CookieJar, which will be passed through directly:
299
- #
300
- # jar = HTTP::CookieJar.new
301
- # jar.add(HTTP::Cookie.new('foo', 'Value', domain: 'example.com',
302
- # path: '/', for_domain: false))
303
- #
304
- # RestMan::Request.new(..., :cookies => jar)
305
- #
306
- # @param [URI::HTTP] uri The URI for the request. This will be used to
307
- # infer the domain name for cookies passed as strings in a hash. To avoid
308
- # this implicit behavior, pass a full cookie jar or use HTTP::Cookie hash
309
- # values.
310
- # @param [Hash] headers The headers hash from which to pull the :cookies
311
- # option. MUTATION NOTE: This key will be deleted from the hash if
312
- # present.
313
- # @param [Hash] args The options passed to Request#initialize. This hash
314
- # will be used as another potential source for the :cookies key.
315
- # These args will not be mutated.
316
- #
317
- # @return [HTTP::CookieJar] A cookie jar containing the parsed cookies.
318
- #
319
- def process_cookie_args!(uri, headers, args)
320
-
321
- # Avoid ambiguity in whether options from headers or options from
322
- # Request#initialize should take precedence by raising ArgumentError when
323
- # both are present. Prior versions of rest-man claimed to give
324
- # precedence to init options, but actually gave precedence to headers.
325
- # Avoid that mess by erroring out instead.
326
- if headers[:cookies] && args[:cookies]
327
- raise ArgumentError.new(
328
- "Cannot pass :cookies in Request.new() and in headers hash")
329
- end
330
-
331
- cookies_data = headers.delete(:cookies) || args[:cookies]
332
-
333
- # return copy of cookie jar as is
334
- if cookies_data.is_a?(HTTP::CookieJar)
335
- return cookies_data.dup
336
- end
337
-
338
- # convert cookies hash into a CookieJar
339
- jar = HTTP::CookieJar.new
340
-
341
- (cookies_data || []).each do |key, val|
342
-
343
- # Support for Array<HTTP::Cookie> mode:
344
- # If key is a cookie object, add it to the jar directly and assert that
345
- # there is no separate val.
346
- if key.is_a?(HTTP::Cookie)
347
- if val
348
- raise ArgumentError.new("extra cookie val: #{val.inspect}")
349
- end
350
-
351
- jar.add(key)
352
- next
353
- end
354
-
355
- if key.is_a?(Symbol)
356
- key = key.to_s
357
- end
358
-
359
- # assume implicit domain from the request URI, and set for_domain to
360
- # permit subdomains
361
- jar.add(HTTP::Cookie.new(key, val, domain: uri.hostname.downcase,
362
- path: '/', for_domain: true))
363
- end
364
-
365
- jar
366
- end
367
-
368
- # Generate headers for use by a request. Header keys will be stringified
369
- # using `#stringify_headers` to normalize them as capitalized strings.
370
- #
371
- # The final headers consist of:
372
- # - default headers from #default_headers
373
- # - user_headers provided here
374
- # - headers from the payload object (e.g. Content-Type, Content-Lenth)
375
- # - cookie headers from #make_cookie_header
376
- #
377
- # BUG: stringify_headers does not alter the capitalization of headers that
378
- # are passed as strings, it only normalizes those passed as symbols. This
379
- # behavior will probably remain for a while for compatibility, but it means
380
- # that the warnings that attempt to detect accidental header overrides may
381
- # not always work.
382
- # https://github.com/rest-man/rest-man/issues/599
383
- #
384
- # @param [Hash] user_headers User-provided headers to include
385
- #
386
- # @return [Hash<String, String>] A hash of HTTP headers => values
387
- #
388
- def make_headers(user_headers)
389
- headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
390
-
391
- # override headers from the payload (e.g. Content-Type, Content-Length)
392
- if @payload
393
- payload_headers = @payload.headers
394
-
395
- # Warn the user if we override any headers that were previously
396
- # present. This usually indicates that rest-man was passed
397
- # conflicting information, e.g. if it was asked to render a payload as
398
- # x-www-form-urlencoded but a Content-Type application/json was
399
- # also supplied by the user.
400
- payload_headers.each_pair do |key, val|
401
- if headers.include?(key) && headers[key] != val
402
- warn("warning: Overriding #{key.inspect} header " +
403
- "#{headers.fetch(key).inspect} with #{val.inspect} " +
404
- "due to payload")
405
- end
406
- end
407
-
408
- headers.merge!(payload_headers)
409
- end
410
-
411
- # merge in cookies
412
- cookies = make_cookie_header
413
- if cookies && !cookies.empty?
414
- if headers['Cookie']
415
- warn('warning: overriding "Cookie" header with :cookies option')
416
- end
417
- headers['Cookie'] = cookies
418
- end
419
-
420
- headers
421
- end
422
-
423
- # The proxy URI for this request. If `:proxy` was provided on this request,
424
- # use it over `RestMan.proxy`.
425
- #
426
- # Return false if a proxy was explicitly set and is falsy.
427
- #
428
- # @return [URI, false, nil]
429
- #
430
- def proxy_uri
431
- if defined?(@proxy)
432
- if @proxy
433
- URI.parse(@proxy)
434
- else
435
- false
436
- end
437
- elsif RestMan.proxy_set?
438
- if RestMan.proxy
439
- URI.parse(RestMan.proxy)
440
- else
441
- false
442
- end
443
- else
444
- nil
445
- end
446
- end
116
+ # :include: _doc/lib/restman/request/make_cookie_header.rdoc
117
+ active_method :make_cookie_header
447
118
 
448
- def net_http_object(hostname, port)
449
- p_uri = proxy_uri
119
+ # :include: _doc/lib/restman/request/make_headers.rdoc
120
+ active_method :make_headers
450
121
 
451
- if p_uri.nil?
452
- # no proxy set
453
- Net::HTTP.new(hostname, port)
454
- elsif !p_uri
455
- # proxy explicitly set to none
456
- Net::HTTP.new(hostname, port, nil, nil, nil, nil)
457
- else
458
- Net::HTTP.new(hostname, port,
459
- p_uri.hostname, p_uri.port, p_uri.user, p_uri.password)
122
+ # :include: _doc/lib/restman/request/proxy_uri.rdoc
123
+ active_method :proxy_uri, ProxyURI
460
124
 
461
- end
462
- end
125
+ active_method :net_http_object, NetHTTPObject
463
126
 
464
127
  def net_http_request_class(method)
465
128
  Net::HTTP.const_get(method.capitalize, false)
466
129
  end
467
130
 
468
- def net_http_do_request(http, req, body=nil, &block)
469
- if body && body.respond_to?(:read)
470
- req.body_stream = body
471
- return http.request(req, nil, &block)
472
- else
473
- return http.request(req, body, &block)
474
- end
475
- end
476
-
477
- # Normalize a URL by adding a protocol if none is present.
478
- #
479
- # If the string has no HTTP-like scheme (i.e. scheme followed by '//'), a
480
- # scheme of 'http' will be added. This mimics the behavior of browsers and
481
- # user agents like cURL.
482
- #
483
- # @param [String] url A URL string.
484
- #
485
- # @return [String]
486
- #
487
- def normalize_url(url)
488
- url = 'http://' + url unless url.match(%r{\A[a-z][a-z0-9+.-]*://}i)
489
- url
490
- end
491
-
492
- # Return a certificate store that can be used to validate certificates with
493
- # the system certificate authorities. This will probably not do anything on
494
- # OS X, which monkey patches OpenSSL in terrible ways to insert its own
495
- # validation. On most *nix platforms, this will add the system certifcates
496
- # using OpenSSL::X509::Store#set_default_paths. On Windows, this will use
497
- # RestMan::Windows::RootCerts to look up the CAs trusted by the system.
498
- #
499
- # @return [OpenSSL::X509::Store]
500
- #
131
+ # :include: _doc/lib/restman/request/default_ssl_cert_store.rdoc
501
132
  def self.default_ssl_cert_store
502
- cert_store = OpenSSL::X509::Store.new
503
- cert_store.set_default_paths
504
-
505
- # set_default_paths() doesn't do anything on Windows, so look up
506
- # certificates using the win32 API.
507
- if RestMan::Platform.windows?
508
- RestMan::Windows::RootCerts.instance.to_a.uniq.each do |cert|
509
- begin
510
- cert_store.add_cert(cert)
511
- rescue OpenSSL::X509::StoreError => err
512
- # ignore duplicate certs
513
- raise unless err.message == 'cert already in hash table'
514
- end
515
- end
516
- end
517
-
518
- cert_store
519
- end
520
-
521
- def redacted_uri
522
- if uri.password
523
- sanitized_uri = uri.dup
524
- sanitized_uri.password = 'REDACTED'
525
- sanitized_uri
526
- else
527
- uri
528
- end
529
- end
530
-
531
- def redacted_url
532
- redacted_uri.to_s
133
+ DefaultSSLCertStore.call
533
134
  end
534
135
 
535
136
  # Default to the global logger if there's not a request-specific one
@@ -537,52 +138,12 @@ module RestMan
537
138
  @log || RestMan.log
538
139
  end
539
140
 
540
- def log_request
541
- return unless log
542
-
543
- out = []
141
+ active_method :log_request
544
142
 
545
- out << "RestMan.#{method} #{redacted_url.inspect}"
546
- out << payload.short_inspect if payload
547
- out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
548
- log << out.join(', ') + "\n"
549
- end
550
-
551
- # Return a hash of headers whose keys are capitalized strings
552
- #
553
- # BUG: stringify_headers does not fix the capitalization of headers that
554
- # are already Strings. Leaving this behavior as is for now for
555
- # backwards compatibility.
556
- # https://github.com/rest-man/rest-man/issues/599
557
- #
558
- def stringify_headers headers
559
- headers.inject({}) do |result, (key, value)|
560
- if key.is_a? Symbol
561
- key = key.to_s.split(/_/).map(&:capitalize).join('-')
562
- end
563
- if 'CONTENT-TYPE' == key.upcase
564
- result[key] = maybe_convert_extension(value.to_s)
565
- elsif 'ACCEPT' == key.upcase
566
- # Accept can be composed of several comma-separated values
567
- if value.is_a? Array
568
- target_values = value
569
- else
570
- target_values = value.to_s.split ','
571
- end
572
- result[key] = target_values.map { |ext|
573
- maybe_convert_extension(ext.to_s.strip)
574
- }.join(', ')
575
- else
576
- result[key] = value.to_s
577
- end
578
- result
579
- end
580
- end
143
+ # :include: _doc/lib/restman/request/stringify_headers.rdoc
144
+ active_method :stringify_headers
581
145
 
582
- # Default headers set by RestMan. In addition to these headers, servers
583
- # will receive headers set by Net::HTTP, such as Accept-Encoding and Host.
584
- #
585
- # @return [Hash<Symbol, String>]
146
+ # :include: _doc/lib/restman/request/default_headers.rdoc
586
147
  def default_headers
587
148
  {
588
149
  :accept => '*/*',
@@ -590,173 +151,7 @@ module RestMan
590
151
  }
591
152
  end
592
153
 
593
- private
594
-
595
- # Parse the `@url` string into a URI object and save it as
596
- # `@uri`. Also save any basic auth user or password as @user and @password.
597
- # If no auth info was passed, check for credentials in a Netrc file.
598
- #
599
- # @param [String] url A URL string.
600
- #
601
- # @return [URI]
602
- #
603
- # @raise URI::InvalidURIError on invalid URIs
604
- #
605
- def parse_url_with_auth!(url)
606
- uri = URI.parse(url)
607
-
608
- if uri.hostname.nil?
609
- raise URI::InvalidURIError.new("bad URI(no host provided): #{url}")
610
- end
611
-
612
- @user = CGI.unescape(uri.user) if uri.user
613
- @password = CGI.unescape(uri.password) if uri.password
614
- if !@user && !@password
615
- @user, @password = Netrc.read[uri.hostname]
616
- end
617
-
618
- @uri = uri
619
- end
620
-
621
- def print_verify_callback_warnings
622
- warned = false
623
- if RestMan::Platform.mac_mri?
624
- warn('warning: ssl_verify_callback return code is ignored on OS X')
625
- warned = true
626
- end
627
- if RestMan::Platform.jruby?
628
- warn('warning: SSL verify_callback may not work correctly in jruby')
629
- warn('see https://github.com/jruby/jruby/issues/597')
630
- warned = true
631
- end
632
- warned
633
- end
634
-
635
- # Parse a method and return a normalized string version.
636
- #
637
- # Raise ArgumentError if the method is falsy, but otherwise do no
638
- # validation.
639
- #
640
- # @param method [String, Symbol]
641
- #
642
- # @return [String]
643
- #
644
- # @see net_http_request_class
645
- #
646
- def normalize_method(method)
647
- raise ArgumentError.new('must pass :method') unless method
648
- method.to_s.downcase
649
- end
650
-
651
- def transmit uri, req, payload, & block
652
-
653
- # We set this to true in the net/http block so that we can distinguish
654
- # read_timeout from open_timeout. Now that we only support Ruby 2.0+,
655
- # this is only needed for Timeout exceptions thrown outside of Net::HTTP.
656
- established_connection = false
657
-
658
- setup_credentials req
659
-
660
- net = net_http_object(uri.hostname, uri.port)
661
- net.use_ssl = uri.is_a?(URI::HTTPS)
662
- net.ssl_version = ssl_version if ssl_version
663
- net.ciphers = ssl_ciphers if ssl_ciphers
664
-
665
- net.verify_mode = verify_ssl
666
-
667
- net.cert = ssl_client_cert if ssl_client_cert
668
- net.key = ssl_client_key if ssl_client_key
669
- net.ca_file = ssl_ca_file if ssl_ca_file
670
- net.ca_path = ssl_ca_path if ssl_ca_path
671
- net.cert_store = ssl_cert_store if ssl_cert_store
672
-
673
- # We no longer rely on net.verify_callback for the main SSL verification
674
- # because it's not well supported on all platforms (see comments below).
675
- # But do allow users to set one if they want.
676
- if ssl_verify_callback
677
- net.verify_callback = ssl_verify_callback
678
-
679
- # Hilariously, jruby only calls the callback when cert_store is set to
680
- # something, so make sure to set one.
681
- # https://github.com/jruby/jruby/issues/597
682
- if RestMan::Platform.jruby?
683
- net.cert_store ||= OpenSSL::X509::Store.new
684
- end
685
-
686
- if ssl_verify_callback_warnings != false
687
- if print_verify_callback_warnings
688
- warn('pass :ssl_verify_callback_warnings => false to silence this')
689
- end
690
- end
691
- end
692
-
693
- if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
694
- warn('WARNING: OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE')
695
- warn('This dangerous monkey patch leaves you open to MITM attacks!')
696
- warn('Try passing :verify_ssl => false instead.')
697
- end
698
-
699
- if defined? @read_timeout
700
- if @read_timeout == -1
701
- warn 'Deprecated: to disable timeouts, please use nil instead of -1'
702
- @read_timeout = nil
703
- end
704
- net.read_timeout = @read_timeout
705
- end
706
- if defined? @open_timeout
707
- if @open_timeout == -1
708
- warn 'Deprecated: to disable timeouts, please use nil instead of -1'
709
- @open_timeout = nil
710
- end
711
- net.open_timeout = @open_timeout
712
- end
713
-
714
- RestMan.before_execution_procs.each do |before_proc|
715
- before_proc.call(req, args)
716
- end
717
-
718
- if @before_execution_proc
719
- @before_execution_proc.call(req, args)
720
- end
721
-
722
- log_request
723
-
724
- start_time = Time.now
725
- tempfile = nil
726
-
727
- net.start do |http|
728
- established_connection = true
729
-
730
- if @block_response
731
- net_http_do_request(http, req, payload, &@block_response)
732
- else
733
- res = net_http_do_request(http, req, payload) { |http_response|
734
- if @raw_response
735
- # fetch body into tempfile
736
- tempfile = fetch_body_to_tempfile(http_response)
737
- else
738
- # fetch body
739
- http_response.read_body
740
- end
741
- http_response
742
- }
743
- process_result(res, start_time, tempfile, &block)
744
- end
745
- end
746
- rescue EOFError
747
- raise RestMan::ServerBrokeConnection
748
- rescue Net::OpenTimeout => err
749
- raise RestMan::Exceptions::OpenTimeout.new(nil, err)
750
- rescue Net::ReadTimeout => err
751
- raise RestMan::Exceptions::ReadTimeout.new(nil, err)
752
- rescue Timeout::Error, Errno::ETIMEDOUT => err
753
- # handling for non-Net::HTTP timeouts
754
- if established_connection
755
- raise RestMan::Exceptions::ReadTimeout.new(nil, err)
756
- else
757
- raise RestMan::Exceptions::OpenTimeout.new(nil, err)
758
- end
759
- end
154
+ active_method :transmit
760
155
 
761
156
  def setup_credentials(req)
762
157
  if user && !@processed_headers_lowercase.include?('authorization')
@@ -764,96 +159,16 @@ module RestMan
764
159
  end
765
160
  end
766
161
 
767
- def fetch_body_to_tempfile(http_response)
768
- # Taken from Chef, which as in turn...
769
- # Stolen from http://www.ruby-forum.com/topic/166423
770
- # Kudos to _why!
771
- tf = Tempfile.new('rest-man.')
772
- tf.binmode
773
-
774
- size = 0
775
- total = http_response['Content-Length'].to_i
776
- stream_log_bucket = nil
777
-
778
- http_response.read_body do |chunk|
779
- tf.write chunk
780
- size += chunk.size
781
- if log
782
- if total == 0
783
- log << "streaming %s %s (%d of unknown) [0 Content-Length]\n" % [@method.upcase, @url, size]
784
- else
785
- percent = (size * 100) / total
786
- current_log_bucket, _ = percent.divmod(@stream_log_percent)
787
- if current_log_bucket != stream_log_bucket
788
- stream_log_bucket = current_log_bucket
789
- log << "streaming %s %s %d%% done (%d of %d)\n" % [@method.upcase, @url, (size * 100) / total, size, total]
790
- end
791
- end
792
- end
793
- end
794
- tf.close
795
- tf
796
- end
797
-
798
- # @param res The Net::HTTP response object
799
- # @param start_time [Time] Time of request start
800
- def process_result(res, start_time, tempfile=nil, &block)
801
- if @raw_response
802
- unless tempfile
803
- raise ArgumentError.new('tempfile is required')
804
- end
805
- response = RawResponse.new(tempfile, res, self, start_time)
806
- else
807
- response = Response.create(res.body, res, self, start_time)
808
- end
809
-
810
- response.log_response
162
+ active_method :fetch_body_to_tempfile
811
163
 
812
- if block_given?
813
- block.call(response, self, res, & block)
814
- else
815
- response.return!(&block)
816
- end
817
-
818
- end
164
+ # :include: _doc/lib/restman/request/process_result.rdoc
165
+ active_method :process_result
819
166
 
820
167
  def parser
821
168
  URI.const_defined?(:Parser) ? URI::Parser.new : URI
822
169
  end
823
170
 
824
- # Given a MIME type or file extension, return either a MIME type or, if
825
- # none is found, the input unchanged.
826
- #
827
- # >> maybe_convert_extension('json')
828
- # => 'application/json'
829
- #
830
- # >> maybe_convert_extension('unknown')
831
- # => 'unknown'
832
- #
833
- # >> maybe_convert_extension('application/xml')
834
- # => 'application/xml'
835
- #
836
- # @param ext [String]
837
- #
838
- # @return [String]
839
- #
840
- def maybe_convert_extension(ext)
841
- unless ext =~ /\A[a-zA-Z0-9_@-]+\z/
842
- # Don't look up strings unless they look like they could be a file
843
- # extension known to mime-types.
844
- #
845
- # There currently isn't any API public way to look up extensions
846
- # directly out of MIME::Types, but the type_for() method only strips
847
- # off after a period anyway.
848
- return ext
849
- end
850
-
851
- types = MIME::Types.type_for(ext)
852
- if types.empty?
853
- ext
854
- else
855
- types.first.content_type
856
- end
857
- end
171
+ # :include: _doc/lib/restman/request/maybe_convert_extension.rdoc
172
+ active_method :maybe_convert_extension
858
173
  end
859
174
  end