1
0
Fork 0
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:
Adam Jacob 2009-03-16 15:28:20 -07:00
parent 3b01ab4733
commit 212ae67bfe
8 changed files with 173 additions and 64 deletions

View file

@ -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'

View 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

View 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

View file

@ -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']

View file

@ -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

View 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
View 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

View file

@ -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