'
8 | And that service is accessed at the path '/long_running_service.html'
9 | And that service takes 2 seconds to generate a response
10 | When I set my HTTParty timeout option to 1
11 | And I call HTTParty#get with '/long_running_service.html'
12 | Then it should raise a Timeout::Error exception
13 | And I wait for the server to recover
14 |
--------------------------------------------------------------------------------
/features/supports_read_timeout_option.feature:
--------------------------------------------------------------------------------
1 | Feature: Supports the read timeout option
2 | In order to handle inappropriately slow response times
3 | As a developer
4 | I want my request to raise an exception after my specified read_timeout as elapsed
5 |
6 | Scenario: A long running response
7 | Given a remote service that returns '
Some HTML
'
8 | And that service is accessed at the path '/long_running_service.html'
9 | And that service takes 2 seconds to generate a response
10 | When I set my HTTParty read_timeout option to 1
11 | And I call HTTParty#get with '/long_running_service.html'
12 | Then it should raise a Timeout::Error exception
13 | And I wait for the server to recover
14 |
--------------------------------------------------------------------------------
/examples/rescue_json.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | require File.join(dir, 'httparty')
3 |
4 | # Take note of the "; 1" at the end of the following line. It's required only if
5 | # running this in IRB, because IRB will try to inspect the variable named
6 | # "request", triggering the exception.
7 | request = HTTParty.get 'https://rubygems.org/api/v1/versions/doesnotexist.json' ; 1
8 |
9 | # Check an exception due to parsing the response
10 | # because HTTParty evaluate the response lazily
11 | begin
12 | request.inspect
13 | # This would also suffice by forcing the request to be parsed:
14 | # request.parsed_response
15 | rescue => e
16 | puts "Rescued #{e.inspect}"
17 | end
18 |
--------------------------------------------------------------------------------
/lib/httparty/response/headers.rb:
--------------------------------------------------------------------------------
1 | module HTTParty
2 | class Response #:nodoc:
3 | class Headers
4 | include ::Net::HTTPHeader
5 |
6 | def initialize(header = {})
7 | @header = header
8 | end
9 |
10 | def ==(other)
11 | @header == other
12 | end
13 |
14 | def inspect
15 | @header.inspect
16 | end
17 |
18 | def method_missing(name, *args, &block)
19 | if @header.respond_to?(name)
20 | @header.send(name, *args, &block)
21 | else
22 | super
23 | end
24 | end
25 |
26 | def respond_to?(method, include_all = false)
27 | super || @header.respond_to?(method, include_all)
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/httparty/logger/logger.rb:
--------------------------------------------------------------------------------
1 | require 'httparty/logger/apache_formatter'
2 | require 'httparty/logger/curl_formatter'
3 |
4 | module HTTParty
5 | module Logger
6 | def self.formatters
7 | @formatters ||= {
8 | :curl => Logger::CurlFormatter,
9 | :apache => Logger::ApacheFormatter
10 | }
11 | end
12 |
13 | def self.add_formatter(name, formatter)
14 | raise HTTParty::Error.new("Log Formatter with name #{name} already exists") if formatters.include?(name)
15 | formatters.merge!(name.to_sym => formatter)
16 | end
17 |
18 | def self.build(logger, level, formatter)
19 | level ||= :info
20 | formatter ||= :apache
21 |
22 | logger_klass = formatters[formatter] || Logger::ApacheFormatter
23 | logger_klass.new(logger, level)
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generated/bogushost.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICBTCCAW6gAwIBAgIBATANBgkqhkiG9w0BAQUFADAuMSwwKgYDVQQDEyNJTlNF
3 | Q1VSRSBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTAgFw0xMDEwMjAxMzQ2MjNa
4 | GA80NzQ4MDkxNTEzNDYyM1owDzENMAsGA1UEAxMEYm9nbzCBnzANBgkqhkiG9w0B
5 | AQEFAAOBjQAwgYkCgYEAr6b0ZBrRrVvPmPbQv36Jnj5jv00ZkhimXrmbv9Z1AdIZ
6 | WSsBpMd8TP7exE5OR5/DaxKmiZqVskgRyRkLm52/Dkt7Ncrzr5I3unHnMqsAv/28
7 | 5fGlYoRxnkCGMse/6NOFgCemRFw/bglxPNAGrFYKStameBRbCm0dCgtlvcwzdf8C
8 | AwEAAaNQME4wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUddLPFtGmb0aFWbTl2kAo
9 | xD+fd6kwHwYDVR0jBBgwFoAUy0Lz6RgmtpywlBOXdPABQArp358wDQYJKoZIhvcN
10 | AQEFBQADgYEAosqpPVsFu6cOIhGFT85Y1wwRUaihO0vWO7ghBU5ScuRU3tuvyJDZ
11 | Z/HoAMXV6XZjVZzRosjtPjFbyWkZYjUqJJRMyEaRiGArWe6urKLzwnD6R9O3eNa5
12 | 7bgFhzZ5WBldJmtq4A3oNqBuvgZkYM6NVKvS4UoakkTliHB21/mDOSY=
13 | -----END CERTIFICATE-----
14 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generated/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICCjCCAXOgAwIBAgIBATANBgkqhkiG9w0BAQUFADAuMSwwKgYDVQQDEyNJTlNF
3 | Q1VSRSBUZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTAgFw0xMDEwMjAxMzQ2MjNa
4 | GA80NzQ4MDkxNTEzNDYyM1owFDESMBAGA1UEAxMJbG9jYWxob3N0MIGfMA0GCSqG
5 | SIb3DQEBAQUAA4GNADCBiQKBgQCvpvRkGtGtW8+Y9tC/fomePmO/TRmSGKZeuZu/
6 | 1nUB0hlZKwGkx3xM/t7ETk5Hn8NrEqaJmpWySBHJGQubnb8OS3s1yvOvkje6cecy
7 | qwC//bzl8aVihHGeQIYyx7/o04WAJ6ZEXD9uCXE80AasVgpK1qZ4FFsKbR0KC2W9
8 | zDN1/wIDAQABo1AwTjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBR10s8W0aZvRoVZ
9 | tOXaQCjEP593qTAfBgNVHSMEGDAWgBTLQvPpGCa2nLCUE5d08AFACunfnzANBgkq
10 | hkiG9w0BAQUFAAOBgQCR4Oor0YAvK0tNFrOLtqmC6D0F5IYCyu7komk7JGn9L4nn
11 | 7VyVxd4MXdc1r1v+WP5JtnA9ZjMmEmH9gl4gwR/Cu+TMkArsq0Z8mREOLNL8pwpx
12 | Zxgk0CwacYR9RQcpuJ9nSDzVoO5ecYkb5C9q7gwgqbmCzr7oz/rwTqRwiUZCVQ==
13 | -----END CERTIFICATE-----
14 |
--------------------------------------------------------------------------------
/lib/httparty/logger/apache_formatter.rb:
--------------------------------------------------------------------------------
1 | module HTTParty
2 | module Logger
3 | class ApacheFormatter #:nodoc:
4 | TAG_NAME = HTTParty.name
5 |
6 | attr_accessor :level, :logger, :current_time
7 |
8 | def initialize(logger, level)
9 | @logger = logger
10 | @level = level.to_sym
11 | end
12 |
13 | def format(request, response)
14 | current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
15 | http_method = request.http_method.name.split("::").last.upcase
16 | path = request.path.to_s
17 | content_length = response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
18 | @logger.send @level, "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generated/selfsigned.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICHTCCAYagAwIBAgIJALT/G+ylQljIMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
3 | BAMTCWxvY2FsaG9zdDAgFw0xMDEwMjAxMzQ2MjNaGA80NzQ4MDkxNTEzNDYyM1ow
4 | FDESMBAGA1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
5 | gQCvpvRkGtGtW8+Y9tC/fomePmO/TRmSGKZeuZu/1nUB0hlZKwGkx3xM/t7ETk5H
6 | n8NrEqaJmpWySBHJGQubnb8OS3s1yvOvkje6cecyqwC//bzl8aVihHGeQIYyx7/o
7 | 04WAJ6ZEXD9uCXE80AasVgpK1qZ4FFsKbR0KC2W9zDN1/wIDAQABo3UwczAdBgNV
8 | HQ4EFgQUddLPFtGmb0aFWbTl2kAoxD+fd6kwRAYDVR0jBD0wO4AUddLPFtGmb0aF
9 | WbTl2kAoxD+fd6mhGKQWMBQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJALT/G+ylQljI
10 | MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAlOCBO54S88mD3VYviER6
11 | V+lkd7iWmdas2wUUDeMKA9CxnirWi7ne2U7wQH/5FJ1j3ImSfjb4h/98xiVJE84e
12 | Ld7mb61g/M4g4b62kt0HK8/cGUxfuz5zwIfi28qJq3ow6AFEq1fywbJvUAnnamwU
13 | cZF/qoVfJhus2mXjYc4hFWg=
14 | -----END CERTIFICATE-----
15 |
--------------------------------------------------------------------------------
/examples/basic.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | require File.join(dir, 'httparty')
3 | require 'pp'
4 |
5 | # You can also use post, put, delete, head, options in the same fashion
6 | response = HTTParty.get('https://api.stackexchange.com/2.2/questions?site=stackoverflow')
7 | puts response.body, response.code, response.message, response.headers.inspect
8 |
9 | # An example post to a minimal rails app in the development environment
10 | # Note that "skip_before_filter :verify_authenticity_token" must be set in the
11 | # "pears" controller for this example
12 |
13 | class Partay
14 | include HTTParty
15 | base_uri 'http://localhost:3000'
16 | end
17 |
18 | options = {
19 | body: {
20 | pear: { # your resource
21 | foo: '123', # your columns/data
22 | bar: 'second',
23 | baz: 'last thing'
24 | }
25 | }
26 | }
27 |
28 | pp Partay.post('/pears.xml', options)
29 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generated/ca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXgIBAAKBgQDeWQFx3fnaqIgjOqfK+8mPn1zHV2fMzKOe5orZxEfjbSsF+6Qs
3 | TYxs5Wv2dir/sJ2OpekTy4UAX5RNIgroxZn9uJ2SvbF39638J7alUhXEf6u3eknP
4 | fBmELcdjP4dTgIPaIj3ADOxvqYxXuZ3V+OVh+KvhIEYY9ORypQlSnb+1AwIDAQAB
5 | AoGBAL147VFCDlM1gGU865V+wIFCFQbNxedwjxGuda4io/v6oEoF6R3Tq5F0Y27v
6 | va6Lq4fOe/LhYGI0EKU2GEPJd3F2wA21r+81InPKAkqYI5CDQtKDDNLviur8ZVKF
7 | i3UzutjeYoCqmWeHaKPD6w5DtqeBieem7LTWRyXlFtHZV/nBAkEA8nsMOSd1+JTm
8 | ZT4HDsEFQrN8mIFUUioFSHPut2CwzvTEW+hTkLQiog3bua4n7uQOFImR63X9qMsh
9 | IjZRJQNmowJBAOq+mQdnRWYKl0SYb++Eb3uW6L4h1zsW375+caKo9omtpeqDW/y0
10 | BWyY0q4DPkm3yU26Yr+b2JijISrml9/8PiECQQDHuXyG8y7jktn3GFE94NURbL+6
11 | 6gPnLX9ufzdoSjc4MDowrbtvHEDOlHWgioeP6L6EQhA0DtrhlnbzNCRARX3bAkEA
12 | jQOsF+dwqAjKr/lGnMKY2cxgyf64NZXbGKsKhmUrnK9E0SjR9G8MJx1yyffGzi/q
13 | bJf/xAzRw3eTcBsPtwznIQJAHq5MOK7oaUuO+6cbsZYpOYOOkKIvDLiOtdSr7LTI
14 | DziH/fpzB0VhCmFhhEQwHhlB4t3m66A9TelHmhrCDsIaLA==
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generated/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXQIBAAKBgQCvpvRkGtGtW8+Y9tC/fomePmO/TRmSGKZeuZu/1nUB0hlZKwGk
3 | x3xM/t7ETk5Hn8NrEqaJmpWySBHJGQubnb8OS3s1yvOvkje6cecyqwC//bzl8aVi
4 | hHGeQIYyx7/o04WAJ6ZEXD9uCXE80AasVgpK1qZ4FFsKbR0KC2W9zDN1/wIDAQAB
5 | AoGALIdgkTgTS6VovVhklwcXEBy04LxE7Tp+gqj/COTvCKUgc/BpHELOCh7ajl1j
6 | jti7i5tQyLV9mZKXn6lPvgWBd0w+p6VhM4NFA97CoodEJm2ckFC9zUABCh9dOpbm
7 | 8KzF7hdpYWgJJchwwZ60tbcP7K1DkiNX6Kk9qKQEWvitMBECQQDpOSzzLldcEU9l
8 | ze/nG2+rf6ecaPnKeafY8R2qVils8I7ZJAW3+0bNT5gQs7rT7aWo8vMvrXq++lWb
9 | JkNV6hK9AkEAwM5wsmg7REmAaDwgUBq5mNt963/uG2ihAODFS70lYT23UYl5Y3rD
10 | s3qU4ntG4DvWIQgPdwdstzDh9fMBVXa1awJBAID1WoOE5k1ETRDP1I2HwDGmPnng
11 | Ge75YfQ1LuAXEITqZzJuFrNqv/Waw0zI9M9moqlO3WVJmYusRFWrzKPe8EkCQEwC
12 | FlN+275z63csHOD3aCtmfCGW8VtEyBP8iErvagkHt3khZQVepD/hF0ihqLNFY4jq
13 | EI6wEp+1WZ8ICYKTpbkCQQDhl5QLdy5Xo3k3agCnB9nktSzs2iqFvsGvfOAW4628
14 | iThKTNua6bBvbdiG0Vh2Sv0XBYVJoHB3WnTVgFyPJaaF
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generated/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICbTCCAdagAwIBAgIJAIAeO9TXtJ45MA0GCSqGSIb3DQEBBQUAMC4xLDAqBgNV
3 | BAMTI0lOU0VDVVJFIFRlc3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MCAXDTEwMTAy
4 | MDEzNDYyM1oYDzQ3NDgwOTE1MTM0NjIzWjAuMSwwKgYDVQQDEyNJTlNFQ1VSRSBU
5 | ZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
6 | gYkCgYEA3lkBcd352qiIIzqnyvvJj59cx1dnzMyjnuaK2cRH420rBfukLE2MbOVr
7 | 9nYq/7CdjqXpE8uFAF+UTSIK6MWZ/bidkr2xd/et/Ce2pVIVxH+rt3pJz3wZhC3H
8 | Yz+HU4CD2iI9wAzsb6mMV7md1fjlYfir4SBGGPTkcqUJUp2/tQMCAwEAAaOBkDCB
9 | jTAdBgNVHQ4EFgQUy0Lz6RgmtpywlBOXdPABQArp358wXgYDVR0jBFcwVYAUy0Lz
10 | 6RgmtpywlBOXdPABQArp35+hMqQwMC4xLDAqBgNVBAMTI0lOU0VDVVJFIFRlc3Qg
11 | Q2VydGlmaWNhdGUgQXV0aG9yaXR5ggkAgB471Ne0njkwDAYDVR0TBAUwAwEB/zAN
12 | BgkqhkiG9w0BAQUFAAOBgQCmi3JQm+EIWjkRlyz9sijkYS+Ps4opmd/weeaXwa4E
13 | gVBWJGyiduB+kBnfv61+/tDjlrbjBDH5dP8suczHQL8gox4zGgjw64KH4o1ujZYR
14 | cEPbhnUpwbXu7yItlajBZfpFefjF5P0Ao2iEzQldDy0D6nQ19h5QANvQxqweTPQp
15 | pw==
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/features/supports_redirection.feature:
--------------------------------------------------------------------------------
1 | Feature: Supports Redirection
2 |
3 | As a developer
4 | I want to work with services that may redirect me
5 | And I want it to follow a reasonable number of redirects
6 | Because sometimes web services do that
7 |
8 | Scenario: A service that redirects once
9 | Given a remote service that returns 'Service Response'
10 | And that service is accessed at the path '/landing_service.html'
11 | And the url '/redirector.html' redirects to '/landing_service.html'
12 | When I call HTTParty#get with '/redirector.html'
13 | Then the return value should match 'Service Response'
14 |
15 | # TODO: Look in to why this actually fails...
16 | Scenario: A service that redirects to a relative URL
17 |
18 | Scenario: A service that redirects infinitely
19 | Given the url '/first.html' redirects to '/second.html'
20 | And the url '/second.html' redirects to '/first.html'
21 | When I call HTTParty#get with '/first.html'
22 | Then it should raise an HTTParty::RedirectionTooDeep exception
23 |
--------------------------------------------------------------------------------
/features/basic_authentication.feature:
--------------------------------------------------------------------------------
1 | Feature: Basic Authentication
2 |
3 | As a developer
4 | I want to be able to use a service that requires Basic Authentication
5 | Because that is not an uncommon requirement
6 |
7 | Scenario: Passing no credentials to a page requiring Basic Authentication
8 | Given a restricted page at '/basic_auth.html'
9 | When I call HTTParty#get with '/basic_auth.html'
10 | Then it should return a response with a 401 response code
11 |
12 | Scenario: Passing proper credentials to a page requiring Basic Authentication
13 | Given a remote service that returns 'Authenticated Page'
14 | And that service is accessed at the path '/basic_auth.html'
15 | And that service is protected by Basic Authentication
16 | And that service requires the username 'jcash' with the password 'maninblack'
17 | When I call HTTParty#get with '/basic_auth.html' and a basic_auth hash:
18 | | username | password |
19 | | jcash | maninblack |
20 | Then the return value should match 'Authenticated Page'
21 |
--------------------------------------------------------------------------------
/script/release:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #/ Usage: release
3 | #/
4 | #/ Tag the version in the repo and push the gem.
5 | #/
6 |
7 | set -e
8 | cd $(dirname "$0")/..
9 |
10 | [ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
11 | grep '^#/' <"$0"| cut -c4-
12 | exit 0
13 | }
14 |
15 | gem_name=httparty
16 |
17 | # Build a new gem archive.
18 | rm -rf $gem_name-*.gem
19 | gem build -q $gem_name.gemspec
20 |
21 | # Make sure we're on the master branch.
22 | (git branch | grep -q '* master') || {
23 | echo "Only release from the master branch."
24 | exit 1
25 | }
26 |
27 | # Figure out what version we're releasing.
28 | tag=v`ls $gem_name-*.gem | sed "s/^$gem_name-\(.*\)\.gem$/\1/"`
29 |
30 | echo "Releasing $tag"
31 |
32 | # Make sure we haven't released this version before.
33 | git fetch -t origin
34 |
35 | (git tag -l | grep -q "$tag") && {
36 | echo "Whoops, there's already a '${tag}' tag."
37 | exit 1
38 | }
39 |
40 | # Tag it and bag it.
41 | gem push $gem_name-*.gem && git tag "$tag" &&
42 | git push origin master && git push origin "$tag"
43 |
--------------------------------------------------------------------------------
/examples/twitter.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | require File.join(dir, 'httparty')
3 | require 'pp'
4 | config = YAML.load(File.read(File.join(ENV['HOME'], '.twitter')))
5 |
6 | class Twitter
7 | include HTTParty
8 | base_uri 'twitter.com'
9 |
10 | def initialize(u, p)
11 | @auth = {username: u, password: p}
12 | end
13 |
14 | # which can be :friends, :user or :public
15 | # options[:query] can be things like since, since_id, count, etc.
16 | def timeline(which = :friends, options = {})
17 | options.merge!({ basic_auth: @auth })
18 | self.class.get("/statuses/#{which}_timeline.json", options)
19 | end
20 |
21 | def post(text)
22 | options = { query: { status: text }, basic_auth: @auth }
23 | self.class.post('/statuses/update.json', options)
24 | end
25 | end
26 |
27 | twitter = Twitter.new(config['email'], config['password'])
28 | pp twitter.timeline
29 | # pp twitter.timeline(:friends, query: {since_id: 868482746})
30 | # pp twitter.timeline(:friends, query: 'since_id=868482746')
31 | # pp twitter.post('this is a test of 0.2.0')
32 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008 John Nunemaker
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | * Contributions will not be accepted without tests.
4 | * Please post unconfirmed bugs to the mailing list first: https://groups.google.com/forum/#!forum/httparty-gem
5 | * Don't change the version. The maintainers will handle that when they release.
6 | * Always provide as much information and reproducibility as possible when filing an issue or submitting a pull request.
7 |
8 | ## Workflow
9 |
10 | * Fork the project.
11 | * Run `bundle`
12 | * Run `bundle exec rake`
13 | * Make your feature addition or bug fix.
14 | * Add tests for it. This is important so I don't break it in a future version unintentionally.
15 | * Run `bundle exec rake` (No, REALLY :))
16 | * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
17 | * Send me a pull request. Bonus points for topic branches.
18 |
19 | ## Help and Docs
20 |
21 | * https://groups.google.com/forum/#!forum/httparty-gem
22 | * http://stackoverflow.com/questions/tagged/httparty
23 | * http://rdoc.info/projects/jnunemaker/httparty
24 |
--------------------------------------------------------------------------------
/examples/aaws.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'active_support'
3 |
4 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
5 | require File.join(dir, 'httparty')
6 | require 'pp'
7 | config = YAML.load(File.read(File.join(ENV['HOME'], '.aaws')))
8 |
9 | module AAWS
10 | class Book
11 | include HTTParty
12 | base_uri 'http://ecs.amazonaws.com'
13 | default_params Service: 'AWSECommerceService', Operation: 'ItemSearch', SearchIndex: 'Books'
14 |
15 | def initialize(key)
16 | self.class.default_params AWSAccessKeyId: key
17 | end
18 |
19 | def search(options = {})
20 | raise ArgumentError, 'You must search for something' if options[:query].blank?
21 |
22 | # amazon uses nasty camelized query params
23 | options[:query] = options[:query].inject({}) { |h, q| h[q[0].to_s.camelize] = q[1]; h }
24 |
25 | # make a request and return the items (NOTE: this doesn't handle errors at this point)
26 | self.class.get('/onca/xml', options)['ItemSearchResponse']['Items']
27 | end
28 | end
29 | end
30 |
31 | aaws = AAWS::Book.new(config[:access_key])
32 | pp aaws.search(query: { title: 'Ruby On Rails' })
33 |
--------------------------------------------------------------------------------
/httparty.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $LOAD_PATH.push File.expand_path("../lib", __FILE__)
3 | require "httparty/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "httparty"
7 | s.version = HTTParty::VERSION
8 | s.platform = Gem::Platform::RUBY
9 | s.licenses = ['MIT']
10 | s.authors = ["John Nunemaker", "Sandro Turriate"]
11 | s.email = ["nunemaker@gmail.com"]
12 | s.homepage = "http://jnunemaker.github.com/httparty"
13 | s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.'
14 | s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.'
15 |
16 | s.required_ruby_version = '>= 2.0.0'
17 |
18 | s.add_dependency 'multi_xml', ">= 0.5.2"
19 |
20 | # If this line is removed, all hard partying will cease.
21 | s.post_install_message = "When you HTTParty, you must party hard!"
22 |
23 | s.files = `git ls-files`.split("\n")
24 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
26 | s.require_paths = ["lib"]
27 | end
28 |
--------------------------------------------------------------------------------
/examples/logging.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | require File.join(dir, 'httparty')
3 | require 'logger'
4 | require 'pp'
5 |
6 | my_logger = Logger.new "httparty.log"
7 |
8 | my_logger.info "Logging can be used on the main HTTParty class. It logs redirects too."
9 | HTTParty.get "http://google.com", logger: my_logger
10 |
11 | my_logger.info '*' * 70
12 |
13 | my_logger.info "It can be used also on a custom class."
14 |
15 | class Google
16 | include HTTParty
17 | logger ::Logger.new "httparty.log"
18 | end
19 |
20 | Google.get "http://google.com"
21 |
22 | my_logger.info '*' * 70
23 |
24 | my_logger.info "The default formatter is :apache. The :curl formatter can also be used."
25 | my_logger.info "You can tell wich method to call on the logger too. It is info by default."
26 | HTTParty.get "http://google.com", logger: my_logger, log_level: :debug, log_format: :curl
27 |
28 | my_logger.info '*' * 70
29 |
30 | my_logger.info "These configs are also available on custom classes."
31 | class Google
32 | include HTTParty
33 | logger ::Logger.new("httparty.log"), :debug, :curl
34 | end
35 |
36 | Google.get "http://google.com"
37 |
--------------------------------------------------------------------------------
/features/handles_compressed_responses.feature:
--------------------------------------------------------------------------------
1 | Feature: Handles Compressed Responses
2 |
3 | In order to save bandwidth
4 | As a developer
5 | I want to uncompress compressed responses
6 |
7 | Scenario: Supports deflate encoding
8 | Given a remote deflate service
9 | And the response from the service has a body of '
Some HTML
'
10 | And that service is accessed at the path '/deflate_service.html'
11 | When I call HTTParty#get with '/deflate_service.html'
12 | Then the return value should match '
Some HTML
'
13 |
14 | Scenario: Supports gzip encoding
15 | Given a remote gzip service
16 | And the response from the service has a body of '
Some HTML
'
17 | And that service is accessed at the path '/gzip_service.html'
18 | When I call HTTParty#get with '/gzip_service.html'
19 | Then the return value should match '
Some HTML
'
20 |
21 | Scenario: Supports HEAD request with gzip encoding
22 | Given a remote gzip service
23 | And that service is accessed at the path '/gzip_head.gz.js'
24 | When I call HTTParty#head with '/gzip_head.gz.js'
25 | Then it should return a response with a 200 response code
26 | Then it should return a response with a gzip content-encoding
27 | Then it should return a response with a blank body
28 |
--------------------------------------------------------------------------------
/features/deals_with_http_error_codes.feature:
--------------------------------------------------------------------------------
1 | Feature: Deals with HTTP error codes
2 |
3 | As a developer
4 | I want to be informed of non-successful responses
5 | Because sometimes thing explode
6 | And I should probably know what happened
7 |
8 | Scenario: A response of '404 - Not Found'
9 | Given a remote service that returns a 404 status code
10 | And that service is accessed at the path '/404_service.html'
11 | When I call HTTParty#get with '/404_service.html'
12 | Then it should return a response with a 404 response code
13 |
14 | Scenario: A response of '500 - Internal Server Error'
15 | Given a remote service that returns a 500 status code
16 | And that service is accessed at the path '/500_service.html'
17 | When I call HTTParty#get with '/500_service.html'
18 | Then it should return a response with a 500 response code
19 |
20 | Scenario: A non-successful response where I need the body
21 | Given a remote service that returns a 400 status code
22 | And the response from the service has a body of 'Bad response'
23 | And that service is accessed at the path '/400_service.html'
24 | When I call HTTParty#get with '/400_service.html'
25 | Then it should return a response with a 400 response code
26 | And the return value should match 'Bad response'
27 |
--------------------------------------------------------------------------------
/examples/tripit_sign_in.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | require File.join(dir, 'httparty')
3 |
4 | class TripIt
5 | include HTTParty
6 | base_uri 'https://www.tripit.com'
7 | debug_output
8 |
9 | def initialize(email, password)
10 | @email = email
11 | get_response = self.class.get('/account/login')
12 | get_response_cookie = parse_cookie(get_response.headers['Set-Cookie'])
13 |
14 | post_response = self.class.post(
15 | '/account/login',
16 | body: {
17 | login_email_address: email,
18 | login_password: password
19 | },
20 | headers: {'Cookie' => get_response_cookie.to_cookie_string }
21 | )
22 |
23 | @cookie = parse_cookie(post_response.headers['Set-Cookie'])
24 | end
25 |
26 | def account_settings
27 | self.class.get('/account/edit', headers: { 'Cookie' => @cookie.to_cookie_string })
28 | end
29 |
30 | def logged_in?
31 | account_settings.include? "You're logged in as #{@email}"
32 | end
33 |
34 | private
35 |
36 | def parse_cookie(resp)
37 | cookie_hash = CookieHash.new
38 | resp.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
39 | cookie_hash
40 | end
41 | end
42 |
43 | tripit = TripIt.new('email', 'password')
44 | puts "Logged in: #{tripit.logged_in?}"
45 |
--------------------------------------------------------------------------------
/examples/delicious.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2 | require File.join(dir, 'httparty')
3 | require 'pp'
4 | config = YAML.load(File.read(File.join(ENV['HOME'], '.delicious')))
5 |
6 | class Delicious
7 | include HTTParty
8 | base_uri 'https://api.del.icio.us/v1'
9 |
10 | def initialize(u, p)
11 | @auth = { username: u, password: p }
12 | end
13 |
14 | # query params that filter the posts are:
15 | # tag (optional). Filter by this tag.
16 | # dt (optional). Filter by this date (CCYY-MM-DDThh:mm:ssZ).
17 | # url (optional). Filter by this url.
18 | # ie: posts(query: {tag: 'ruby'})
19 | def posts(options = {})
20 | options.merge!({ basic_auth: @auth })
21 | self.class.get('/posts/get', options)
22 | end
23 |
24 | # query params that filter the posts are:
25 | # tag (optional). Filter by this tag.
26 | # count (optional). Number of items to retrieve (Default:15, Maximum:100).
27 | def recent(options = {})
28 | options.merge!({ basic_auth: @auth })
29 | self.class.get('/posts/recent', options)
30 | end
31 | end
32 |
33 | delicious = Delicious.new(config['username'], config['password'])
34 | pp delicious.posts(query: { tag: 'ruby' })
35 | pp delicious.recent
36 |
37 | delicious.recent['posts']['post'].each { |post| puts post['href'] }
38 |
--------------------------------------------------------------------------------
/lib/httparty/exceptions.rb:
--------------------------------------------------------------------------------
1 | module HTTParty
2 | # @abstact Exceptions raised by HTTParty inherit from Error
3 | class Error < StandardError; end
4 |
5 | # Exception raised when you attempt to set a non-existent format
6 | class UnsupportedFormat < Error; end
7 |
8 | # Exception raised when using a URI scheme other than HTTP or HTTPS
9 | class UnsupportedURIScheme < Error; end
10 |
11 | # @abstract Exceptions which inherit from ResponseError contain the Net::HTTP
12 | # response object accessible via the {#response} method.
13 | class ResponseError < Error
14 | # Returns the response of the last request
15 | # @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g.
16 | # Net::HTTPOK
17 | attr_reader :response
18 |
19 | # Instantiate an instance of ResponseError with a Net::HTTPResponse object
20 | # @param [Net::HTTPResponse]
21 | def initialize(response)
22 | @response = response
23 | end
24 | end
25 |
26 | # Exception that is raised when request has redirected too many times.
27 | # Calling {#response} returns the Net:HTTP response object.
28 | class RedirectionTooDeep < ResponseError; end
29 |
30 | # Exception that is raised when request redirects and location header is present more than once
31 | class DuplicateLocationHeader < ResponseError; end
32 | end
33 |
--------------------------------------------------------------------------------
/examples/custom_parsers.rb:
--------------------------------------------------------------------------------
1 | class ParseAtom
2 | include HTTParty
3 |
4 | # Support Atom along with the default parsers: xml, json, etc.
5 | class Parser::Atom < HTTParty::Parser
6 | SupportedFormats.merge!({"application/atom+xml" => :atom})
7 |
8 | protected
9 |
10 | # perform atom parsing on body
11 | def atom
12 | body.to_atom
13 | end
14 | end
15 |
16 | parser Parser::Atom
17 | end
18 |
19 | class OnlyParseAtom
20 | include HTTParty
21 |
22 | # Only support Atom
23 | class Parser::OnlyAtom < HTTParty::Parser
24 | SupportedFormats = { "application/atom+xml" => :atom }
25 |
26 | protected
27 |
28 | # perform atom parsing on body
29 | def atom
30 | body.to_atom
31 | end
32 | end
33 |
34 | parser Parser::OnlyAtom
35 | end
36 |
37 | class SkipParsing
38 | include HTTParty
39 |
40 | # Parse the response body however you like
41 | class Parser::Simple < HTTParty::Parser
42 | def parse
43 | body
44 | end
45 | end
46 |
47 | parser Parser::Simple
48 | end
49 |
50 | class AdHocParsing
51 | include HTTParty
52 | parser(
53 | proc do |body, format|
54 | case format
55 | when :json
56 | body.to_json
57 | when :xml
58 | body.to_xml
59 | else
60 | body
61 | end
62 | end
63 | )
64 | end
65 |
--------------------------------------------------------------------------------
/spec/httparty/exception_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2 |
3 | RSpec.describe HTTParty::Error do
4 | subject { described_class }
5 |
6 | describe '#ancestors' do
7 | subject { super().ancestors }
8 | it { is_expected.to include(StandardError) }
9 | end
10 |
11 | describe HTTParty::UnsupportedFormat do
12 | describe '#ancestors' do
13 | subject { super().ancestors }
14 | it { is_expected.to include(HTTParty::Error) }
15 | end
16 | end
17 |
18 | describe HTTParty::UnsupportedURIScheme do
19 | describe '#ancestors' do
20 | subject { super().ancestors }
21 | it { is_expected.to include(HTTParty::Error) }
22 | end
23 | end
24 |
25 | describe HTTParty::ResponseError do
26 | describe '#ancestors' do
27 | subject { super().ancestors }
28 | it { is_expected.to include(HTTParty::Error) }
29 | end
30 | end
31 |
32 | describe HTTParty::RedirectionTooDeep do
33 | describe '#ancestors' do
34 | subject { super().ancestors }
35 | it { is_expected.to include(HTTParty::ResponseError) }
36 | end
37 | end
38 |
39 | describe HTTParty::DuplicateLocationHeader do
40 | describe '#ancestors' do
41 | subject { super().ancestors }
42 | it { is_expected.to include(HTTParty::ResponseError) }
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/spec/fixtures/ssl/generate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | if [ -d "generated" ] ; then
5 | echo >&2 "error: 'generated' directory already exists. Delete it first."
6 | exit 1
7 | fi
8 |
9 | mkdir generated
10 |
11 | # Generate the CA private key and certificate
12 | openssl req -batch -subj '/CN=INSECURE Test Certificate Authority' -newkey rsa:1024 -new -x509 -days 999999 -keyout generated/ca.key -nodes -out generated/ca.crt
13 |
14 | # Create symlinks for ssl_ca_path
15 | c_rehash generated
16 |
17 | # Generate the server private key and self-signed certificate
18 | openssl req -batch -subj '/CN=localhost' -newkey rsa:1024 -new -x509 -days 999999 -keyout generated/server.key -nodes -out generated/selfsigned.crt
19 |
20 | # Generate certificate signing request with bogus hostname
21 | openssl req -batch -subj '/CN=bogo' -new -days 999999 -key generated/server.key -nodes -out generated/bogushost.csr
22 |
23 | # Sign the certificate requests
24 | openssl x509 -CA generated/ca.crt -CAkey generated/ca.key -set_serial 1 -in generated/selfsigned.crt -out generated/server.crt -clrext -extfile openssl-exts.cnf -extensions cert -days 999999
25 | openssl x509 -req -CA generated/ca.crt -CAkey generated/ca.key -set_serial 1 -in generated/bogushost.csr -out generated/bogushost.crt -clrext -extfile openssl-exts.cnf -extensions cert -days 999999
26 |
27 | # Remove certificate signing requests
28 | rm -f generated/*.csr
29 |
30 |
--------------------------------------------------------------------------------
/spec/httparty/logger/logger_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2 |
3 | RSpec.describe HTTParty::Logger do
4 | describe ".build" do
5 | subject { HTTParty::Logger }
6 |
7 | it "defaults level to :info" do
8 | logger_double = double
9 | expect(subject.build(logger_double, nil, nil).level).to eq(:info)
10 | end
11 |
12 | it "defaults format to :apache" do
13 | logger_double = double
14 | expect(subject.build(logger_double, nil, nil)).to be_an_instance_of(HTTParty::Logger::ApacheFormatter)
15 | end
16 |
17 | it "builds :curl style logger" do
18 | logger_double = double
19 | expect(subject.build(logger_double, nil, :curl)).to be_an_instance_of(HTTParty::Logger::CurlFormatter)
20 | end
21 |
22 | it "builds :custom style logger" do
23 | CustomFormatter = Class.new(HTTParty::Logger::CurlFormatter)
24 | HTTParty::Logger.add_formatter(:custom, CustomFormatter)
25 |
26 | logger_double = double
27 | expect(subject.build(logger_double, nil, :custom)).
28 | to be_an_instance_of(CustomFormatter)
29 | end
30 | it "raises error when formatter exists" do
31 | CustomFormatter2= Class.new(HTTParty::Logger::CurlFormatter)
32 | HTTParty::Logger.add_formatter(:custom2, CustomFormatter2)
33 |
34 | expect{ HTTParty::Logger.add_formatter(:custom2, CustomFormatter2) }.
35 | to raise_error HTTParty::Error
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "simplecov"
2 | SimpleCov.start
3 |
4 | require "httparty"
5 | require "fakeweb"
6 |
7 | def file_fixture(filename)
8 | open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename}")).read
9 | end
10 |
11 | Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each {|f| require f}
12 |
13 | RSpec.configure do |config|
14 | config.include HTTParty::StubResponse
15 | config.include HTTParty::SSLTestHelper
16 |
17 | config.before(:suite) do
18 | FakeWeb.allow_net_connect = false
19 | end
20 |
21 | config.after(:suite) do
22 | FakeWeb.allow_net_connect = true
23 | end
24 |
25 | config.expect_with :rspec do |expectations|
26 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
27 | end
28 |
29 | config.mock_with :rspec do |mocks|
30 | mocks.verify_partial_doubles = false
31 | end
32 |
33 | config.filter_run :focus
34 | config.run_all_when_everything_filtered = true
35 |
36 | config.disable_monkey_patching!
37 |
38 | config.warnings = true
39 |
40 | if config.files_to_run.one?
41 | config.default_formatter = 'doc'
42 | end
43 |
44 | config.profile_examples = 10
45 |
46 | config.order = :random
47 |
48 | Kernel.srand config.seed
49 | end
50 |
51 | RSpec::Matchers.define :use_ssl do
52 | match(&:use_ssl?)
53 | end
54 |
55 | RSpec::Matchers.define :use_cert_store do |cert_store|
56 | match do |connection|
57 | connection.cert_store == cert_store
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/features/steps/httparty_steps.rb:
--------------------------------------------------------------------------------
1 | When /^I set my HTTParty timeout option to (\d+)$/ do |timeout|
2 | @request_options[:timeout] = timeout.to_i
3 | end
4 |
5 | When /^I set my HTTParty open_timeout option to (\d+)$/ do |timeout|
6 | @request_options[:open_timeout] = timeout.to_i
7 | end
8 |
9 | When /^I set my HTTParty read_timeout option to (\d+)$/ do |timeout|
10 | @request_options[:read_timeout] = timeout.to_i
11 | end
12 |
13 | When /I call HTTParty#get with '(.*)'$/ do |url|
14 | begin
15 | @response_from_httparty = HTTParty.get("http://#{@host_and_port}#{url}", @request_options)
16 | rescue HTTParty::RedirectionTooDeep, Timeout::Error => e
17 | @exception_from_httparty = e
18 | end
19 | end
20 |
21 | When /^I call HTTParty#head with '(.*)'$/ do |url|
22 | begin
23 | @response_from_httparty = HTTParty.head("http://#{@host_and_port}#{url}", @request_options)
24 | rescue HTTParty::RedirectionTooDeep, Timeout::Error => e
25 | @exception_from_httparty = e
26 | end
27 | end
28 |
29 | When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_table|
30 | h = auth_table.hashes.first
31 | @response_from_httparty = HTTParty.get(
32 | "http://#{@host_and_port}#{url}",
33 | basic_auth: { username: h["username"], password: h["password"] }
34 | )
35 | end
36 |
37 | When /I call HTTParty#get with '(.*)' and a digest_auth hash:/ do |url, auth_table|
38 | h = auth_table.hashes.first
39 | @response_from_httparty = HTTParty.get(
40 | "http://#{@host_and_port}#{url}",
41 | digest_auth: { username: h["username"], password: h["password"] }
42 | )
43 | end
44 |
--------------------------------------------------------------------------------
/spec/httparty/hash_conversions_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe HTTParty::HashConversions do
2 | describe ".to_params" do
3 | it "creates a params string from a hash" do
4 | hash = {
5 | name: "bob",
6 | address: {
7 | street: '111 ruby ave.',
8 | city: 'ruby central',
9 | phones: ['111-111-1111', '222-222-2222']
10 | }
11 | }
12 | expect(HTTParty::HashConversions.to_params(hash)).to eq("name=bob&address[street]=111%20ruby%20ave.&address[city]=ruby%20central&address[phones][]=111-111-1111&address[phones][]=222-222-2222")
13 | end
14 | end
15 |
16 | describe ".normalize_param" do
17 | context "value is an array" do
18 | it "creates a params string" do
19 | expect(
20 | HTTParty::HashConversions.normalize_param(:people, ["Bob Jones", "Mike Smith"])
21 | ).to eq("people[]=Bob%20Jones&people[]=Mike%20Smith&")
22 | end
23 | end
24 |
25 | context "value is an empty array" do
26 | it "creates a params string" do
27 | expect(
28 | HTTParty::HashConversions.normalize_param(:people, [])
29 | ).to eq("people[]=&")
30 | end
31 | end
32 |
33 | context "value is hash" do
34 | it "creates a params string" do
35 | expect(
36 | HTTParty::HashConversions.normalize_param(:person, { name: "Bob Jones" })
37 | ).to eq("person[name]=Bob%20Jones&")
38 | end
39 | end
40 |
41 | context "value is a string" do
42 | it "creates a params string" do
43 | expect(
44 | HTTParty::HashConversions.normalize_param(:name, "Bob Jones")
45 | ).to eq("name=Bob%20Jones&")
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/spec/support/ssl_test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'pathname'
2 |
3 | module HTTParty
4 | module SSLTestHelper
5 | def ssl_verify_test(mode, ca_basename, server_cert_filename, options = {})
6 | options = {
7 | format: :json,
8 | timeout: 30
9 | }.merge(options)
10 |
11 | if mode
12 | ca_path = File.expand_path("../../fixtures/ssl/generated/#{ca_basename}", __FILE__)
13 | raise ArgumentError.new("#{ca_path} does not exist") unless File.exist?(ca_path)
14 | options[mode] = ca_path
15 | end
16 |
17 | begin
18 | test_server = SSLTestServer.new(
19 | rsa_key: File.read(File.expand_path("../../fixtures/ssl/generated/server.key", __FILE__)),
20 | cert: File.read(File.expand_path("../../fixtures/ssl/generated/#{server_cert_filename}", __FILE__)))
21 |
22 | test_server.start
23 |
24 | if mode
25 | ca_path = File.expand_path("../../fixtures/ssl/generated/#{ca_basename}", __FILE__)
26 | raise ArgumentError.new("#{ca_path} does not exist") unless File.exist?(ca_path)
27 | return HTTParty.get("https://localhost:#{test_server.port}/", options)
28 | else
29 | return HTTParty.get("https://localhost:#{test_server.port}/", options)
30 | end
31 | ensure
32 | test_server.stop if test_server
33 | end
34 |
35 | test_server = SSLTestServer.new({
36 | rsa_key: path.join('server.key').read,
37 | cert: path.join(server_cert_filename).read
38 | })
39 |
40 | test_server.start
41 |
42 | HTTParty.get("https://localhost:#{test_server.port}/", options)
43 | ensure
44 | test_server.stop if test_server
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/features/digest_authentication.feature:
--------------------------------------------------------------------------------
1 | Feature: Digest Authentication
2 |
3 | As a developer
4 | I want to be able to use a service that requires Digest Authentication
5 | Because that is not an uncommon requirement
6 |
7 | Scenario: Passing no credentials to a page requiring Digest Authentication
8 | Given a restricted page at '/digest_auth.html'
9 | When I call HTTParty#get with '/digest_auth.html'
10 | Then it should return a response with a 401 response code
11 |
12 | Scenario: Passing proper credentials to a page requiring Digest Authentication
13 | Given a remote service that returns 'Digest Authenticated Page'
14 | And that service is accessed at the path '/digest_auth.html'
15 | And that service is protected by Digest Authentication
16 | And that service requires the username 'jcash' with the password 'maninblack'
17 | When I call HTTParty#get with '/digest_auth.html' and a digest_auth hash:
18 | | username | password |
19 | | jcash | maninblack |
20 | Then the return value should match 'Digest Authenticated Page'
21 |
22 | Scenario: Passing proper credentials to a page requiring Digest Authentication using md5-sess algorithm
23 | Given a remote service that returns 'Digest Authenticated Page Using MD5-sess'
24 | And that service is accessed at the path '/digest_auth.html'
25 | And that service is protected by MD5-sess Digest Authentication
26 | And that service requires the username 'jcash' with the password 'maninblack'
27 | When I call HTTParty#get with '/digest_auth.html' and a digest_auth hash:
28 | | username | password |
29 | | jcash | maninblack |
30 | Then the return value should match 'Digest Authenticated Page Using MD5-sess'
31 |
--------------------------------------------------------------------------------
/spec/httparty/logger/apache_formatter_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2 |
3 | RSpec.describe HTTParty::Logger::ApacheFormatter do
4 | let(:subject) { described_class.new(logger_double, :info) }
5 | let(:logger_double) { double('Logger') }
6 | let(:request_double) { double('Request', http_method: Net::HTTP::Get, path: "http://my.domain.com/my_path") }
7 | let(:request_time) { Time.new.strftime("%Y-%m-%d %H:%M:%S %z") }
8 |
9 | before do
10 | subject.current_time = request_time
11 | expect(logger_double).to receive(:info).with(log_message)
12 | end
13 |
14 | describe "#format" do
15 | let(:log_message) { "[HTTParty] [#{request_time}] 302 \"GET http://my.domain.com/my_path\" - " }
16 |
17 | it "formats a response in a style that resembles apache's access log" do
18 | response_double = double(
19 | code: 302,
20 | :[] => nil
21 | )
22 |
23 | subject.format(request_double, response_double)
24 | end
25 |
26 | context 'when there is a parsed response' do
27 | let(:log_message) { "[HTTParty] [#{request_time}] 200 \"GET http://my.domain.com/my_path\" 512 "}
28 |
29 | it "can handle the Content-Length header" do
30 | # Simulate a parsed response that is an array, where accessing a string key will raise an error. See Issue #299.
31 | response_double = double(
32 | code: 200,
33 | headers: { 'Content-Length' => 512 }
34 | )
35 | allow(response_double).to receive(:[]).with('Content-Length').and_raise(TypeError.new('no implicit conversion of String into Integer'))
36 |
37 | subject.format(request_double, response_double)
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/httparty/module_inheritable_attributes.rb:
--------------------------------------------------------------------------------
1 | module HTTParty
2 | module ModuleInheritableAttributes #:nodoc:
3 | def self.included(base)
4 | base.extend(ClassMethods)
5 | end
6 |
7 | # borrowed from Rails 3.2 ActiveSupport
8 | def self.hash_deep_dup(hash)
9 | duplicate = hash.dup
10 |
11 | duplicate.each_pair do |key, value|
12 | duplicate[key] = if value.is_a?(Hash)
13 | hash_deep_dup(value)
14 | elsif value.is_a?(Proc)
15 | duplicate[key] = value.dup
16 | else
17 | value
18 | end
19 | end
20 |
21 | duplicate
22 | end
23 |
24 | module ClassMethods #:nodoc:
25 | def mattr_inheritable(*args)
26 | @mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
27 | @mattr_inheritable_attrs += args
28 |
29 | args.each do |arg|
30 | module_eval %(class << self; attr_accessor :#{arg} end)
31 | end
32 |
33 | @mattr_inheritable_attrs
34 | end
35 |
36 | def inherited(subclass)
37 | super
38 | @mattr_inheritable_attrs.each do |inheritable_attribute|
39 | ivar = "@#{inheritable_attribute}"
40 | subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
41 |
42 | if instance_variable_get(ivar).respond_to?(:merge)
43 | method = <<-EOM
44 | def self.#{inheritable_attribute}
45 | duplicate = ModuleInheritableAttributes.hash_deep_dup(#{ivar})
46 | #{ivar} = superclass.#{inheritable_attribute}.merge(duplicate)
47 | end
48 | EOM
49 |
50 | subclass.class_eval method
51 | end
52 | end
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/httparty/hash_conversions.rb:
--------------------------------------------------------------------------------
1 | require 'erb'
2 |
3 | module HTTParty
4 | module HashConversions
5 | # @return This hash as a query string
6 | #
7 | # @example
8 | # { name: "Bob",
9 | # address: {
10 | # street: '111 Ruby Ave.',
11 | # city: 'Ruby Central',
12 | # phones: ['111-111-1111', '222-222-2222']
13 | # }
14 | # }.to_params
15 | # #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
16 | def self.to_params(hash)
17 | hash.to_hash.map { |k, v| normalize_param(k, v) }.join.chop
18 | end
19 |
20 | # @param key