transport 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +109 -0
- data/Rakefile +50 -0
- data/lib/transport.rb +9 -0
- data/lib/transport/common.rb +41 -0
- data/lib/transport/common/request_builder.rb +43 -0
- data/lib/transport/http.rb +43 -0
- data/lib/transport/http/formatter.rb +85 -0
- data/lib/transport/http/request_builder.rb +73 -0
- data/lib/transport/http/request_builder/parameter_serializer.rb +61 -0
- data/lib/transport/json.rb +37 -0
- data/lib/transport/json/request_builder.rb +56 -0
- data/lib/transport/json/response_parser.rb +33 -0
- data/lib/transport/spec.rb +10 -0
- data/lib/transport/spec/faker.rb +75 -0
- data/lib/transport/unexpected_status_code_error.rb +21 -0
- data/spec/acceptance/http_spec.rb +20 -0
- data/spec/acceptance/json_spec.rb +10 -0
- data/spec/lib/transport/common/request_builder_spec.rb +61 -0
- data/spec/lib/transport/common_spec.rb +68 -0
- data/spec/lib/transport/http/formatter_spec.rb +28 -0
- data/spec/lib/transport/http/request_builder/parameter_serializer_spec.rb +18 -0
- data/spec/lib/transport/http/request_builder_spec.rb +134 -0
- data/spec/lib/transport/http_spec.rb +61 -0
- data/spec/lib/transport/json/request_builder_spec.rb +78 -0
- data/spec/lib/transport/json/response_parser_spec.rb +52 -0
- data/spec/lib/transport/json_spec.rb +58 -0
- data/spec/lib/transport/spec/faker_spec.rb +87 -0
- data/spec/lib/transport/unexpected_status_code_error_spec.rb +18 -0
- data/spec/spec_helper.rb +6 -0
- metadata +133 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# Common transport layer for http transfers.
|
6
|
+
class HTTP
|
7
|
+
|
8
|
+
# Builder for the transport layer requests.
|
9
|
+
class RequestBuilder
|
10
|
+
|
11
|
+
# Serializer for transport http parameters.
|
12
|
+
class ParameterSerializer
|
13
|
+
|
14
|
+
attr_reader :result
|
15
|
+
|
16
|
+
def initialize(parameters = nil)
|
17
|
+
@parameters = parameters || { }
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
quote_parameters
|
22
|
+
serialize_parameters
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def quote_parameters
|
28
|
+
@quoted_parameters = { }
|
29
|
+
@parameters.each do |key, value|
|
30
|
+
encoded_key = CGI.escape(key.to_s)
|
31
|
+
@quoted_parameters[encoded_key] = self.class.escape value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize_parameters
|
36
|
+
@result = if @parameters.nil? || @parameters.empty?
|
37
|
+
nil
|
38
|
+
else
|
39
|
+
@quoted_parameters.collect do |key, value|
|
40
|
+
self.class.pair key, value
|
41
|
+
end.join("&")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.escape(value)
|
46
|
+
value.is_a?(Array) ? value.map{ |element| CGI.escape element } : CGI.escape(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.pair(key, value)
|
50
|
+
value.is_a?(Array) ?
|
51
|
+
value.map{ |element| "#{key}=#{element}" }.join("&") :
|
52
|
+
"#{key}=#{value}"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "common")
|
2
|
+
require File.join(File.dirname(__FILE__), "http")
|
3
|
+
|
4
|
+
module Transport
|
5
|
+
|
6
|
+
# Transport layer for json transfers.
|
7
|
+
class JSON
|
8
|
+
include Common
|
9
|
+
|
10
|
+
autoload :RequestBuilder, File.join(File.dirname(__FILE__), "json", "request_builder")
|
11
|
+
autoload :ResponseParser, File.join(File.dirname(__FILE__), "json", "response_parser")
|
12
|
+
|
13
|
+
# ParserError will be raised if the response parser couldn't process the http response.
|
14
|
+
class ParserError < StandardError; end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
perform_http_transport
|
18
|
+
parse_response
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def perform_http_transport
|
24
|
+
http_transport = HTTP.new @uri, @request, @options
|
25
|
+
http_transport.perform
|
26
|
+
@http_response = http_transport.response
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_response
|
30
|
+
response_parser = ResponseParser.new @http_response
|
31
|
+
response_parser.perform
|
32
|
+
@response = response_parser.result
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# Transport layer for json transfers.
|
6
|
+
class JSON
|
7
|
+
|
8
|
+
# Builder for the json transport layer requests.
|
9
|
+
class RequestBuilder
|
10
|
+
include Common::RequestBuilder
|
11
|
+
|
12
|
+
attr_reader :request
|
13
|
+
|
14
|
+
def perform
|
15
|
+
set_headers
|
16
|
+
convert_parameters_to_json
|
17
|
+
convert_body_to_json
|
18
|
+
build_http_request
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_headers
|
24
|
+
headers = @options[:headers] || { }
|
25
|
+
headers.merge! "Accept" => "application/json"
|
26
|
+
headers.merge! "Content-Type" => "application/json" if @options[:body]
|
27
|
+
@options[:headers] = headers
|
28
|
+
end
|
29
|
+
|
30
|
+
def convert_parameters_to_json
|
31
|
+
return unless @options[:encode_parameters]
|
32
|
+
parameters = @options[:parameters]
|
33
|
+
if parameters
|
34
|
+
parameters.each do |key, value|
|
35
|
+
parameters[key] = value.to_json if value.respond_to?(:to_json)
|
36
|
+
end
|
37
|
+
@options[:parameters] = parameters
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def convert_body_to_json
|
42
|
+
body = @options[:body]
|
43
|
+
@options[:body] = body.to_json if body
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_http_request
|
47
|
+
http_request_builder = HTTP::RequestBuilder.new @http_method, @url, @options
|
48
|
+
http_request_builder.perform
|
49
|
+
@request = http_request_builder.request
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Transport
|
4
|
+
|
5
|
+
# Transport layer for json transfers.
|
6
|
+
class JSON
|
7
|
+
|
8
|
+
# Parser for the json response.
|
9
|
+
class ResponseParser
|
10
|
+
|
11
|
+
attr_reader :result
|
12
|
+
|
13
|
+
def initialize(http_response)
|
14
|
+
@http_response = http_response
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
parse_response_body
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse_response_body
|
24
|
+
@result = @http_response && @http_response.strip != "" ? ::JSON.parse(@http_response) : nil
|
25
|
+
rescue ::JSON::ParserError => error
|
26
|
+
raise ParserError, error.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
module Transport
|
5
|
+
|
6
|
+
module Spec
|
7
|
+
|
8
|
+
# Spec helper class to fake http and json request.
|
9
|
+
class Faker
|
10
|
+
|
11
|
+
TRANSPORT_CLASSES = [
|
12
|
+
Transport::HTTP,
|
13
|
+
Transport::JSON
|
14
|
+
].freeze unless defined?(TRANSPORT_CLASSES)
|
15
|
+
|
16
|
+
# This error is raised if no fake request is found for the call.
|
17
|
+
class NoFakeRequestError < StandardError; end
|
18
|
+
|
19
|
+
def initialize(fakes)
|
20
|
+
@fakes = fakes
|
21
|
+
end
|
22
|
+
|
23
|
+
def stub_requests!
|
24
|
+
TRANSPORT_CLASSES.each do |transport_class|
|
25
|
+
stub_requests_for_transport! transport_class
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def stub_requests_for_transport!(transport_class)
|
32
|
+
transport_class.stub(:request).and_return do |*arguments|
|
33
|
+
request *arguments
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def request(http_method, url, options = { })
|
38
|
+
parameters, headers, expected_status_code = options.values_at :parameters, :headers, :expected_status_code
|
39
|
+
fake = find_fake :http_method => http_method, :url => url, :parameters => parameters, :headers => headers
|
40
|
+
response = fake[:response]
|
41
|
+
check_status_code response, expected_status_code
|
42
|
+
response[:body].dup
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_status_code(response, expected_status_code)
|
46
|
+
response_code, response_body = response.values_at :code, :body
|
47
|
+
raise Transport::UnexpectedStatusCodeError.new(
|
48
|
+
response_code.to_i,
|
49
|
+
response_body
|
50
|
+
) if expected_status_code && expected_status_code.to_s != response_code
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_fake(options)
|
54
|
+
fake = @fakes.detect{ |fake| self.class.fake_match? fake, options }
|
55
|
+
raise NoFakeRequestError, "no fake request found for [#{options[:http_method]} #{options[:url]} #{options[:parameters].inspect} #{options[:headers].inspect}]" unless fake
|
56
|
+
fake
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.fake_match?(fake, options)
|
60
|
+
fake[:http_method].to_s == options[:http_method].to_s &&
|
61
|
+
fake[:url].to_s == options[:url].to_s &&
|
62
|
+
fake[:parameters] == options[:parameters] &&
|
63
|
+
fake[:headers] == options[:headers]
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.fake!(filename)
|
67
|
+
faker = new YAML.load_file(filename)
|
68
|
+
faker.stub_requests!
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Transport
|
3
|
+
|
4
|
+
# The UnexpectedStatusCodeError is raised if the :expected_status_code option is given to
|
5
|
+
# the :request method and the responded status code is different from the expected one.
|
6
|
+
class UnexpectedStatusCodeError < StandardError
|
7
|
+
|
8
|
+
attr_reader :status_code
|
9
|
+
attr_reader :message
|
10
|
+
|
11
|
+
def initialize(status_code, message = nil)
|
12
|
+
@status_code, @message = status_code, message
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{super} received status code #{self.status_code}" + (@message ? " [#{@message}]" : "")
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
describe "fetching the google start page" do
|
5
|
+
|
6
|
+
it "should return some html" do
|
7
|
+
response = Transport::HTTP.request :get, "http://www.google.de"
|
8
|
+
response.should_not be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "fetch a search query result from google" do
|
14
|
+
|
15
|
+
it "should return some html" do
|
16
|
+
response = Transport::HTTP.request :get, "http://www.google.de/search", :parameters => { "q" => "transport" }
|
17
|
+
response.should_not be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
2
|
+
|
3
|
+
describe "fetching the delicious bookmarks" do
|
4
|
+
|
5
|
+
it "should return some json" do
|
6
|
+
response = Transport::JSON.request :get, "http://feeds.delicious.com/v2/json", :logger => Logger.new(STDOUT)
|
7
|
+
response.should be_instance_of(Array)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "common", "request_builder"))
|
3
|
+
|
4
|
+
describe Transport::Common::RequestBuilder do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@klass = Class.new
|
8
|
+
@klass.send :include, described_class
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "initialize" do
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
@object = @klass.new :test_http_method, "http://host:1234/test", :test_options
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set the http_method" do
|
18
|
+
@object.http_method.should == :test_http_method
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the url" do
|
22
|
+
@object.url.should == "http://host:1234/test"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should set the options" do
|
26
|
+
@object.options.should == :test_options
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should set the uri" do
|
30
|
+
@object.uri.host.should == "host"
|
31
|
+
@object.uri.port.should == 1234
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "build" do
|
37
|
+
|
38
|
+
before :each do
|
39
|
+
@uri = Object.new
|
40
|
+
@request = Object.new
|
41
|
+
@object = mock described_class, :perform => nil, :uri => @uri, :request => @request
|
42
|
+
@klass.stub(:new).and_return(@object)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should initialize the request builder" do
|
46
|
+
@klass.should_receive(:new).with(:test_argument).and_return(@object)
|
47
|
+
@klass.build :test_argument
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should perform the build" do
|
51
|
+
@object.should_receive(:perform)
|
52
|
+
@klass.build
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return the uri and the request" do
|
56
|
+
@klass.build.should == [ @uri, @request ]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "transport", "common"))
|
3
|
+
|
4
|
+
describe Transport::Common do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@klass = Class.new
|
8
|
+
@klass.send :include, described_class
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "initialize" do
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
@object = @klass.new :test_uri, :test_request, :test_options
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set the uri" do
|
18
|
+
@object.uri.should == :test_uri
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the request" do
|
22
|
+
@object.request.should == :test_request
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should set the options" do
|
26
|
+
@object.options.should == :test_options
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "request" do
|
32
|
+
|
33
|
+
before :each do
|
34
|
+
@uri = Object.new
|
35
|
+
@request = Object.new
|
36
|
+
|
37
|
+
@klass::RequestBuilder.stub(:build).and_return([ @uri, @request ])
|
38
|
+
|
39
|
+
@object = mock described_class, :perform => nil, :response => :test_response
|
40
|
+
@klass.stub(:new).and_return(@object)
|
41
|
+
end
|
42
|
+
|
43
|
+
def do_request
|
44
|
+
@klass.request :test_http_method, :test_url, :test_options
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should build the request" do
|
48
|
+
@klass::RequestBuilder.should_receive(:build).with(:test_http_method, :test_url, :test_options).and_return([ @uri, @request ])
|
49
|
+
do_request
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should initialize the transport" do
|
53
|
+
@klass.should_receive(:new).with(@uri, @request, :test_options).and_return(@object)
|
54
|
+
do_request
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should perform the transport" do
|
58
|
+
@object.should_receive(:perform)
|
59
|
+
do_request
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return the response" do
|
63
|
+
do_request.should == :test_response
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|