framed_rails 0.1.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/.gitignore +11 -0
- data/.ruby-version +1 -0
- data/CHANGELOG +1 -0
- data/Gemfile +3 -0
- data/LICENSE +1 -0
- data/README.md +107 -0
- data/framed_rails.gemspec +37 -0
- data/lib/framed/client.rb +34 -0
- data/lib/framed/emitters.rb +113 -0
- data/lib/framed/example.rb +17 -0
- data/lib/framed/exceptions.rb +13 -0
- data/lib/framed/okjson.rb +602 -0
- data/lib/framed/rails.rb +43 -0
- data/lib/framed/railtie.rb +9 -0
- data/lib/framed/utils.rb +54 -0
- data/lib/framed/version.rb +4 -0
- data/lib/framed_rails.rb +71 -0
- data/vendor/gems/excon-0.45.3/data/cacert.pem +3860 -0
- data/vendor/gems/excon-0.45.3/lib/excon/connection.rb +469 -0
- data/vendor/gems/excon-0.45.3/lib/excon/constants.rb +142 -0
- data/vendor/gems/excon-0.45.3/lib/excon/errors.rb +155 -0
- data/vendor/gems/excon-0.45.3/lib/excon/extensions/uri.rb +33 -0
- data/vendor/gems/excon-0.45.3/lib/excon/headers.rb +83 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/base.rb +24 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/decompress.rb +35 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/escape_path.rb +11 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/expects.rb +18 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/idempotent.rb +33 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/instrumentor.rb +34 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/mock.rb +51 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/redirect_follower.rb +56 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/response_parser.rb +12 -0
- data/vendor/gems/excon-0.45.3/lib/excon/pretty_printer.rb +45 -0
- data/vendor/gems/excon-0.45.3/lib/excon/response.rb +212 -0
- data/vendor/gems/excon-0.45.3/lib/excon/socket.rb +310 -0
- data/vendor/gems/excon-0.45.3/lib/excon/ssl_socket.rb +151 -0
- data/vendor/gems/excon-0.45.3/lib/excon/standard_instrumentor.rb +27 -0
- data/vendor/gems/excon-0.45.3/lib/excon/unix_socket.rb +40 -0
- data/vendor/gems/excon-0.45.3/lib/excon/utils.rb +87 -0
- data/vendor/gems/excon-0.45.3/lib/excon.rb +234 -0
- metadata +91 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
module Excon
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
class StubNotFound < StandardError; end
|
6
|
+
class InvalidStub < StandardError; end
|
7
|
+
|
8
|
+
class SocketError < Error
|
9
|
+
attr_reader :socket_error
|
10
|
+
|
11
|
+
def initialize(socket_error=Excon::Error.new)
|
12
|
+
if socket_error.message =~ /certificate verify failed/
|
13
|
+
super("Unable to verify certificate, please set `Excon.defaults[:ssl_ca_path] = path_to_certs`, `ENV['SSL_CERT_DIR'] = path_to_certs`, `Excon.defaults[:ssl_ca_file] = path_to_file`, `ENV['SSL_CERT_FILE'] = path_to_file`, `Excon.defaults[:ssl_verify_callback] = callback` (see OpenSSL::SSL::SSLContext#verify_callback), or `Excon.defaults[:ssl_verify_peer] = false` (less secure).")
|
14
|
+
else
|
15
|
+
super("#{socket_error.message} (#{socket_error.class})")
|
16
|
+
end
|
17
|
+
set_backtrace(socket_error.backtrace)
|
18
|
+
@socket_error = socket_error
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Timeout < Error; end
|
23
|
+
|
24
|
+
class ResponseParseError < Error; end
|
25
|
+
|
26
|
+
class ProxyParseError < Error; end
|
27
|
+
|
28
|
+
class ProxyConnectionError < Error; end
|
29
|
+
|
30
|
+
class HTTPStatusError < Error
|
31
|
+
attr_reader :request, :response
|
32
|
+
|
33
|
+
def initialize(msg, request = nil, response = nil)
|
34
|
+
super(msg)
|
35
|
+
@request = request
|
36
|
+
@response = response
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# HTTP Error classes
|
41
|
+
class Informational < HTTPStatusError; end
|
42
|
+
class Success < HTTPStatusError; end
|
43
|
+
class Redirection < HTTPStatusError; end
|
44
|
+
class ClientError < HTTPStatusError; end
|
45
|
+
class ServerError < HTTPStatusError; end
|
46
|
+
|
47
|
+
class Continue < Informational; end # 100
|
48
|
+
class SwitchingProtocols < Informational; end # 101
|
49
|
+
class OK < Success; end # 200
|
50
|
+
class Created < Success; end # 201
|
51
|
+
class Accepted < Success; end # 202
|
52
|
+
class NonAuthoritativeInformation < Success; end # 203
|
53
|
+
class NoContent < Success; end # 204
|
54
|
+
class ResetContent < Success; end # 205
|
55
|
+
class PartialContent < Success; end # 206
|
56
|
+
class MultipleChoices < Redirection; end # 300
|
57
|
+
class MovedPermanently < Redirection; end # 301
|
58
|
+
class Found < Redirection; end # 302
|
59
|
+
class SeeOther < Redirection; end # 303
|
60
|
+
class NotModified < Redirection; end # 304
|
61
|
+
class UseProxy < Redirection; end # 305
|
62
|
+
class TemporaryRedirect < Redirection; end # 307
|
63
|
+
class BadRequest < ClientError; end # 400
|
64
|
+
class Unauthorized < ClientError; end # 401
|
65
|
+
class PaymentRequired < ClientError; end # 402
|
66
|
+
class Forbidden < ClientError; end # 403
|
67
|
+
class NotFound < ClientError; end # 404
|
68
|
+
class MethodNotAllowed < ClientError; end # 405
|
69
|
+
class NotAcceptable < ClientError; end # 406
|
70
|
+
class ProxyAuthenticationRequired < ClientError; end # 407
|
71
|
+
class RequestTimeout < ClientError; end # 408
|
72
|
+
class Conflict < ClientError; end # 409
|
73
|
+
class Gone < ClientError; end # 410
|
74
|
+
class LengthRequired < ClientError; end # 411
|
75
|
+
class PreconditionFailed < ClientError; end # 412
|
76
|
+
class RequestEntityTooLarge < ClientError; end # 413
|
77
|
+
class RequestURITooLong < ClientError; end # 414
|
78
|
+
class UnsupportedMediaType < ClientError; end # 415
|
79
|
+
class RequestedRangeNotSatisfiable < ClientError; end # 416
|
80
|
+
class ExpectationFailed < ClientError; end # 417
|
81
|
+
class UnprocessableEntity < ClientError; end # 422
|
82
|
+
class TooManyRequests < ClientError; end # 429
|
83
|
+
class InternalServerError < ServerError; end # 500
|
84
|
+
class NotImplemented < ServerError; end # 501
|
85
|
+
class BadGateway < ServerError; end # 502
|
86
|
+
class ServiceUnavailable < ServerError; end # 503
|
87
|
+
class GatewayTimeout < ServerError; end # 504
|
88
|
+
|
89
|
+
# Messages for nicer exceptions, from rfc2616
|
90
|
+
def self.status_error(request, response)
|
91
|
+
@errors ||= {
|
92
|
+
100 => [Excon::Errors::Continue, 'Continue'],
|
93
|
+
101 => [Excon::Errors::SwitchingProtocols, 'Switching Protocols'],
|
94
|
+
200 => [Excon::Errors::OK, 'OK'],
|
95
|
+
201 => [Excon::Errors::Created, 'Created'],
|
96
|
+
202 => [Excon::Errors::Accepted, 'Accepted'],
|
97
|
+
203 => [Excon::Errors::NonAuthoritativeInformation, 'Non-Authoritative Information'],
|
98
|
+
204 => [Excon::Errors::NoContent, 'No Content'],
|
99
|
+
205 => [Excon::Errors::ResetContent, 'Reset Content'],
|
100
|
+
206 => [Excon::Errors::PartialContent, 'Partial Content'],
|
101
|
+
300 => [Excon::Errors::MultipleChoices, 'Multiple Choices'],
|
102
|
+
301 => [Excon::Errors::MovedPermanently, 'Moved Permanently'],
|
103
|
+
302 => [Excon::Errors::Found, 'Found'],
|
104
|
+
303 => [Excon::Errors::SeeOther, 'See Other'],
|
105
|
+
304 => [Excon::Errors::NotModified, 'Not Modified'],
|
106
|
+
305 => [Excon::Errors::UseProxy, 'Use Proxy'],
|
107
|
+
307 => [Excon::Errors::TemporaryRedirect, 'Temporary Redirect'],
|
108
|
+
400 => [Excon::Errors::BadRequest, 'Bad Request'],
|
109
|
+
401 => [Excon::Errors::Unauthorized, 'Unauthorized'],
|
110
|
+
402 => [Excon::Errors::PaymentRequired, 'Payment Required'],
|
111
|
+
403 => [Excon::Errors::Forbidden, 'Forbidden'],
|
112
|
+
404 => [Excon::Errors::NotFound, 'Not Found'],
|
113
|
+
405 => [Excon::Errors::MethodNotAllowed, 'Method Not Allowed'],
|
114
|
+
406 => [Excon::Errors::NotAcceptable, 'Not Acceptable'],
|
115
|
+
407 => [Excon::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
|
116
|
+
408 => [Excon::Errors::RequestTimeout, 'Request Timeout'],
|
117
|
+
409 => [Excon::Errors::Conflict, 'Conflict'],
|
118
|
+
410 => [Excon::Errors::Gone, 'Gone'],
|
119
|
+
411 => [Excon::Errors::LengthRequired, 'Length Required'],
|
120
|
+
412 => [Excon::Errors::PreconditionFailed, 'Precondition Failed'],
|
121
|
+
413 => [Excon::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
|
122
|
+
414 => [Excon::Errors::RequestURITooLong, 'Request-URI Too Long'],
|
123
|
+
415 => [Excon::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
|
124
|
+
416 => [Excon::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
|
125
|
+
417 => [Excon::Errors::ExpectationFailed, 'Expectation Failed'],
|
126
|
+
422 => [Excon::Errors::UnprocessableEntity, 'Unprocessable Entity'],
|
127
|
+
429 => [Excon::Errors::TooManyRequests, 'Too Many Requests'],
|
128
|
+
500 => [Excon::Errors::InternalServerError, 'InternalServerError'],
|
129
|
+
501 => [Excon::Errors::NotImplemented, 'Not Implemented'],
|
130
|
+
502 => [Excon::Errors::BadGateway, 'Bad Gateway'],
|
131
|
+
503 => [Excon::Errors::ServiceUnavailable, 'Service Unavailable'],
|
132
|
+
504 => [Excon::Errors::GatewayTimeout, 'Gateway Timeout']
|
133
|
+
}
|
134
|
+
|
135
|
+
error_class, error_message = @errors[response[:status]] || [Excon::Errors::HTTPStatusError, 'Unknown']
|
136
|
+
|
137
|
+
message = StringIO.new
|
138
|
+
message.puts("Expected(#{request[:expects].inspect}) <=> Actual(#{response[:status]} #{error_message})")
|
139
|
+
|
140
|
+
if request[:debug_request]
|
141
|
+
message.puts('excon.error.request')
|
142
|
+
Excon::PrettyPrinter.pp(message, request)
|
143
|
+
end
|
144
|
+
|
145
|
+
if request[:debug_response]
|
146
|
+
message.puts('excon.error.response')
|
147
|
+
Excon::PrettyPrinter.pp(message, response.data)
|
148
|
+
end
|
149
|
+
|
150
|
+
message.rewind
|
151
|
+
error_class.new(message.read, request, response)
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# TODO: Remove this monkey patch once ruby 1.9.3+ is the minimum supported version.
|
2
|
+
#
|
3
|
+
# This patch backports URI#hostname to ruby 1.9.2 and older.
|
4
|
+
# URI#hostname is used for IPv6 support in Excon.
|
5
|
+
#
|
6
|
+
# URI#hostname was added in stdlib in v1_9_3_0 in this commit:
|
7
|
+
# https://github.com/ruby/ruby/commit/5fd45a4b79dd26f9e7b6dc41142912df911e4d7d
|
8
|
+
#
|
9
|
+
# Addressable::URI is also an URI parser accepted in some parts of Excon.
|
10
|
+
# Addressable::URI#hostname was added in addressable-2.3.5+ in this commit:
|
11
|
+
# https://github.com/sporkmonger/addressable/commit/1b94abbec1f914d5f707c92a10efbb9e69aab65e
|
12
|
+
#
|
13
|
+
# Users who want to use Addressable::URI to parse URIs must upgrade to 2.3.5 or newer.
|
14
|
+
require 'uri'
|
15
|
+
unless URI("http://foo/bar").respond_to?(:hostname)
|
16
|
+
module URI
|
17
|
+
class Generic
|
18
|
+
# extract the host part of the URI and unwrap brackets for IPv6 addresses.
|
19
|
+
#
|
20
|
+
# This method is same as URI::Generic#host except
|
21
|
+
# brackets for IPv6 (and future IP) addresses are removed.
|
22
|
+
#
|
23
|
+
# u = URI("http://[::1]/bar")
|
24
|
+
# p u.hostname #=> "::1"
|
25
|
+
# p u.host #=> "[::1]"
|
26
|
+
#
|
27
|
+
def hostname
|
28
|
+
v = self.host
|
29
|
+
/\A\[(.*)\]\z/ =~ v ? $1 : v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Excon
|
2
|
+
class Headers < Hash
|
3
|
+
|
4
|
+
SENTINEL = {}
|
5
|
+
|
6
|
+
alias_method :raw_writer, :[]=
|
7
|
+
alias_method :raw_reader, :[]
|
8
|
+
if SENTINEL.respond_to?(:assoc)
|
9
|
+
alias_method :raw_assoc, :assoc
|
10
|
+
end
|
11
|
+
alias_method :raw_delete, :delete
|
12
|
+
alias_method :raw_fetch, :fetch
|
13
|
+
alias_method :raw_has_key?, :has_key?
|
14
|
+
alias_method :raw_include?, :include?
|
15
|
+
alias_method :raw_key?, :key?
|
16
|
+
alias_method :raw_member?, :member?
|
17
|
+
alias_method :raw_merge, :merge
|
18
|
+
alias_method :raw_merge!, :merge!
|
19
|
+
alias_method :raw_rehash, :rehash
|
20
|
+
alias_method :raw_store, :store
|
21
|
+
alias_method :raw_values_at, :values_at
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@downcased = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
@downcased[key.to_s.downcase]
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :[]=, :store
|
32
|
+
def []=(key, value)
|
33
|
+
raw_writer(key, value)
|
34
|
+
@downcased[key.to_s.downcase] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
if SENTINEL.respond_to? :assoc
|
38
|
+
def assoc(obj)
|
39
|
+
@downcased.assoc(obj.downcase)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(key, &proc)
|
44
|
+
raw_delete(key, &proc)
|
45
|
+
@downcased.delete(key.to_s.downcase, &proc)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch(key, default = nil, &proc)
|
49
|
+
if proc
|
50
|
+
@downcased.fetch(key.to_s.downcase, &proc)
|
51
|
+
else
|
52
|
+
@downcased.fetch(key.to_s.downcase, default)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :has_key?, :key?
|
57
|
+
alias_method :has_key?, :member?
|
58
|
+
def has_key?(key)
|
59
|
+
raw_key?(key) || @downcased.has_key?(key.to_s.downcase)
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge(other_hash)
|
63
|
+
self.dup.merge!(other_hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge!(other_hash)
|
67
|
+
other_hash.each do |key, value|
|
68
|
+
self[key] = value
|
69
|
+
end
|
70
|
+
raw_merge!(other_hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
def rehash
|
74
|
+
@downcased.rehash
|
75
|
+
raw_rehash
|
76
|
+
end
|
77
|
+
|
78
|
+
def values_at(*keys)
|
79
|
+
@downcased.values_at(*keys.map {|key| key.to_s.downcase})
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Base
|
4
|
+
def initialize(stack)
|
5
|
+
@stack = stack
|
6
|
+
end
|
7
|
+
|
8
|
+
def error_call(datum)
|
9
|
+
# do stuff
|
10
|
+
@stack.error_call(datum)
|
11
|
+
end
|
12
|
+
|
13
|
+
def request_call(datum)
|
14
|
+
# do stuff
|
15
|
+
@stack.request_call(datum)
|
16
|
+
end
|
17
|
+
|
18
|
+
def response_call(datum)
|
19
|
+
@stack.response_call(datum)
|
20
|
+
# do stuff
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Decompress < Excon::Middleware::Base
|
4
|
+
def request_call(datum)
|
5
|
+
unless datum.has_key?(:response_block)
|
6
|
+
key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding') == 0 } || 'Accept-Encoding'
|
7
|
+
if datum[:headers][key].to_s.empty?
|
8
|
+
datum[:headers][key] = 'deflate, gzip'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
@stack.request_call(datum)
|
12
|
+
end
|
13
|
+
|
14
|
+
def response_call(datum)
|
15
|
+
unless datum.has_key?(:response_block)
|
16
|
+
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
|
17
|
+
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
18
|
+
if encoding = encodings.last
|
19
|
+
if encoding.casecmp('deflate') == 0
|
20
|
+
# assume inflate omits header
|
21
|
+
datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(datum[:response][:body])
|
22
|
+
encodings.pop
|
23
|
+
elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
|
24
|
+
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(datum[:response][:body])).read
|
25
|
+
encodings.pop
|
26
|
+
end
|
27
|
+
datum[:response][:headers][key] = encodings.join(', ')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@stack.response_call(datum)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class EscapePath < Excon::Middleware::Base
|
4
|
+
def request_call(datum)
|
5
|
+
# make sure path is encoded, prevent double encoding
|
6
|
+
datum[:path] = Excon::Utils.escape_uri(Excon::Utils.unescape_uri(datum[:path]))
|
7
|
+
@stack.request_call(datum)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Expects < Excon::Middleware::Base
|
4
|
+
def response_call(datum)
|
5
|
+
if datum.has_key?(:expects) && ![*datum[:expects]].include?(datum[:response][:status])
|
6
|
+
raise(
|
7
|
+
Excon::Errors.status_error(
|
8
|
+
datum.reject {|key,value| key == :response},
|
9
|
+
Excon::Response.new(datum[:response])
|
10
|
+
)
|
11
|
+
)
|
12
|
+
else
|
13
|
+
@stack.response_call(datum)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Idempotent < Excon::Middleware::Base
|
4
|
+
def error_call(datum)
|
5
|
+
if datum[:idempotent]
|
6
|
+
if datum.has_key?(:request_block)
|
7
|
+
if datum[:request_block].respond_to?(:rewind)
|
8
|
+
datum[:request_block].rewind
|
9
|
+
else
|
10
|
+
Excon.display_warning('Excon requests with a :request_block must implement #rewind in order to be :idempotent.')
|
11
|
+
datum[:idempotent] = false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
if datum.has_key?(:pipeline)
|
15
|
+
Excon.display_warning("Excon requests can not be :idempotent when pipelining.")
|
16
|
+
datum[:idempotent] = false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if datum[:idempotent] && [Excon::Errors::Timeout, Excon::Errors::SocketError,
|
21
|
+
Excon::Errors::HTTPStatusError].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1
|
22
|
+
# reduces remaining retries, reset connection, and restart request_call
|
23
|
+
datum[:retries_remaining] -= 1
|
24
|
+
connection = datum.delete(:connection)
|
25
|
+
datum.reject! {|key, _| !Excon::VALID_REQUEST_KEYS.include?(key) }
|
26
|
+
connection.request(datum)
|
27
|
+
else
|
28
|
+
@stack.error_call(datum)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Instrumentor < Excon::Middleware::Base
|
4
|
+
def error_call(datum)
|
5
|
+
if datum.has_key?(:instrumentor)
|
6
|
+
datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.error", :error => datum[:error])
|
7
|
+
end
|
8
|
+
@stack.error_call(datum)
|
9
|
+
end
|
10
|
+
|
11
|
+
def request_call(datum)
|
12
|
+
if datum.has_key?(:instrumentor)
|
13
|
+
if datum[:retries_remaining] < datum[:retry_limit]
|
14
|
+
event_name = "#{datum[:instrumentor_name]}.retry"
|
15
|
+
else
|
16
|
+
event_name = "#{datum[:instrumentor_name]}.request"
|
17
|
+
end
|
18
|
+
datum[:instrumentor].instrument(event_name, datum) do
|
19
|
+
@stack.request_call(datum)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
@stack.request_call(datum)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def response_call(datum)
|
27
|
+
if datum.has_key?(:instrumentor)
|
28
|
+
datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.response", datum[:response])
|
29
|
+
end
|
30
|
+
@stack.response_call(datum)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Mock < Excon::Middleware::Base
|
4
|
+
def request_call(datum)
|
5
|
+
if datum[:mock]
|
6
|
+
# convert File/Tempfile body to string before matching:
|
7
|
+
if datum[:body].respond_to?(:read)
|
8
|
+
if datum[:body].respond_to?(:binmode)
|
9
|
+
datum[:body].binmode
|
10
|
+
end
|
11
|
+
if datum[:body].respond_to?(:rewind)
|
12
|
+
datum[:body].rewind
|
13
|
+
end
|
14
|
+
datum[:body] = datum[:body].read
|
15
|
+
elsif !datum[:body].nil? && !datum[:body].is_a?(String)
|
16
|
+
raise Excon::Errors::InvalidStub.new("Request body should be a string or an IO object. #{datum[:body].class} provided")
|
17
|
+
end
|
18
|
+
|
19
|
+
if stub = Excon.stub_for(datum)
|
20
|
+
datum[:response] = {
|
21
|
+
:body => '',
|
22
|
+
:headers => {},
|
23
|
+
:status => 200,
|
24
|
+
:remote_ip => '127.0.0.1'
|
25
|
+
}
|
26
|
+
|
27
|
+
stub_datum = case stub.last
|
28
|
+
when Proc
|
29
|
+
stub.last.call(datum)
|
30
|
+
else
|
31
|
+
stub.last
|
32
|
+
end
|
33
|
+
|
34
|
+
datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
|
35
|
+
if stub_datum.has_key?(:headers)
|
36
|
+
datum[:response][:headers].merge!(stub_datum[:headers])
|
37
|
+
end
|
38
|
+
else
|
39
|
+
# if we reach here no stubs matched
|
40
|
+
message = StringIO.new
|
41
|
+
message.puts('no stubs matched')
|
42
|
+
Excon::PrettyPrinter.pp(message, datum)
|
43
|
+
raise(Excon::Errors::StubNotFound.new(message.string))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@stack.request_call(datum)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class RedirectFollower < Excon::Middleware::Base
|
4
|
+
def response_call(datum)
|
5
|
+
if datum.has_key?(:response)
|
6
|
+
case datum[:response][:status]
|
7
|
+
when 301, 302, 303, 307, 308
|
8
|
+
uri_parser = datum[:uri_parser] || Excon.defaults[:uri_parser]
|
9
|
+
_, location = datum[:response][:headers].detect do |key, value|
|
10
|
+
key.casecmp('Location') == 0
|
11
|
+
end
|
12
|
+
uri = uri_parser.parse(location)
|
13
|
+
|
14
|
+
# delete old/redirect response
|
15
|
+
response = datum.delete(:response)
|
16
|
+
|
17
|
+
params = datum.dup
|
18
|
+
params.delete(:connection)
|
19
|
+
params.delete(:password)
|
20
|
+
params.delete(:stack)
|
21
|
+
params.delete(:user)
|
22
|
+
|
23
|
+
if [301, 302, 303].include?(response[:status])
|
24
|
+
params[:method] = :get
|
25
|
+
params.delete(:body)
|
26
|
+
params[:headers].delete('Content-Length')
|
27
|
+
end
|
28
|
+
params[:headers] = datum[:headers].dup
|
29
|
+
params[:headers].delete('Authorization')
|
30
|
+
params[:headers].delete('Proxy-Connection')
|
31
|
+
params[:headers].delete('Proxy-Authorization')
|
32
|
+
params[:headers].delete('Host')
|
33
|
+
params.merge!(
|
34
|
+
:scheme => uri.scheme || datum[:scheme],
|
35
|
+
:host => uri.host || datum[:host],
|
36
|
+
:hostname => uri.hostname || datum[:hostname],
|
37
|
+
:port => uri.port || datum[:port],
|
38
|
+
:path => uri.path,
|
39
|
+
:query => uri.query
|
40
|
+
)
|
41
|
+
|
42
|
+
params.merge!(:user => Utils.unescape_uri(uri.user)) if uri.user
|
43
|
+
params.merge!(:password => Utils.unescape_uri(uri.password)) if uri.password
|
44
|
+
|
45
|
+
response = Excon::Connection.new(params).request
|
46
|
+
datum.merge!({:response => response.data})
|
47
|
+
else
|
48
|
+
@stack.response_call(datum)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
@stack.response_call(datum)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class ResponseParser < Excon::Middleware::Base
|
4
|
+
def response_call(datum)
|
5
|
+
unless datum.has_key?(:response)
|
6
|
+
datum = Excon::Response.parse(datum[:connection].send(:socket), datum)
|
7
|
+
end
|
8
|
+
@stack.response_call(datum)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Excon
|
2
|
+
class PrettyPrinter
|
3
|
+
def self.pp(io, datum, indent=0)
|
4
|
+
datum = datum.dup
|
5
|
+
|
6
|
+
# reduce duplication/noise of output
|
7
|
+
unless datum.is_a?(Excon::Headers)
|
8
|
+
datum.delete(:connection)
|
9
|
+
datum.delete(:stack)
|
10
|
+
|
11
|
+
if datum.has_key?(:headers) && datum[:headers].has_key?('Authorization')
|
12
|
+
datum[:headers] = datum[:headers].dup
|
13
|
+
datum[:headers]['Authorization'] = REDACTED
|
14
|
+
end
|
15
|
+
|
16
|
+
if datum.has_key?(:password)
|
17
|
+
datum[:password] = REDACTED
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
indent += 2
|
22
|
+
max_key_length = datum.keys.map {|key| key.inspect.length}.max
|
23
|
+
datum.keys.sort_by {|key| key.to_s}.each do |key|
|
24
|
+
value = datum[key]
|
25
|
+
io.write("#{' ' * indent}#{key.inspect.ljust(max_key_length)} => ")
|
26
|
+
case value
|
27
|
+
when Array
|
28
|
+
io.puts("[")
|
29
|
+
value.each do |v|
|
30
|
+
io.puts("#{' ' * indent} #{v.inspect}")
|
31
|
+
end
|
32
|
+
io.write("#{' ' * indent}]")
|
33
|
+
when Hash
|
34
|
+
io.puts("{")
|
35
|
+
self.pp(io, value, indent)
|
36
|
+
io.write("#{' ' * indent}}")
|
37
|
+
else
|
38
|
+
io.write("#{value.inspect}")
|
39
|
+
end
|
40
|
+
io.puts
|
41
|
+
end
|
42
|
+
indent -= 2
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|