/lib/httparty.rb

http://github.com/jnunemaker/httparty · Ruby · 668 lines · 267 code · 69 blank · 332 comment · 13 complexity · 8b4c0910e6d86dfaba6c5cb367420be2 MD5 · raw file

  1. require 'pathname'
  2. require 'net/http'
  3. require 'net/https'
  4. require 'uri'
  5. require 'zlib'
  6. require 'multi_xml'
  7. require 'mime/types'
  8. require 'json'
  9. require 'csv'
  10. require 'httparty/module_inheritable_attributes'
  11. require 'httparty/cookie_hash'
  12. require 'httparty/net_digest_auth'
  13. require 'httparty/version'
  14. require 'httparty/connection_adapter'
  15. require 'httparty/logger/logger'
  16. require 'httparty/request/body'
  17. require 'httparty/response_fragment'
  18. require 'httparty/text_encoder'
  19. require 'httparty/headers_processor'
  20. # @see HTTParty::ClassMethods
  21. module HTTParty
  22. def self.included(base)
  23. base.extend ClassMethods
  24. base.send :include, ModuleInheritableAttributes
  25. base.send(:mattr_inheritable, :default_options)
  26. base.send(:mattr_inheritable, :default_cookies)
  27. base.instance_variable_set("@default_options", {})
  28. base.instance_variable_set("@default_cookies", CookieHash.new)
  29. end
  30. # == Common Request Options
  31. # Request methods (get, post, patch, put, delete, head, options) all take a common set of options. These are:
  32. #
  33. # [:+body+:] Body of the request. If passed an object that responds to #to_hash, will try to normalize it first, by default passing it to ActiveSupport::to_params. Any other kind of object will get used as-is.
  34. # [:+http_proxyaddr+:] Address of proxy server to use.
  35. # [:+http_proxyport+:] Port of proxy server to use.
  36. # [:+http_proxyuser+:] User for proxy server authentication.
  37. # [:+http_proxypass+:] Password for proxy server authentication.
  38. # [:+limit+:] Maximum number of redirects to follow. Takes precedences over :+no_follow+.
  39. # [:+query+:] Query string, or an object that responds to #to_hash representing it. Normalized according to the same rules as :+body+. If you specify this on a POST, you must use an object which responds to #to_hash. See also HTTParty::ClassMethods.default_params.
  40. # [:+timeout+:] Timeout for opening connection and reading data.
  41. # [:+local_host+:] Local address to bind to before connecting.
  42. # [:+local_port+:] Local port to bind to before connecting.
  43. # [:+body_stream+:] Allow streaming to a REST server to specify a body_stream.
  44. # [:+stream_body+:] Allow for streaming large files without loading them into memory.
  45. # [:+multipart+:] Force content-type to be multipart
  46. #
  47. # There are also another set of options with names corresponding to various class methods. The methods in question are those that let you set a class-wide default, and the options override the defaults on a request-by-request basis. Those options are:
  48. # * :+base_uri+: see HTTParty::ClassMethods.base_uri.
  49. # * :+basic_auth+: see HTTParty::ClassMethods.basic_auth. Only one of :+basic_auth+ and :+digest_auth+ can be used at a time; if you try using both, you'll get an ArgumentError.
  50. # * :+debug_output+: see HTTParty::ClassMethods.debug_output.
  51. # * :+digest_auth+: see HTTParty::ClassMethods.digest_auth. Only one of :+basic_auth+ and :+digest_auth+ can be used at a time; if you try using both, you'll get an ArgumentError.
  52. # * :+format+: see HTTParty::ClassMethods.format.
  53. # * :+headers+: see HTTParty::ClassMethods.headers. Must be a an object which responds to #to_hash.
  54. # * :+maintain_method_across_redirects+: see HTTParty::ClassMethods.maintain_method_across_redirects.
  55. # * :+no_follow+: see HTTParty::ClassMethods.no_follow.
  56. # * :+parser+: see HTTParty::ClassMethods.parser.
  57. # * :+uri_adapter+: see HTTParty::ClassMethods.uri_adapter
  58. # * :+connection_adapter+: see HTTParty::ClassMethods.connection_adapter.
  59. # * :+pem+: see HTTParty::ClassMethods.pem.
  60. # * :+query_string_normalizer+: see HTTParty::ClassMethods.query_string_normalizer
  61. # * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
  62. # * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
  63. module ClassMethods
  64. # Turns on logging
  65. #
  66. # class Foo
  67. # include HTTParty
  68. # logger Logger.new('http_logger'), :info, :apache
  69. # end
  70. def logger(logger, level = :info, format = :apache)
  71. default_options[:logger] = logger
  72. default_options[:log_level] = level
  73. default_options[:log_format] = format
  74. end
  75. # Raises HTTParty::ResponseError if response's code matches this statuses
  76. #
  77. # class Foo
  78. # include HTTParty
  79. # raise_on [404, 500]
  80. # end
  81. def raise_on(codes = [])
  82. default_options[:raise_on] = *codes
  83. end
  84. # Allows setting http proxy information to be used
  85. #
  86. # class Foo
  87. # include HTTParty
  88. # http_proxy 'http://foo.com', 80, 'user', 'pass'
  89. # end
  90. def http_proxy(addr = nil, port = nil, user = nil, pass = nil)
  91. default_options[:http_proxyaddr] = addr
  92. default_options[:http_proxyport] = port
  93. default_options[:http_proxyuser] = user
  94. default_options[:http_proxypass] = pass
  95. end
  96. # Allows setting a base uri to be used for each request.
  97. # Will normalize uri to include http, etc.
  98. #
  99. # class Foo
  100. # include HTTParty
  101. # base_uri 'twitter.com'
  102. # end
  103. def base_uri(uri = nil)
  104. return default_options[:base_uri] unless uri
  105. default_options[:base_uri] = HTTParty.normalize_base_uri(uri)
  106. end
  107. # Allows setting basic authentication username and password.
  108. #
  109. # class Foo
  110. # include HTTParty
  111. # basic_auth 'username', 'password'
  112. # end
  113. def basic_auth(u, p)
  114. default_options[:basic_auth] = {username: u, password: p}
  115. end
  116. # Allows setting digest authentication username and password.
  117. #
  118. # class Foo
  119. # include HTTParty
  120. # digest_auth 'username', 'password'
  121. # end
  122. def digest_auth(u, p)
  123. default_options[:digest_auth] = {username: u, password: p}
  124. end
  125. # Do not send rails style query strings.
  126. # Specifically, don't use bracket notation when sending an array
  127. #
  128. # For a query:
  129. # get '/', query: {selected_ids: [1,2,3]}
  130. #
  131. # The default query string looks like this:
  132. # /?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3
  133. #
  134. # Call `disable_rails_query_string_format` to transform the query string
  135. # into:
  136. # /?selected_ids=1&selected_ids=2&selected_ids=3
  137. #
  138. # @example
  139. # class Foo
  140. # include HTTParty
  141. # disable_rails_query_string_format
  142. # end
  143. def disable_rails_query_string_format
  144. query_string_normalizer Request::NON_RAILS_QUERY_STRING_NORMALIZER
  145. end
  146. # Allows setting default parameters to be appended to each request.
  147. # Great for api keys and such.
  148. #
  149. # class Foo
  150. # include HTTParty
  151. # default_params api_key: 'secret', another: 'foo'
  152. # end
  153. def default_params(h = {})
  154. raise ArgumentError, 'Default params must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)
  155. default_options[:default_params] ||= {}
  156. default_options[:default_params].merge!(h)
  157. end
  158. # Allows setting a default timeout for all HTTP calls
  159. # Timeout is specified in seconds.
  160. #
  161. # class Foo
  162. # include HTTParty
  163. # default_timeout 10
  164. # end
  165. def default_timeout(value)
  166. validate_timeout_argument(__method__, value)
  167. default_options[:timeout] = value
  168. end
  169. # Allows setting a default open_timeout for all HTTP calls in seconds
  170. #
  171. # class Foo
  172. # include HTTParty
  173. # open_timeout 10
  174. # end
  175. def open_timeout(value)
  176. validate_timeout_argument(__method__, value)
  177. default_options[:open_timeout] = value
  178. end
  179. # Allows setting a default read_timeout for all HTTP calls in seconds
  180. #
  181. # class Foo
  182. # include HTTParty
  183. # read_timeout 10
  184. # end
  185. def read_timeout(value)
  186. validate_timeout_argument(__method__, value)
  187. default_options[:read_timeout] = value
  188. end
  189. # Allows setting a default write_timeout for all HTTP calls in seconds
  190. # Supported by Ruby > 2.6.0
  191. #
  192. # class Foo
  193. # include HTTParty
  194. # write_timeout 10
  195. # end
  196. def write_timeout(value)
  197. validate_timeout_argument(__method__, value)
  198. default_options[:write_timeout] = value
  199. end
  200. # Set an output stream for debugging, defaults to $stderr.
  201. # The output stream is passed on to Net::HTTP#set_debug_output.
  202. #
  203. # class Foo
  204. # include HTTParty
  205. # debug_output $stderr
  206. # end
  207. def debug_output(stream = $stderr)
  208. default_options[:debug_output] = stream
  209. end
  210. # Allows setting HTTP headers to be used for each request.
  211. #
  212. # class Foo
  213. # include HTTParty
  214. # headers 'Accept' => 'text/html'
  215. # end
  216. def headers(h = nil)
  217. if h
  218. raise ArgumentError, 'Headers must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)
  219. default_options[:headers] ||= {}
  220. default_options[:headers].merge!(h.to_hash)
  221. else
  222. default_options[:headers] || {}
  223. end
  224. end
  225. def cookies(h = {})
  226. raise ArgumentError, 'Cookies must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)
  227. default_cookies.add_cookies(h)
  228. end
  229. # Proceed to the location header when an HTTP response dictates a redirect.
  230. # Redirects are always followed by default.
  231. #
  232. # @example
  233. # class Foo
  234. # include HTTParty
  235. # base_uri 'http://google.com'
  236. # follow_redirects true
  237. # end
  238. def follow_redirects(value = true)
  239. default_options[:follow_redirects] = value
  240. end
  241. # Allows setting the format with which to parse.
  242. # Must be one of the allowed formats ie: json, xml
  243. #
  244. # class Foo
  245. # include HTTParty
  246. # format :json
  247. # end
  248. def format(f = nil)
  249. if f.nil?
  250. default_options[:format]
  251. else
  252. parser(Parser) if parser.nil?
  253. default_options[:format] = f
  254. validate_format
  255. end
  256. end
  257. # Declare whether or not to follow redirects. When true, an
  258. # {HTTParty::RedirectionTooDeep} error will raise upon encountering a
  259. # redirect. You can then gain access to the response object via
  260. # HTTParty::RedirectionTooDeep#response.
  261. #
  262. # @see HTTParty::ResponseError#response
  263. #
  264. # @example
  265. # class Foo
  266. # include HTTParty
  267. # base_uri 'http://google.com'
  268. # no_follow true
  269. # end
  270. #
  271. # begin
  272. # Foo.get('/')
  273. # rescue HTTParty::RedirectionTooDeep => e
  274. # puts e.response.body
  275. # end
  276. def no_follow(value = false)
  277. default_options[:no_follow] = value
  278. end
  279. # Declare that you wish to maintain the chosen HTTP method across redirects.
  280. # The default behavior is to follow redirects via the GET method, except
  281. # if you are making a HEAD request, in which case the default is to
  282. # follow all redirects with HEAD requests.
  283. # If you wish to maintain the original method, you can set this option to true.
  284. #
  285. # @example
  286. # class Foo
  287. # include HTTParty
  288. # base_uri 'http://google.com'
  289. # maintain_method_across_redirects true
  290. # end
  291. def maintain_method_across_redirects(value = true)
  292. default_options[:maintain_method_across_redirects] = value
  293. end
  294. # Declare that you wish to resend the full HTTP request across redirects,
  295. # even on redirects that should logically become GET requests.
  296. # A 303 redirect in HTTP signifies that the redirected url should normally
  297. # retrieved using a GET request, for instance, it is the output of a previous
  298. # POST. maintain_method_across_redirects respects this behavior, but you
  299. # can force HTTParty to resend_on_redirect even on 303 responses.
  300. #
  301. # @example
  302. # class Foo
  303. # include HTTParty
  304. # base_uri 'http://google.com'
  305. # resend_on_redirect
  306. # end
  307. def resend_on_redirect(value = true)
  308. default_options[:resend_on_redirect] = value
  309. end
  310. # Allows setting a PEM file to be used
  311. #
  312. # class Foo
  313. # include HTTParty
  314. # pem File.read('/home/user/my.pem'), "optional password"
  315. # end
  316. def pem(pem_contents, password = nil)
  317. default_options[:pem] = pem_contents
  318. default_options[:pem_password] = password
  319. end
  320. # Allows setting a PKCS12 file to be used
  321. #
  322. # class Foo
  323. # include HTTParty
  324. # pkcs12 File.read('/home/user/my.p12'), "password"
  325. # end
  326. def pkcs12(p12_contents, password)
  327. default_options[:p12] = p12_contents
  328. default_options[:p12_password] = password
  329. end
  330. # Override the way query strings are normalized.
  331. # Helpful for overriding the default rails normalization of Array queries.
  332. #
  333. # For a query:
  334. # get '/', query: {selected_ids: [1,2,3]}
  335. #
  336. # The default query string normalizer returns:
  337. # /?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3
  338. #
  339. # Let's change it to this:
  340. # /?selected_ids=1&selected_ids=2&selected_ids=3
  341. #
  342. # Pass a Proc to the query normalizer which accepts the yielded query.
  343. #
  344. # @example Modifying Array query strings
  345. # class ServiceWrapper
  346. # include HTTParty
  347. #
  348. # query_string_normalizer proc { |query|
  349. # query.map do |key, value|
  350. # value.map {|v| "#{key}=#{v}"}
  351. # end.join('&')
  352. # }
  353. # end
  354. #
  355. # @param [Proc] normalizer custom query string normalizer.
  356. # @yield [Hash, String] query string
  357. # @yieldreturn [Array] an array that will later be joined with '&'
  358. def query_string_normalizer(normalizer)
  359. default_options[:query_string_normalizer] = normalizer
  360. end
  361. # Allows setting of SSL version to use. This only works in Ruby 1.9+.
  362. # You can get a list of valid versions from OpenSSL::SSL::SSLContext::METHODS.
  363. #
  364. # class Foo
  365. # include HTTParty
  366. # ssl_version :SSLv3
  367. # end
  368. def ssl_version(version)
  369. default_options[:ssl_version] = version
  370. end
  371. # Allows setting of SSL ciphers to use. This only works in Ruby 1.9+.
  372. # You can get a list of valid specific ciphers from OpenSSL::Cipher.ciphers.
  373. # You also can specify a cipher suite here, listed here at openssl.org:
  374. # http://www.openssl.org/docs/apps/ciphers.html#CIPHER_SUITE_NAMES
  375. #
  376. # class Foo
  377. # include HTTParty
  378. # ciphers "RC4-SHA"
  379. # end
  380. def ciphers(cipher_names)
  381. default_options[:ciphers] = cipher_names
  382. end
  383. # Allows setting an OpenSSL certificate authority file. The file
  384. # should contain one or more certificates in PEM format.
  385. #
  386. # Setting this option enables certificate verification. All
  387. # certificates along a chain must be available in ssl_ca_file or
  388. # ssl_ca_path for verification to succeed.
  389. #
  390. #
  391. # class Foo
  392. # include HTTParty
  393. # ssl_ca_file '/etc/ssl/certs/ca-certificates.crt'
  394. # end
  395. def ssl_ca_file(path)
  396. default_options[:ssl_ca_file] = path
  397. end
  398. # Allows setting an OpenSSL certificate authority path (directory).
  399. #
  400. # Setting this option enables certificate verification. All
  401. # certificates along a chain must be available in ssl_ca_file or
  402. # ssl_ca_path for verification to succeed.
  403. #
  404. # class Foo
  405. # include HTTParty
  406. # ssl_ca_path '/etc/ssl/certs/'
  407. # end
  408. def ssl_ca_path(path)
  409. default_options[:ssl_ca_path] = path
  410. end
  411. # Allows setting a custom parser for the response.
  412. #
  413. # class Foo
  414. # include HTTParty
  415. # parser Proc.new {|data| ...}
  416. # end
  417. def parser(custom_parser = nil)
  418. if custom_parser.nil?
  419. default_options[:parser]
  420. else
  421. default_options[:parser] = custom_parser
  422. validate_format
  423. end
  424. end
  425. # Allows setting a custom URI adapter.
  426. #
  427. # class Foo
  428. # include HTTParty
  429. # uri_adapter Addressable::URI
  430. # end
  431. def uri_adapter(uri_adapter)
  432. raise ArgumentError, 'The URI adapter should respond to #parse' unless uri_adapter.respond_to?(:parse)
  433. default_options[:uri_adapter] = uri_adapter
  434. end
  435. # Allows setting a custom connection_adapter for the http connections
  436. #
  437. # @example
  438. # class Foo
  439. # include HTTParty
  440. # connection_adapter Proc.new {|uri, options| ... }
  441. # end
  442. #
  443. # @example provide optional configuration for your connection_adapter
  444. # class Foo
  445. # include HTTParty
  446. # connection_adapter Proc.new {|uri, options| ... }, {foo: :bar}
  447. # end
  448. #
  449. # @see HTTParty::ConnectionAdapter
  450. def connection_adapter(custom_adapter = nil, options = nil)
  451. if custom_adapter.nil?
  452. default_options[:connection_adapter]
  453. else
  454. default_options[:connection_adapter] = custom_adapter
  455. default_options[:connection_adapter_options] = options
  456. end
  457. end
  458. # Allows making a get request to a url.
  459. #
  460. # class Foo
  461. # include HTTParty
  462. # end
  463. #
  464. # # Simple get with full url
  465. # Foo.get('http://foo.com/resource.json')
  466. #
  467. # # Simple get with full url and query parameters
  468. # # ie: http://foo.com/resource.json?limit=10
  469. # Foo.get('http://foo.com/resource.json', query: {limit: 10})
  470. def get(path, options = {}, &block)
  471. perform_request Net::HTTP::Get, path, options, &block
  472. end
  473. # Allows making a post request to a url.
  474. #
  475. # class Foo
  476. # include HTTParty
  477. # end
  478. #
  479. # # Simple post with full url and setting the body
  480. # Foo.post('http://foo.com/resources', body: {bar: 'baz'})
  481. #
  482. # # Simple post with full url using :query option,
  483. # # which appends the parameters to the URI.
  484. # Foo.post('http://foo.com/resources', query: {bar: 'baz'})
  485. def post(path, options = {}, &block)
  486. perform_request Net::HTTP::Post, path, options, &block
  487. end
  488. # Perform a PATCH request to a path
  489. def patch(path, options = {}, &block)
  490. perform_request Net::HTTP::Patch, path, options, &block
  491. end
  492. # Perform a PUT request to a path
  493. def put(path, options = {}, &block)
  494. perform_request Net::HTTP::Put, path, options, &block
  495. end
  496. # Perform a DELETE request to a path
  497. def delete(path, options = {}, &block)
  498. perform_request Net::HTTP::Delete, path, options, &block
  499. end
  500. # Perform a MOVE request to a path
  501. def move(path, options = {}, &block)
  502. perform_request Net::HTTP::Move, path, options, &block
  503. end
  504. # Perform a COPY request to a path
  505. def copy(path, options = {}, &block)
  506. perform_request Net::HTTP::Copy, path, options, &block
  507. end
  508. # Perform a HEAD request to a path
  509. def head(path, options = {}, &block)
  510. ensure_method_maintained_across_redirects options
  511. perform_request Net::HTTP::Head, path, options, &block
  512. end
  513. # Perform an OPTIONS request to a path
  514. def options(path, options = {}, &block)
  515. perform_request Net::HTTP::Options, path, options, &block
  516. end
  517. # Perform a MKCOL request to a path
  518. def mkcol(path, options = {}, &block)
  519. perform_request Net::HTTP::Mkcol, path, options, &block
  520. end
  521. def lock(path, options = {}, &block)
  522. perform_request Net::HTTP::Lock, path, options, &block
  523. end
  524. def unlock(path, options = {}, &block)
  525. perform_request Net::HTTP::Unlock, path, options, &block
  526. end
  527. attr_reader :default_options
  528. private
  529. def validate_timeout_argument(timeout_type, value)
  530. raise ArgumentError, "#{ timeout_type } must be an integer or float" unless value && (value.is_a?(Integer) || value.is_a?(Float))
  531. end
  532. def ensure_method_maintained_across_redirects(options)
  533. unless options.key?(:maintain_method_across_redirects)
  534. options[:maintain_method_across_redirects] = true
  535. end
  536. end
  537. def perform_request(http_method, path, options, &block) #:nodoc:
  538. options = ModuleInheritableAttributes.hash_deep_dup(default_options).merge(options)
  539. HeadersProcessor.new(headers, options).call
  540. process_cookies(options)
  541. Request.new(http_method, path, options).perform(&block)
  542. end
  543. def process_cookies(options) #:nodoc:
  544. return unless options[:cookies] || default_cookies.any?
  545. options[:headers] ||= headers.dup
  546. options[:headers]["cookie"] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string
  547. end
  548. def validate_format
  549. if format && parser.respond_to?(:supports_format?) && !parser.supports_format?(format)
  550. supported_format_names = parser.supported_formats.map(&:to_s).sort.join(', ')
  551. raise UnsupportedFormat, "'#{format.inspect}' Must be one of: #{supported_format_names}"
  552. end
  553. end
  554. end
  555. def self.normalize_base_uri(url) #:nodoc:
  556. normalized_url = url.dup
  557. use_ssl = (normalized_url =~ /^https/) || (normalized_url =~ /:443\b/)
  558. ends_with_slash = normalized_url =~ /\/$/
  559. normalized_url.chop! if ends_with_slash
  560. normalized_url.gsub!(/^https?:\/\//i, '')
  561. "http#{'s' if use_ssl}://#{normalized_url}"
  562. end
  563. class Basement #:nodoc:
  564. include HTTParty
  565. end
  566. def self.get(*args, &block)
  567. Basement.get(*args, &block)
  568. end
  569. def self.post(*args, &block)
  570. Basement.post(*args, &block)
  571. end
  572. def self.patch(*args, &block)
  573. Basement.patch(*args, &block)
  574. end
  575. def self.put(*args, &block)
  576. Basement.put(*args, &block)
  577. end
  578. def self.delete(*args, &block)
  579. Basement.delete(*args, &block)
  580. end
  581. def self.move(*args, &block)
  582. Basement.move(*args, &block)
  583. end
  584. def self.copy(*args, &block)
  585. Basement.copy(*args, &block)
  586. end
  587. def self.head(*args, &block)
  588. Basement.head(*args, &block)
  589. end
  590. def self.options(*args, &block)
  591. Basement.options(*args, &block)
  592. end
  593. end
  594. require 'httparty/hash_conversions'
  595. require 'httparty/utils'
  596. require 'httparty/exceptions'
  597. require 'httparty/parser'
  598. require 'httparty/request'
  599. require 'httparty/response'