forked from francois/rest-client
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Conflicts: lib/rest_client.rb lib/restclient/resource.rb rest-client.gemspec spec/rest_client_spec.rb
- Loading branch information
Showing
23 changed files
with
1,182 additions
and
754 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
1.2.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,293 +1,2 @@ | ||
require 'uri' | ||
require 'net/https' | ||
require 'zlib' | ||
require 'stringio' | ||
|
||
require File.dirname(__FILE__) + '/rest_client/resource' | ||
require File.dirname(__FILE__) + '/rest_client/request_errors' | ||
require File.dirname(__FILE__) + '/rest_client/payload' | ||
require File.dirname(__FILE__) + '/rest_client/net_http_ext' | ||
|
||
# This module's static methods are the entry point for using the REST client. | ||
# | ||
# # GET | ||
# xml = RestClient.get 'http://example.com/resource' | ||
# jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg' | ||
# | ||
# # authentication and SSL | ||
# RestClient.get 'https://user:password@example.com/private/resource' | ||
# | ||
# # POST or PUT with a hash sends parameters as a urlencoded form body | ||
# RestClient.post 'http://example.com/resource', :param1 => 'one' | ||
# | ||
# # nest hash parameters | ||
# RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' } | ||
# | ||
# # POST and PUT with raw payloads | ||
# RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain' | ||
# RestClient.post 'http://example.com/resource.xml', xml_doc | ||
# RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf' | ||
# | ||
# # DELETE | ||
# RestClient.delete 'http://example.com/resource' | ||
# | ||
# To use with a proxy, just set RestClient.proxy to the proper http proxy: | ||
# | ||
# RestClient.proxy = "http://proxy.example.com/" | ||
# | ||
# Or inherit the proxy from the environment: | ||
# | ||
# RestClient.proxy = ENV['http_proxy'] | ||
# | ||
# For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call: | ||
# | ||
# >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz' | ||
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}" | ||
# | ||
module RestClient | ||
def self.get(url, headers={}, &b) | ||
Request.execute(:method => :get, | ||
:url => url, | ||
:headers => headers, &b) | ||
end | ||
|
||
def self.post(url, payload, headers={}, &b) | ||
Request.execute(:method => :post, | ||
:url => url, | ||
:payload => payload, | ||
:headers => headers, &b) | ||
end | ||
|
||
def self.put(url, payload, headers={}, &b) | ||
Request.execute(:method => :put, | ||
:url => url, | ||
:payload => payload, | ||
:headers => headers, &b) | ||
end | ||
|
||
def self.delete(url, headers={}, &b) | ||
Request.execute(:method => :delete, | ||
:url => url, | ||
:headers => headers, &b) | ||
end | ||
|
||
class <<self | ||
attr_accessor :proxy | ||
end | ||
|
||
# Print log of RestClient calls. Value can be stdout, stderr, or a filename. | ||
# You can also configure logging by the environment variable RESTCLIENT_LOG. | ||
def self.log=(log) | ||
@@log = log | ||
end | ||
|
||
def self.log # :nodoc: | ||
return ENV['RESTCLIENT_LOG'] if ENV['RESTCLIENT_LOG'] | ||
return @@log if defined? @@log | ||
nil | ||
end | ||
|
||
# Internal class used to build and execute the request. | ||
class Request | ||
attr_reader :method, :url, :payload, :headers, :user, :password, :timeout | ||
|
||
def self.execute(args, &b) | ||
new(args).execute(&b) | ||
end | ||
|
||
def initialize(args) | ||
@method = args[:method] or raise ArgumentError, "must pass :method" | ||
@url = args[:url] or raise ArgumentError, "must pass :url" | ||
@payload = Payload.generate(args[:payload] || '') | ||
@headers = args[:headers] || {} | ||
@user = args[:user] | ||
@password = args[:password] | ||
@timeout = args[:timeout] | ||
end | ||
|
||
def execute(&b) | ||
execute_inner(&b) | ||
rescue Redirect => e | ||
@url = e.url | ||
execute(&b) | ||
end | ||
|
||
def execute_inner(&b) | ||
uri = parse_url_with_auth(url) | ||
transmit(uri, net_http_request_class(method).new(uri.request_uri, make_headers(headers)), payload, &b) | ||
end | ||
|
||
def make_headers(user_headers) | ||
default_headers.merge(user_headers).inject({}) do |final, (key, value)| | ||
final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s | ||
final | ||
end | ||
end | ||
|
||
def net_http_class | ||
if RestClient.proxy | ||
proxy_uri = URI.parse(RestClient.proxy) | ||
Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password) | ||
else | ||
Net::HTTP | ||
end | ||
end | ||
|
||
def net_http_request_class(method) | ||
Net::HTTP.const_get(method.to_s.capitalize) | ||
end | ||
|
||
def parse_url(url) | ||
url = "http://#{url}" unless url.match(/^http/) | ||
URI.parse(url) | ||
end | ||
|
||
def parse_url_with_auth(url) | ||
uri = parse_url(url) | ||
@user = uri.user if uri.user | ||
@password = uri.password if uri.password | ||
uri | ||
end | ||
|
||
def process_payload(p=nil, parent_key=nil) | ||
unless p.is_a?(Hash) | ||
p | ||
else | ||
@headers[:content_type] ||= 'application/x-www-form-urlencoded' | ||
p.keys.map do |k| | ||
key = parent_key ? "#{parent_key}[#{k}]" : k | ||
if p[k].is_a? Hash | ||
process_payload(p[k], key) | ||
else | ||
value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) | ||
"#{key}=#{value}" | ||
end | ||
end.join("&") | ||
end | ||
end | ||
|
||
def transmit(uri, req, payload, &b) | ||
setup_credentials(req) | ||
|
||
net = net_http_class.new(uri.host, uri.port) | ||
net.use_ssl = uri.is_a?(URI::HTTPS) | ||
net.verify_mode = OpenSSL::SSL::VERIFY_NONE | ||
|
||
display_log request_log | ||
|
||
net.start do |http| | ||
http.read_timeout = @timeout if @timeout | ||
res = http.request(req, payload) | ||
display_log response_log(res) | ||
## Ok. I know this is weird but it's a hack for now | ||
## this lets process_result determine if it should read the body | ||
## into memory or not | ||
string = process_result(http.request(req, payload || "", &b), &b) | ||
if string | ||
Response.new(string, res) | ||
else | ||
nil | ||
end | ||
end | ||
rescue EOFError | ||
raise RestClient::ServerBrokeConnection | ||
rescue Timeout::Error | ||
raise RestClient::RequestTimeout | ||
ensure | ||
payload.close | ||
end | ||
|
||
def setup_credentials(req) | ||
req.basic_auth(user, password) if user | ||
end | ||
|
||
def process_result(res, &b) | ||
if %w(200 201 202).include? res.code | ||
decode(res['content-encoding'], res.body) unless b | ||
elsif %w(301 302 303).include? res.code | ||
url = res.header['Location'] | ||
|
||
if url !~ /^http/ | ||
uri = URI.parse(@url) | ||
uri.path = "/#{url}".squeeze('/') | ||
url = uri.to_s | ||
end | ||
|
||
raise Redirect, url | ||
elsif res.code == "304" | ||
raise NotModified | ||
elsif res.code == "401" | ||
raise Unauthorized, res | ||
elsif res.code == "404" | ||
raise ResourceNotFound, res | ||
else | ||
raise RequestFailed, res | ||
end | ||
end | ||
|
||
def payload | ||
@payload | ||
|
||
def decode(content_encoding, body) | ||
if content_encoding == 'gzip' | ||
Zlib::GzipReader.new(StringIO.new(body)).read | ||
elsif content_encoding == 'deflate' | ||
Zlib::Inflate.new.inflate(body) | ||
else | ||
body | ||
end | ||
end | ||
|
||
def request_log | ||
out = [] | ||
out << "RestClient.#{method} #{url.inspect}" | ||
out << (payload.size > 100 ? "(#{payload.size} byte payload)".inspect : payload.inspect) if payload | ||
out << headers.inspect.gsub(/^\{/, '').gsub(/\}$/, '') unless headers.empty? | ||
out.join(', ') | ||
end | ||
|
||
def response_log(res) | ||
"# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{(res.body) ? res.body.size : nil} bytes" | ||
end | ||
|
||
def display_log(msg) | ||
return unless log_to = RestClient.log | ||
|
||
if log_to == 'stdout' | ||
STDOUT.puts msg | ||
elsif log_to == 'stderr' | ||
STDERR.puts msg | ||
else | ||
File.open(log_to, 'a') { |f| f.puts msg } | ||
end | ||
end | ||
|
||
def default_headers | ||
@payload.headers.merge({ :accept => 'application/xml', :accept_encoding => 'gzip, deflate' }) | ||
end | ||
end | ||
|
||
class Response < String | ||
attr_reader :net_http_res | ||
|
||
def initialize(string, net_http_res) | ||
@net_http_res = net_http_res | ||
super string | ||
end | ||
|
||
def code | ||
@code ||= @net_http_res.code.to_i | ||
end | ||
|
||
def headers | ||
@headers ||= self.class.beautify_headers(@net_http_res.to_hash) | ||
end | ||
|
||
def self.beautify_headers(headers) | ||
headers.inject({}) do |out, (key, value)| | ||
out[key.gsub(/-/, '_').to_sym] = value.first | ||
out | ||
end | ||
end | ||
end | ||
end | ||
# This file exists for backward compatbility with require 'rest_client' | ||
require File.dirname(__FILE__) + '/restclient' |
Oops, something went wrong.