songkick-transport 1.2.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +52 -15
- data/lib/songkick/transport/authentication.rb +27 -0
- data/lib/songkick/transport/base.rb +146 -19
- data/lib/songkick/transport/curb.rb +31 -19
- data/lib/songkick/transport/headers.rb +15 -10
- data/lib/songkick/transport/html_report.html.erb +6 -2
- data/lib/songkick/transport/httparty.rb +15 -21
- data/lib/songkick/transport/rack_test.rb +20 -35
- data/lib/songkick/transport/reporting.rb +1 -2
- data/lib/songkick/transport/request.rb +17 -18
- data/lib/songkick/transport/response.rb +6 -7
- data/lib/songkick/transport/service.rb +51 -14
- data/lib/songkick/transport.rb +27 -25
- data/spec/songkick/transport/authentication_spec.rb +28 -0
- data/spec/songkick/transport/base_spec.rb +146 -0
- data/spec/songkick/transport/curb_spec.rb +16 -38
- data/spec/songkick/transport/httparty_spec.rb +8 -8
- data/spec/songkick/transport/request_spec.rb +9 -9
- data/spec/songkick/transport/response_spec.rb +12 -12
- data/spec/songkick/transport/service_spec.rb +70 -0
- data/spec/songkick/transport_spec.rb +168 -58
- data/spec/spec_helper.rb +21 -12
- metadata +79 -43
- data/lib/songkick/transport/header_decorator.rb +0 -27
- data/lib/songkick/transport/timeout_decorator.rb +0 -27
@@ -1,21 +1,21 @@
|
|
1
1
|
module Songkick
|
2
2
|
module Transport
|
3
|
-
|
3
|
+
|
4
4
|
class Headers
|
5
5
|
include Enumerable
|
6
|
-
|
6
|
+
|
7
7
|
def self.new(hash)
|
8
8
|
return hash if self === hash
|
9
9
|
super
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def self.normalize(header_name)
|
13
13
|
header_name.
|
14
14
|
gsub(/^HTTP_/, '').gsub('_', '-').
|
15
15
|
downcase.
|
16
16
|
gsub(/(^|-)([a-z])/) { $1 + $2.upcase }
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def initialize(hash = {})
|
20
20
|
@hash = {}
|
21
21
|
hash.each do |key, value|
|
@@ -23,30 +23,35 @@ module Songkick
|
|
23
23
|
@hash[self.class.normalize(key)] = value
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def each(&block)
|
28
28
|
@hash.each(&block)
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def [](header_name)
|
32
32
|
@hash[self.class.normalize(header_name)]
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def []=(header_name, value)
|
36
36
|
@hash[self.class.normalize(header_name)] = value
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def merge(hash)
|
40
40
|
headers = self.class.new(to_hash)
|
41
41
|
hash.each { |k,v| headers[k] = v }
|
42
42
|
headers
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def to_hash
|
46
46
|
@hash.dup
|
47
47
|
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
return false unless other.is_a?(self.class)
|
51
|
+
to_hash == other.to_hash
|
52
|
+
end
|
48
53
|
end
|
49
|
-
|
54
|
+
|
50
55
|
end
|
51
56
|
end
|
52
57
|
|
@@ -55,6 +55,7 @@
|
|
55
55
|
|
56
56
|
#transport-report td.data-footer {
|
57
57
|
font-weight: bold;
|
58
|
+
padding-bottom: 10px;
|
58
59
|
}
|
59
60
|
|
60
61
|
#transport-report td.data-ms {
|
@@ -109,7 +110,7 @@
|
|
109
110
|
<tr>
|
110
111
|
<td class="data-verb"><%= request.verb.upcase %></td>
|
111
112
|
<td class="data-path">
|
112
|
-
<% if request.verb == "GET" %>
|
113
|
+
<% if request.verb.upcase == "GET" %>
|
113
114
|
<% nice_params = (request.params.any? ? "?#{request.params.to_a.sort_by {|x,_| x.to_s}.map {|k,v| "#{k}=#{v}"}.join("&") }": "no params") %>
|
114
115
|
<% path = request.path + nice_params %>
|
115
116
|
<% uri = request.endpoint + path %>
|
@@ -127,7 +128,10 @@
|
|
127
128
|
</tr>
|
128
129
|
<% already[uri] = true %>
|
129
130
|
<% end %>
|
130
|
-
<tr
|
131
|
+
<tr>
|
132
|
+
<td colspan="2" class="data-footer"><%= hash[:requests].size %> request(s)</td>
|
133
|
+
<td class="data-footer data-ms"><%= hash[:total_duration] %> ms</td>
|
134
|
+
</tr>
|
131
135
|
</table>
|
132
136
|
|
133
137
|
<% end %>
|
@@ -2,50 +2,45 @@ require 'httparty'
|
|
2
2
|
|
3
3
|
module Songkick
|
4
4
|
module Transport
|
5
|
-
|
5
|
+
|
6
6
|
class HttParty
|
7
7
|
def self.new(host, options = {})
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
transport = klass.new
|
14
|
-
transport.user_agent = options[:user_agent]
|
15
|
-
transport.user_error_codes =
|
16
|
-
options[:user_error_codes] || DEFAULT_USER_ERROR_CODES
|
17
|
-
transport
|
8
|
+
adapter_class = options.delete(:adapter) || Adapter
|
9
|
+
adapter_class.base_uri(host)
|
10
|
+
adapter_class.format(options[:format] || DEFAULT_FORMAT)
|
11
|
+
adapter_class.default_timeout(options[:timeout] || DEFAULT_TIMEOUT)
|
12
|
+
adapter_class.new(host, options)
|
18
13
|
end
|
19
|
-
|
14
|
+
|
20
15
|
class Adapter < Base
|
21
16
|
include HTTParty
|
22
|
-
|
17
|
+
|
23
18
|
def endpoint
|
24
19
|
self.class.base_uri
|
25
20
|
end
|
26
|
-
|
21
|
+
|
27
22
|
def execute_request(req)
|
28
23
|
timeout = req.timeout || self.class.default_options[:timeout]
|
29
|
-
|
24
|
+
|
30
25
|
response = if req.use_body?
|
31
26
|
self.class.__send__(req.verb, req.path, :body => req.body, :headers => req.headers, :timeout => timeout)
|
32
27
|
else
|
33
28
|
self.class.__send__(req.verb, req.url, :headers => req.headers, :timeout => timeout)
|
34
29
|
end
|
35
|
-
|
30
|
+
|
36
31
|
process(req, response.code, response.headers, response.parsed_response)
|
37
|
-
|
32
|
+
|
38
33
|
rescue SocketError => error
|
39
34
|
logger.warn "Could not connect to host: #{self.class.base_uri}"
|
40
35
|
raise ConnectionFailedError, req
|
41
|
-
|
36
|
+
|
42
37
|
rescue Timeout::Error => error
|
43
38
|
logger.warn "Request timed out: #{req}"
|
44
39
|
raise Transport::TimeoutError, req
|
45
40
|
|
46
41
|
rescue UpstreamError => error
|
47
42
|
raise error
|
48
|
-
|
43
|
+
|
49
44
|
rescue Object => error
|
50
45
|
if error.class.name =~ /json/i or error.message =~ /json/i
|
51
46
|
logger.warn("Request returned invalid JSON: #{req}")
|
@@ -57,7 +52,6 @@ module Songkick
|
|
57
52
|
end
|
58
53
|
end
|
59
54
|
end
|
60
|
-
|
55
|
+
|
61
56
|
end
|
62
57
|
end
|
63
|
-
|
@@ -3,52 +3,37 @@ require 'timeout'
|
|
3
3
|
|
4
4
|
module Songkick
|
5
5
|
module Transport
|
6
|
-
|
6
|
+
|
7
7
|
class RackTest < Base
|
8
8
|
class Client
|
9
9
|
attr_reader :app
|
10
10
|
include Rack::Test::Methods
|
11
|
-
|
11
|
+
|
12
12
|
def initialize(app)
|
13
13
|
@app = app
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def initialize(app, options = {})
|
18
|
-
|
19
|
-
@
|
20
|
-
@user_error_codes = options[:user_error_codes] || DEFAULT_USER_ERROR_CODES
|
18
|
+
super(nil, options)
|
19
|
+
@app = app
|
21
20
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
def #{verb}(path, params = {}, head = {}, timeout = nil)
|
26
|
-
client = Client.new(@app)
|
27
|
-
start = Time.now
|
28
|
-
request = Request.new(@app, '#{verb}', path, params, headers.merge(head), timeout)
|
29
|
-
result = nil
|
30
|
-
|
31
|
-
Timeout.timeout(timeout || @timeout) do
|
32
|
-
request.headers.each { |key, value| client.header(key, value) }
|
33
|
-
response = client.#{verb}(path, params)
|
34
|
-
request.response = process("\#{path}, \#{params.inspect}", response.status, response.headers, response.body)
|
35
|
-
Reporting.record(request)
|
36
|
-
request.response
|
37
|
-
end
|
38
|
-
|
39
|
-
rescue UpstreamError => error
|
40
|
-
request.error = error
|
41
|
-
Reporting.record(request)
|
42
|
-
raise error
|
43
|
-
|
44
|
-
rescue Object => error
|
45
|
-
logger.warn(error.message)
|
46
|
-
raise UpstreamError, request
|
47
|
-
end
|
48
|
-
}
|
21
|
+
|
22
|
+
def endpoint
|
23
|
+
@app
|
49
24
|
end
|
25
|
+
|
26
|
+
def execute_request(req)
|
27
|
+
client = Client.new(@app)
|
28
|
+
|
29
|
+
Timeout.timeout(req.timeout || @timeout) do
|
30
|
+
req.headers.each { |key, value| client.header(key, value) }
|
31
|
+
response = client.__send__(req.verb, req.path, req.params)
|
32
|
+
process(req, response.status, response.headers, response.body)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
50
36
|
end
|
51
|
-
|
37
|
+
|
52
38
|
end
|
53
39
|
end
|
54
|
-
|
@@ -27,7 +27,7 @@ module Songkick
|
|
27
27
|
response = request.response
|
28
28
|
duration = (Time.now.to_f - request.start_time.to_f) * 1000
|
29
29
|
logger.info "Response status: #{response.status}, duration: #{duration.ceil}ms"
|
30
|
-
logger.debug "Response data: #{response.data.inspect}"
|
30
|
+
logger.debug { "Response data: #{response.data.inspect}" }
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.logger
|
@@ -67,4 +67,3 @@ module Songkick
|
|
67
67
|
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Songkick
|
2
2
|
module Transport
|
3
|
-
|
3
|
+
|
4
4
|
class Request
|
5
5
|
attr_reader :endpoint,
|
6
6
|
:verb,
|
@@ -10,9 +10,9 @@ module Songkick
|
|
10
10
|
:start_time,
|
11
11
|
:response,
|
12
12
|
:error
|
13
|
-
|
13
|
+
|
14
14
|
alias :http_method :verb
|
15
|
-
|
15
|
+
|
16
16
|
def initialize(endpoint, verb, path, params, headers = {}, timeout = DEFAULT_TIMEOUT)
|
17
17
|
@endpoint = endpoint
|
18
18
|
@verb = verb.to_s.downcase
|
@@ -22,39 +22,39 @@ module Songkick
|
|
22
22
|
@timeout = timeout
|
23
23
|
@start_time = start_time || Time.now
|
24
24
|
@multipart = Serialization.multipart?(params)
|
25
|
-
|
25
|
+
|
26
26
|
if use_body?
|
27
27
|
@headers['Content-Type'] ||= @multipart ? multipart_request[:content_type] : FORM_ENCODING
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def response=(response)
|
32
32
|
@response = response
|
33
33
|
@end_time = Time.now
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def error=(error)
|
37
37
|
@error = error
|
38
38
|
@end_time = Time.now
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def duration
|
42
42
|
return nil unless @end_time
|
43
43
|
(@end_time.to_f - @start_time.to_f) * 1000
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def headers
|
47
47
|
@headers.to_hash
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def use_body?
|
51
51
|
USE_BODY.include?(@verb)
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
def multipart?
|
55
55
|
@multipart
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def body
|
59
59
|
return nil unless use_body?
|
60
60
|
if @multipart
|
@@ -63,16 +63,16 @@ module Songkick
|
|
63
63
|
Serialization.build_query_string(params)
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def url
|
68
68
|
Serialization.build_url(@verb, @endpoint, @path, @params)
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def to_s
|
72
72
|
url = String === @endpoint ?
|
73
73
|
Serialization.build_url(@verb, @endpoint, @path, @params, true) :
|
74
74
|
@endpoint.to_s
|
75
|
-
|
75
|
+
|
76
76
|
command = "#{@verb.upcase} '#{url}'"
|
77
77
|
@headers.each do |key, value|
|
78
78
|
value = Serialization::SANITIZED_VALUE if Serialization.sanitize?(key)
|
@@ -83,15 +83,14 @@ module Songkick
|
|
83
83
|
command << " -d '#{query}'"
|
84
84
|
command
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
private
|
88
|
-
|
88
|
+
|
89
89
|
def multipart_request
|
90
90
|
return nil unless @multipart
|
91
91
|
@multipart_request ||= Serialization.serialize_multipart(params)
|
92
92
|
end
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
end
|
96
96
|
end
|
97
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Songkick
|
2
2
|
module Transport
|
3
|
-
|
3
|
+
|
4
4
|
class Response
|
5
5
|
def self.process(request, status, headers, body, user_error_codes=409)
|
6
6
|
case status.to_i
|
@@ -23,26 +23,25 @@ module Songkick
|
|
23
23
|
content_type = (content_type || '').split(/\s*;\s*/).first
|
24
24
|
Transport.parser_for(content_type).parse(body)
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
attr_reader :body, :data, :headers, :status
|
28
|
-
|
28
|
+
|
29
29
|
def initialize(status, headers, body)
|
30
30
|
@body = body
|
31
31
|
@data = Response.parse(body, headers['Content-Type'])
|
32
32
|
@headers = Headers.new(headers)
|
33
33
|
@status = status.to_i
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def errors
|
37
37
|
data && data['errors']
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
class OK < Response ; end
|
41
41
|
class Created < Response ; end
|
42
42
|
class NoContent < Response ; end
|
43
43
|
class UserError < Response ; end
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
@@ -1,6 +1,34 @@
|
|
1
1
|
module Songkick
|
2
2
|
module Transport
|
3
3
|
class Service
|
4
|
+
DEFAULT_TIMEOUT = 10
|
5
|
+
DEFAULT_TRANSPORT = Songkick::Transport::Curb
|
6
|
+
|
7
|
+
def self.ancestor
|
8
|
+
warn "DEPRECATED: calling ancestor on #{self}"
|
9
|
+
self.ancestors.select { |a| a.respond_to?(:get_user_agent) }[1]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.stub_transport(stub)
|
13
|
+
warn "DEPRECATED: calling stub_transport on #{self}"
|
14
|
+
@stub_transport = stub
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.extra_headers
|
18
|
+
warn "DEPRECATED: calling extra_headers on #{self}"
|
19
|
+
get_with_headers
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.this_extra_headers
|
23
|
+
warn "DEPRECATED: calling this_extra_headers on #{self}"
|
24
|
+
@with_headers || {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parent_service
|
28
|
+
superclass if superclass <= Songkick::Transport::Service
|
29
|
+
end
|
30
|
+
private_class_method :parent_service
|
31
|
+
|
4
32
|
def self.endpoint(name)
|
5
33
|
@endpoint_name = name.to_s
|
6
34
|
end
|
@@ -17,8 +45,8 @@ module Songkick
|
|
17
45
|
@transport_layer = value
|
18
46
|
end
|
19
47
|
|
20
|
-
def self.
|
21
|
-
@
|
48
|
+
def self.transport_layer_options(value)
|
49
|
+
@transport_layer_options = value
|
22
50
|
end
|
23
51
|
|
24
52
|
def self.set_endpoints(hash)
|
@@ -28,20 +56,16 @@ module Songkick
|
|
28
56
|
@endpoints = hash
|
29
57
|
end
|
30
58
|
|
31
|
-
def self.ancestor
|
32
|
-
self.ancestors.select {|a| a.respond_to?(:get_user_agent)}[1]
|
33
|
-
end
|
34
|
-
|
35
59
|
def self.get_endpoint_name
|
36
|
-
@endpoint_name || (
|
60
|
+
@endpoint_name || (parent_service && parent_service.get_endpoint_name)
|
37
61
|
end
|
38
62
|
|
39
63
|
def self.get_timeout
|
40
|
-
@timeout || (
|
64
|
+
@timeout || (parent_service && parent_service.get_timeout) || DEFAULT_TIMEOUT
|
41
65
|
end
|
42
66
|
|
43
67
|
def self.get_user_agent
|
44
|
-
@user_agent || (
|
68
|
+
@user_agent || (parent_service && parent_service.get_user_agent)
|
45
69
|
end
|
46
70
|
|
47
71
|
def self.get_endpoints
|
@@ -49,11 +73,19 @@ module Songkick
|
|
49
73
|
end
|
50
74
|
|
51
75
|
def self.get_transport_layer
|
52
|
-
@transport_layer || (
|
76
|
+
@transport_layer || (parent_service && parent_service.get_transport_layer) || DEFAULT_TRANSPORT
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.get_transport_layer_options
|
80
|
+
((parent_service && parent_service.get_transport_layer_options) || {}).merge(@transport_layer_options || {})
|
53
81
|
end
|
54
82
|
|
55
83
|
def self.get_stub_transport
|
56
|
-
@stub_transport || (
|
84
|
+
@stub_transport || (parent_service && parent_service.get_stub_transport) || nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.get_with_headers
|
88
|
+
((parent_service && parent_service.get_with_headers) || {}).merge(@with_headers || {})
|
57
89
|
end
|
58
90
|
|
59
91
|
def self.new_transport
|
@@ -66,12 +98,17 @@ module Songkick
|
|
66
98
|
unless user_agent = get_user_agent
|
67
99
|
raise "no user agent specified for #{self}, call user_agent 'foo' inside #{self} or on Songkick::Transport::Service"
|
68
100
|
end
|
69
|
-
get_stub_transport || get_transport_layer.new(endpoint, :user_agent => user_agent,
|
70
|
-
|
101
|
+
get_stub_transport || get_transport_layer.new(endpoint, { :user_agent => user_agent, :timeout => get_timeout }.merge(get_transport_layer_options))
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.with_headers(headers)
|
105
|
+
@with_headers = headers
|
71
106
|
end
|
72
107
|
|
73
108
|
def http
|
74
|
-
@http ||= self.class.new_transport
|
109
|
+
r = (@http ||= self.class.new_transport)
|
110
|
+
r.with_headers(self.class.get_with_headers) if !self.class.get_with_headers.empty?
|
111
|
+
r
|
75
112
|
end
|
76
113
|
|
77
114
|
def stub_transport(http)
|