transport 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|