mirror of
https://github.com/rest-client/rest-client.git
synced 2022-11-09 13:49:40 -05:00
Added raw_request support, which downloads the body to a tempfile rather than a string
This commit is contained in:
parent
3b01ab4733
commit
212ae67bfe
8 changed files with 173 additions and 64 deletions
|
@ -4,7 +4,9 @@ require 'zlib'
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
|
|
||||||
require File.dirname(__FILE__) + '/restclient/request'
|
require File.dirname(__FILE__) + '/restclient/request'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/mixin/response'
|
||||||
require File.dirname(__FILE__) + '/restclient/response'
|
require File.dirname(__FILE__) + '/restclient/response'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/raw_response'
|
||||||
require File.dirname(__FILE__) + '/restclient/resource'
|
require File.dirname(__FILE__) + '/restclient/resource'
|
||||||
require File.dirname(__FILE__) + '/restclient/exceptions'
|
require File.dirname(__FILE__) + '/restclient/exceptions'
|
||||||
|
|
||||||
|
|
43
lib/restclient/mixin/response.rb
Normal file
43
lib/restclient/mixin/response.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
module RestClient
|
||||||
|
module Mixin
|
||||||
|
module Response
|
||||||
|
attr_reader :net_http_res
|
||||||
|
|
||||||
|
# HTTP status code, always 200 since RestClient throws exceptions for
|
||||||
|
# other codes.
|
||||||
|
def code
|
||||||
|
@code ||= @net_http_res.code.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# A hash of the headers, beautified with symbols and underscores.
|
||||||
|
# e.g. "Content-type" will become :content_type.
|
||||||
|
def headers
|
||||||
|
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hash of cookies extracted from response headers
|
||||||
|
def cookies
|
||||||
|
@cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
|
||||||
|
key, val = raw_c.split('=')
|
||||||
|
unless %w(expires domain path secure).member?(key)
|
||||||
|
out[key] = val
|
||||||
|
end
|
||||||
|
out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.included(receiver)
|
||||||
|
receiver.extend(RestClient::Mixin::Response::ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def beautify_headers(headers)
|
||||||
|
headers.inject({}) do |out, (key, value)|
|
||||||
|
out[key.gsub(/-/, '_').to_sym] = value.first
|
||||||
|
out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
30
lib/restclient/raw_response.rb
Normal file
30
lib/restclient/raw_response.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
require File.dirname(__FILE__) + '/mixin/response'
|
||||||
|
|
||||||
|
module RestClient
|
||||||
|
# The response from RestClient on a raw request looks like a string, but is
|
||||||
|
# actually one of these. 99% of the time you're making a rest call all you
|
||||||
|
# care about is the body, but on the occassion you want to fetch the
|
||||||
|
# headers you can:
|
||||||
|
#
|
||||||
|
# RestClient.get('http://example.com').headers[:content_type]
|
||||||
|
#
|
||||||
|
# In addition, if you do not use the response as a string, you can access
|
||||||
|
# a Tempfile object at res.file, which contains the path to the raw
|
||||||
|
# downloaded request body.
|
||||||
|
class RawResponse
|
||||||
|
include RestClient::Mixin::Response
|
||||||
|
|
||||||
|
attr_reader :file
|
||||||
|
|
||||||
|
def initialize(tempfile, net_http_res)
|
||||||
|
@net_http_res = net_http_res
|
||||||
|
@file = tempfile
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@file.open
|
||||||
|
@file.read
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
module RestClient
|
module RestClient
|
||||||
# This class is used internally by RestClient to send the request, but you can also
|
# This class is used internally by RestClient to send the request, but you can also
|
||||||
# access it internally if you'd like to use a method not directly supported by the
|
# access it internally if you'd like to use a method not directly supported by the
|
||||||
|
@ -6,7 +8,7 @@ module RestClient
|
||||||
# RestClient::Request.execute(:method => :head, :url => 'http://example.com')
|
# RestClient::Request.execute(:method => :head, :url => 'http://example.com')
|
||||||
#
|
#
|
||||||
class Request
|
class Request
|
||||||
attr_reader :method, :url, :payload, :headers, :cookies, :user, :password, :timeout, :open_timeout
|
attr_reader :method, :url, :payload, :headers, :cookies, :user, :password, :timeout, :open_timeout, :raw_response
|
||||||
|
|
||||||
def self.execute(args)
|
def self.execute(args)
|
||||||
new(args).execute
|
new(args).execute
|
||||||
|
@ -22,6 +24,7 @@ module RestClient
|
||||||
@password = args[:password]
|
@password = args[:password]
|
||||||
@timeout = args[:timeout]
|
@timeout = args[:timeout]
|
||||||
@open_timeout = args[:open_timeout]
|
@open_timeout = args[:open_timeout]
|
||||||
|
@raw_response = args[:raw_response] || false
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
|
@ -103,10 +106,12 @@ module RestClient
|
||||||
net.start do |http|
|
net.start do |http|
|
||||||
res = http.request(req, payload)
|
res = http.request(req, payload)
|
||||||
display_log response_log(res)
|
display_log response_log(res)
|
||||||
string = process_result(res)
|
result = process_result(res)
|
||||||
|
|
||||||
if string or @method == :head
|
if result.kind_of?(String) or @method == :head
|
||||||
Response.new(string, res)
|
Response.new(result, res)
|
||||||
|
elsif result.kind_of?(Tempfile)
|
||||||
|
RawResponse.new(result, res)
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -123,7 +128,28 @@ module RestClient
|
||||||
|
|
||||||
def process_result(res)
|
def process_result(res)
|
||||||
if res.code =~ /\A2\d{2}\z/
|
if res.code =~ /\A2\d{2}\z/
|
||||||
decode res['content-encoding'], res.body if res.body
|
if @raw_response
|
||||||
|
# Taken from Chef, which as in turn...
|
||||||
|
# Stolen from http://www.ruby-forum.com/topic/166423
|
||||||
|
# Kudos to _why!
|
||||||
|
tf = Tempfile.new("rest-client")
|
||||||
|
size, total = 0, res.header['Content-Length'].to_i
|
||||||
|
res.read_body do |chunk|
|
||||||
|
tf.write(chunk)
|
||||||
|
size += chunk.size
|
||||||
|
if size == 0
|
||||||
|
display_log("Request for #{@url} done (0 length file)")
|
||||||
|
elsif total == 0
|
||||||
|
display_log("Request for #{@url} (zero content length)")
|
||||||
|
else
|
||||||
|
display_log("Request for #{@url} %d%% done (%d of %d)" % [(size * 100) / total, size, total])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tf.close
|
||||||
|
tf
|
||||||
|
else
|
||||||
|
decode res['content-encoding'], res.body if res.body
|
||||||
|
end
|
||||||
elsif %w(301 302 303).include? res.code
|
elsif %w(301 302 303).include? res.code
|
||||||
url = res.header['Location']
|
url = res.header['Location']
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require File.dirname(__FILE__) + '/mixin/response'
|
||||||
|
|
||||||
module RestClient
|
module RestClient
|
||||||
# The response from RestClient looks like a string, but is actually one of
|
# The response from RestClient looks like a string, but is actually one of
|
||||||
# these. 99% of the time you're making a rest call all you care about is
|
# these. 99% of the time you're making a rest call all you care about is
|
||||||
|
@ -6,41 +8,13 @@ module RestClient
|
||||||
# RestClient.get('http://example.com').headers[:content_type]
|
# RestClient.get('http://example.com').headers[:content_type]
|
||||||
#
|
#
|
||||||
class Response < String
|
class Response < String
|
||||||
attr_reader :net_http_res
|
|
||||||
|
include RestClient::Mixin::Response
|
||||||
|
|
||||||
def initialize(string, net_http_res)
|
def initialize(string, net_http_res)
|
||||||
@net_http_res = net_http_res
|
@net_http_res = net_http_res
|
||||||
super(string || "")
|
super(string || "")
|
||||||
end
|
end
|
||||||
|
|
||||||
# HTTP status code, always 200 since RestClient throws exceptions for
|
|
||||||
# other codes.
|
|
||||||
def code
|
|
||||||
@code ||= @net_http_res.code.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
# A hash of the headers, beautified with symbols and underscores.
|
|
||||||
# e.g. "Content-type" will become :content_type.
|
|
||||||
def headers
|
|
||||||
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hash of cookies extracted from response headers
|
|
||||||
def cookies
|
|
||||||
@cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
|
|
||||||
key, val = raw_c.split('=')
|
|
||||||
unless %w(expires domain path secure).member?(key)
|
|
||||||
out[key] = val
|
|
||||||
end
|
|
||||||
out
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.beautify_headers(headers)
|
|
||||||
headers.inject({}) do |out, (key, value)|
|
|
||||||
out[key.gsub(/-/, '_').to_sym] = value.first
|
|
||||||
out
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
46
spec/mixin/response_spec.rb
Normal file
46
spec/mixin/response_spec.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
require File.dirname(__FILE__) + '/../base'
|
||||||
|
|
||||||
|
class MockResponse
|
||||||
|
include RestClient::Mixin::Response
|
||||||
|
|
||||||
|
def initialize(body, res)
|
||||||
|
@net_http_res = res
|
||||||
|
@body = @body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe RestClient::Mixin::Response do
|
||||||
|
before do
|
||||||
|
@net_http_res = mock('net http response')
|
||||||
|
@response = MockResponse.new('abc', @net_http_res)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fetches the numeric response code" do
|
||||||
|
@net_http_res.should_receive(:code).and_return('200')
|
||||||
|
@response.code.should == 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "beautifies the headers by turning the keys to symbols" do
|
||||||
|
h = RestClient::Response.beautify_headers('content-type' => [ 'x' ])
|
||||||
|
h.keys.first.should == :content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
it "beautifies the headers by turning the values to strings instead of one-element arrays" do
|
||||||
|
h = RestClient::Response.beautify_headers('x' => [ 'text/html' ] )
|
||||||
|
h.values.first.should == 'text/html'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fetches the headers" do
|
||||||
|
@net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
|
||||||
|
@response.headers.should == { :content_type => 'text/html' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "extracts cookies from response headers" do
|
||||||
|
@net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
|
||||||
|
@response.cookies.should == { 'session_id' => '1' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can access the net http result directly" do
|
||||||
|
@response.net_http_res.should == @net_http_res
|
||||||
|
end
|
||||||
|
end
|
17
spec/raw_response_spec.rb
Normal file
17
spec/raw_response_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
require File.dirname(__FILE__) + '/base'
|
||||||
|
|
||||||
|
describe RestClient::RawResponse do
|
||||||
|
before do
|
||||||
|
@tf = mock("Tempfile", :read => "the answer is 42", :open => true)
|
||||||
|
@net_http_res = mock('net http response')
|
||||||
|
@response = RestClient::RawResponse.new(@tf, @net_http_res)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "behaves like string" do
|
||||||
|
@response.to_s.should == 'the answer is 42'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "exposes a Tempfile" do
|
||||||
|
@response.file.should == @tf
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,35 +10,6 @@ describe RestClient::Response do
|
||||||
@response.should == 'abc'
|
@response.should == 'abc'
|
||||||
end
|
end
|
||||||
|
|
||||||
it "fetches the numeric response code" do
|
|
||||||
@net_http_res.should_receive(:code).and_return('200')
|
|
||||||
@response.code.should == 200
|
|
||||||
end
|
|
||||||
|
|
||||||
it "beautifies the headers by turning the keys to symbols" do
|
|
||||||
h = RestClient::Response.beautify_headers('content-type' => [ 'x' ])
|
|
||||||
h.keys.first.should == :content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
it "beautifies the headers by turning the values to strings instead of one-element arrays" do
|
|
||||||
h = RestClient::Response.beautify_headers('x' => [ 'text/html' ] )
|
|
||||||
h.values.first.should == 'text/html'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "fetches the headers" do
|
|
||||||
@net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
|
|
||||||
@response.headers.should == { :content_type => 'text/html' }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "extracts cookies from response headers" do
|
|
||||||
@net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
|
|
||||||
@response.cookies.should == { 'session_id' => '1' }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can access the net http result directly" do
|
|
||||||
@response.net_http_res.should == @net_http_res
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts nil strings and sets it to empty for the case of HEAD" do
|
it "accepts nil strings and sets it to empty for the case of HEAD" do
|
||||||
RestClient::Response.new(nil, @net_http_res).should == ""
|
RestClient::Response.new(nil, @net_http_res).should == ""
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue