skylight 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,140 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ module Errors
5
+
6
+ class Error < StandardError; end
7
+ class StubNotFound < StandardError; end
8
+
9
+ class SocketError < Error
10
+ attr_reader :socket_error
11
+
12
+ def initialize(socket_error=nil)
13
+ if socket_error.message =~ /certificate verify failed/
14
+ super('Unable to verify certificate, please set `Excon.defaults[:ssl_ca_path] = path_to_certs`, `Excon.defaults[:ssl_ca_file] = path_to_file`, or `Excon.defaults[:ssl_verify_peer] = false` (less secure).')
15
+ else
16
+ super("#{socket_error.message} (#{socket_error.class})")
17
+ end
18
+ set_backtrace(socket_error.backtrace)
19
+ @socket_error = socket_error
20
+ end
21
+ end
22
+
23
+ class Timeout < Error; end
24
+
25
+ class ProxyParseError < Error; end
26
+
27
+ class ProxyConnectionError < Error; end
28
+
29
+ class HTTPStatusError < Error
30
+ attr_reader :request, :response
31
+
32
+ def initialize(msg, request = nil, response = nil)
33
+ super(msg)
34
+ @request = request
35
+ @response = response
36
+ end
37
+ end
38
+
39
+ class Continue < HTTPStatusError; end # 100
40
+ class SwitchingProtocols < HTTPStatusError; end # 101
41
+ class OK < HTTPStatusError; end # 200
42
+ class Created < HTTPStatusError; end # 201
43
+ class Accepted < HTTPStatusError; end # 202
44
+ class NonAuthoritativeInformation < HTTPStatusError; end # 203
45
+ class NoContent < HTTPStatusError; end # 204
46
+ class ResetContent < HTTPStatusError; end # 205
47
+ class PartialContent < HTTPStatusError; end # 206
48
+ class MultipleChoices < HTTPStatusError; end # 300
49
+ class MovedPermanently < HTTPStatusError; end # 301
50
+ class Found < HTTPStatusError; end # 302
51
+ class SeeOther < HTTPStatusError; end # 303
52
+ class NotModified < HTTPStatusError; end # 304
53
+ class UseProxy < HTTPStatusError; end # 305
54
+ class TemporaryRedirect < HTTPStatusError; end # 307
55
+ class BadRequest < HTTPStatusError; end # 400
56
+ class Unauthorized < HTTPStatusError; end # 401
57
+ class PaymentRequired < HTTPStatusError; end # 402
58
+ class Forbidden < HTTPStatusError; end # 403
59
+ class NotFound < HTTPStatusError; end # 404
60
+ class MethodNotAllowed < HTTPStatusError; end # 405
61
+ class NotAcceptable < HTTPStatusError; end # 406
62
+ class ProxyAuthenticationRequired < HTTPStatusError; end # 407
63
+ class RequestTimeout < HTTPStatusError; end # 408
64
+ class Conflict < HTTPStatusError; end # 409
65
+ class Gone < HTTPStatusError; end # 410
66
+ class LengthRequired < HTTPStatusError; end # 411
67
+ class PreconditionFailed < HTTPStatusError; end # 412
68
+ class RequestEntityTooLarge < HTTPStatusError; end # 413
69
+ class RequestURITooLong < HTTPStatusError; end # 414
70
+ class UnsupportedMediaType < HTTPStatusError; end # 415
71
+ class RequestedRangeNotSatisfiable < HTTPStatusError; end # 416
72
+ class ExpectationFailed < HTTPStatusError; end # 417
73
+ class UnprocessableEntity < HTTPStatusError; end # 422
74
+ class InternalServerError < HTTPStatusError; end # 500
75
+ class NotImplemented < HTTPStatusError; end # 501
76
+ class BadGateway < HTTPStatusError; end # 502
77
+ class ServiceUnavailable < HTTPStatusError; end # 503
78
+ class GatewayTimeout < HTTPStatusError; end # 504
79
+
80
+ # Messages for nicer exceptions, from rfc2616
81
+ def self.status_error(request, response)
82
+ @errors ||= {
83
+ 100 => [Excon::Errors::Continue, 'Continue'],
84
+ 101 => [Excon::Errors::SwitchingProtocols, 'Switching Protocols'],
85
+ 200 => [Excon::Errors::OK, 'OK'],
86
+ 201 => [Excon::Errors::Created, 'Created'],
87
+ 202 => [Excon::Errors::Accepted, 'Accepted'],
88
+ 203 => [Excon::Errors::NonAuthoritativeInformation, 'Non-Authoritative Information'],
89
+ 204 => [Excon::Errors::NoContent, 'No Content'],
90
+ 205 => [Excon::Errors::ResetContent, 'Reset Content'],
91
+ 206 => [Excon::Errors::PartialContent, 'Partial Content'],
92
+ 300 => [Excon::Errors::MultipleChoices, 'Multiple Choices'],
93
+ 301 => [Excon::Errors::MovedPermanently, 'Moved Permanently'],
94
+ 302 => [Excon::Errors::Found, 'Found'],
95
+ 303 => [Excon::Errors::SeeOther, 'See Other'],
96
+ 304 => [Excon::Errors::NotModified, 'Not Modified'],
97
+ 305 => [Excon::Errors::UseProxy, 'Use Proxy'],
98
+ 307 => [Excon::Errors::TemporaryRedirect, 'Temporary Redirect'],
99
+ 400 => [Excon::Errors::BadRequest, 'Bad Request'],
100
+ 401 => [Excon::Errors::Unauthorized, 'Unauthorized'],
101
+ 402 => [Excon::Errors::PaymentRequired, 'Payment Required'],
102
+ 403 => [Excon::Errors::Forbidden, 'Forbidden'],
103
+ 404 => [Excon::Errors::NotFound, 'Not Found'],
104
+ 405 => [Excon::Errors::MethodNotAllowed, 'Method Not Allowed'],
105
+ 406 => [Excon::Errors::NotAcceptable, 'Not Acceptable'],
106
+ 407 => [Excon::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
107
+ 408 => [Excon::Errors::RequestTimeout, 'Request Timeout'],
108
+ 409 => [Excon::Errors::Conflict, 'Conflict'],
109
+ 410 => [Excon::Errors::Gone, 'Gone'],
110
+ 411 => [Excon::Errors::LengthRequired, 'Length Required'],
111
+ 412 => [Excon::Errors::PreconditionFailed, 'Precondition Failed'],
112
+ 413 => [Excon::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
113
+ 414 => [Excon::Errors::RequestURITooLong, 'Request-URI Too Long'],
114
+ 415 => [Excon::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
115
+ 416 => [Excon::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
116
+ 417 => [Excon::Errors::ExpectationFailed, 'Expectation Failed'],
117
+ 422 => [Excon::Errors::UnprocessableEntity, 'Unprocessable Entity'],
118
+ 500 => [Excon::Errors::InternalServerError, 'InternalServerError'],
119
+ 501 => [Excon::Errors::NotImplemented, 'Not Implemented'],
120
+ 502 => [Excon::Errors::BadGateway, 'Bad Gateway'],
121
+ 503 => [Excon::Errors::ServiceUnavailable, 'Service Unavailable'],
122
+ 504 => [Excon::Errors::GatewayTimeout, 'Gateway Timeout']
123
+ }
124
+
125
+ error, message = @errors[response[:status]] || [Excon::Errors::HTTPStatusError, 'Unknown']
126
+
127
+ # scrub authorization
128
+ request = request.dup
129
+ request.reject! {|key, value| [:connection, :stack].include?(key)}
130
+ if request.has_key?(:headers) && request[:headers].has_key?('Authorization')
131
+ request[:headers] = request[:headers].dup
132
+ request[:headers]['Authorization'] = REDACTED
133
+ end
134
+ error.new("Expected(#{request[:expects].inspect}) <=> Actual(#{response[:status]} #{message})\n request => #{request.inspect}\n response => #{response.inspect}", request, response)
135
+ end
136
+
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,23 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ module Middleware
5
+ class Base
6
+ def initialize(stack)
7
+ @stack = stack
8
+ end
9
+
10
+ def request_call(datum)
11
+ # do stuff
12
+ @stack.request_call(datum)
13
+ end
14
+
15
+ def response_call(datum)
16
+ @stack.response_call(datum)
17
+ # do stuff
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ module Middleware
5
+ class Expects < Excon::Middleware::Base
6
+ def response_call(datum)
7
+ if datum.has_key?(:expects) && ![*datum[:expects]].include?(datum[:response][:status])
8
+ raise(
9
+ Excon::Errors.status_error(
10
+ datum.reject {|key,value| key == :response},
11
+ datum[:response]
12
+ )
13
+ )
14
+ else
15
+ @stack.response_call(datum)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ module Middleware
5
+ class Instrumentor < Excon::Middleware::Base
6
+ def request_call(datum)
7
+ if datum.has_key?(:instrumentor)
8
+ if datum[:retries_remaining] < datum[:retry_limit]
9
+ event_name = "#{datum[:instrumentor_name]}.retry"
10
+ else
11
+ event_name = "#{datum[:instrumentor_name]}.request"
12
+ end
13
+ datum[:instrumentor].instrument(event_name, datum) do
14
+ @stack.request_call(datum)
15
+ end
16
+ else
17
+ @stack.request_call(datum)
18
+ end
19
+ end
20
+
21
+ def response_call(datum)
22
+ if datum.has_key?(:instrumentor)
23
+ datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.response", datum[:response])
24
+ end
25
+ @stack.response_call(datum)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,88 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ module Middleware
5
+ class Mock < Excon::Middleware::Base
6
+ def request_call(datum)
7
+ if datum[:mock]
8
+ # convert File/Tempfile body to string before matching:
9
+ unless datum[:body].nil? || datum[:body].is_a?(String)
10
+ if datum[:body].respond_to?(:binmode)
11
+ datum[:body].binmode
12
+ end
13
+ if datum[:body].respond_to?(:rewind)
14
+ datum[:body].rewind
15
+ end
16
+ datum[:body] = datum[:body].read
17
+ end
18
+
19
+ datum[:captures] = {:headers => {}} # setup data to hold captures
20
+ Excon.stubs.each do |stub, response|
21
+ headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
22
+ case value = stub[:headers][key]
23
+ when Regexp
24
+ if match = value.match(datum[:headers][key])
25
+ datum[:captures][:headers][key] = match.captures
26
+ end
27
+ match
28
+ else
29
+ value == datum[:headers][key]
30
+ end
31
+ end
32
+ non_headers_match = (stub.keys - [:headers]).all? do |key|
33
+ case value = stub[key]
34
+ when Regexp
35
+ if match = value.match(datum[key])
36
+ datum[:captures][key] = match.captures
37
+ end
38
+ match
39
+ else
40
+ value == datum[key]
41
+ end
42
+ end
43
+ if headers_match && non_headers_match
44
+ datum[:response] = {
45
+ :body => '',
46
+ :headers => {},
47
+ :status => 200,
48
+ :remote_ip => '127.0.0.1'
49
+ }
50
+
51
+ stub_datum = case response
52
+ when Proc
53
+ response.call(datum)
54
+ else
55
+ response
56
+ end
57
+
58
+ datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
59
+ if stub_datum.has_key?(:headers)
60
+ datum[:response][:headers].merge!(stub_datum[:headers])
61
+ end
62
+
63
+ if datum[:expects] && ![*datum[:expects]].include?(datum[:response][:status])
64
+ # don't pass stuff into a block if there was an error
65
+ elsif datum.has_key?(:response_block) && datum[:response].has_key?(:body)
66
+ body = datum[:response].delete(:body)
67
+ content_length = remaining = body.bytesize
68
+ i = 0
69
+ while i < body.length
70
+ datum[:response_block].call(body[i, datum[:chunk_size]], [remaining - datum[:chunk_size], 0].max, content_length)
71
+ remaining -= datum[:chunk_size]
72
+ i += datum[:chunk_size]
73
+ end
74
+ end
75
+ return @stack.request_call(datum)
76
+ end
77
+ end
78
+ # if we reach here no stubs matched
79
+ raise(Excon::Errors::StubNotFound.new('no stubs matched ' << datum.inspect))
80
+ else
81
+ @stack.request_call(datum)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,68 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ class Response
5
+
6
+ attr_accessor :data
7
+
8
+ # backwards compatability reader/writers
9
+ def body=(new_body)
10
+ @data[:body] = new_body
11
+ end
12
+ def body
13
+ @data[:body]
14
+ end
15
+ def headers=(new_headers)
16
+ @data[:headers] = new_headers
17
+ end
18
+ def headers
19
+ @data[:headers]
20
+ end
21
+ def status=(new_status)
22
+ @data[:status] = new_status
23
+ end
24
+ def status
25
+ @data[:status]
26
+ end
27
+ def remote_ip=(new_remote_ip)
28
+ @data[:remote_ip] = new_remote_ip
29
+ end
30
+ def remote_ip
31
+ @data[:remote_ip]
32
+ end
33
+
34
+ def initialize(params={})
35
+ @data = {
36
+ :body => '',
37
+ :headers => {}
38
+ }.merge(params)
39
+ @body = @data[:body]
40
+ @headers = @data[:headers]
41
+ @status = @data[:status]
42
+ @remote_ip = @data[:remote_ip]
43
+ end
44
+
45
+ def [](key)
46
+ @data[key]
47
+ end
48
+
49
+ def params
50
+ $stderr.puts("Excon::Response#params is deprecated use Excon::Response#data instead (#{caller.first})")
51
+ data
52
+ end
53
+
54
+ # Retrieve a specific header value. Header names are treated case-insensitively.
55
+ # @param [String] name Header name
56
+ def get_header(name)
57
+ headers.each do |key,value|
58
+ if key.casecmp(name) == 0
59
+ return value
60
+ end
61
+ end
62
+ nil
63
+ end
64
+
65
+ end # class Response
66
+ end # module Excon
67
+ end
68
+ end
@@ -0,0 +1,206 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ class Socket
5
+
6
+ extend Forwardable
7
+
8
+ attr_accessor :data
9
+
10
+ def params
11
+ $stderr.puts("Excon::Socket#params is deprecated use Excon::Socket#data instead (#{caller.first})")
12
+ @data
13
+ end
14
+ def params=(new_params)
15
+ $stderr.puts("Excon::Socket#params= is deprecated use Excon::Socket#data= instead (#{caller.first})")
16
+ @data = new_params
17
+ end
18
+
19
+ attr_reader :remote_ip
20
+
21
+ def_delegators(:@socket, :close, :close)
22
+ def_delegators(:@socket, :readline, :readline)
23
+
24
+ def initialize(data = {})
25
+ @data = data
26
+ @read_buffer = ''
27
+ @eof = false
28
+
29
+ @data[:family] ||= ::Socket::Constants::AF_UNSPEC
30
+ if @data[:proxy]
31
+ @data[:proxy][:family] ||= ::Socket::Constants::AF_UNSPEC
32
+ end
33
+
34
+ connect
35
+ end
36
+
37
+ def connect
38
+ @socket = nil
39
+ exception = nil
40
+
41
+ addrinfo = if @data[:proxy]
42
+ ::Socket.getaddrinfo(@data[:proxy][:host], @data[:proxy][:port], @data[:proxy][:family], ::Socket::Constants::SOCK_STREAM)
43
+ else
44
+ ::Socket.getaddrinfo(@data[:host], @data[:port], @data[:family], ::Socket::Constants::SOCK_STREAM)
45
+ end
46
+
47
+ addrinfo.each do |_, port, _, ip, a_family, s_type|
48
+ @remote_ip = ip
49
+
50
+ # nonblocking connect
51
+ begin
52
+ sockaddr = ::Socket.sockaddr_in(port, ip)
53
+
54
+ socket = ::Socket.new(a_family, s_type, 0)
55
+
56
+ if @data[:nonblock]
57
+ socket.connect_nonblock(sockaddr)
58
+ else
59
+ begin
60
+ Timeout.timeout(@data[:connect_timeout]) do
61
+ socket.connect(sockaddr)
62
+ end
63
+ rescue Timeout::Error
64
+ raise Excon::Errors::Timeout.new('connect timeout reached')
65
+ end
66
+ end
67
+
68
+ @socket = socket
69
+ break
70
+ rescue Errno::EINPROGRESS
71
+ unless IO.select(nil, [socket], nil, @data[:connect_timeout])
72
+ raise(Excon::Errors::Timeout.new("connect timeout reached"))
73
+ end
74
+ begin
75
+ socket.connect_nonblock(sockaddr)
76
+
77
+ @socket = socket
78
+ break
79
+ rescue Errno::EISCONN
80
+ @socket = socket
81
+ break
82
+ rescue SystemCallError => exception
83
+ socket.close
84
+ next
85
+ end
86
+ rescue SystemCallError => exception
87
+ socket.close
88
+ next
89
+ end
90
+ end
91
+
92
+ unless @socket
93
+ # this will be our last encountered exception
94
+ raise exception
95
+ end
96
+ end
97
+
98
+ def read(max_length=nil)
99
+ if @eof
100
+ return nil
101
+ elsif @data[:nonblock]
102
+ begin
103
+ if max_length
104
+ until @read_buffer.length >= max_length
105
+ @read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
106
+ end
107
+ else
108
+ while true
109
+ @read_buffer << @socket.read_nonblock(@data[:chunk_size])
110
+ end
111
+ end
112
+ rescue OpenSSL::SSL::SSLError => error
113
+ if error.message == 'read would block'
114
+ if IO.select([@socket], nil, nil, @data[:read_timeout])
115
+ retry
116
+ else
117
+ raise(Excon::Errors::Timeout.new("read timeout reached"))
118
+ end
119
+ else
120
+ raise(error)
121
+ end
122
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
123
+ if IO.select([@socket], nil, nil, @data[:read_timeout])
124
+ retry
125
+ else
126
+ raise(Excon::Errors::Timeout.new("read timeout reached"))
127
+ end
128
+ rescue EOFError
129
+ @eof = true
130
+ end
131
+ if max_length
132
+ @read_buffer.slice!(0, max_length)
133
+ else
134
+ # read until EOFError, so return everything
135
+ @read_buffer.slice!(0, @read_buffer.length)
136
+ end
137
+ else
138
+ begin
139
+ Timeout.timeout(@data[:read_timeout]) do
140
+ @socket.read(max_length)
141
+ end
142
+ rescue Timeout::Error
143
+ raise Excon::Errors::Timeout.new('read timeout reached')
144
+ end
145
+ end
146
+ end
147
+
148
+ def write(data)
149
+ if @data[:nonblock]
150
+ # We normally return from the return in the else block below, but
151
+ # we guard that data is still something in case we get weird
152
+ # values and String#[] returns nil. (This behavior has been observed
153
+ # in the wild, so this is a simple defensive mechanism)
154
+ while data
155
+ begin
156
+ # I wish that this API accepted a start position, then we wouldn't
157
+ # have to slice data when there is a short write.
158
+ written = @socket.write_nonblock(data)
159
+ rescue OpenSSL::SSL::SSLError => error
160
+ if error.message == 'write would block'
161
+ if IO.select(nil, [@socket], nil, @data[:write_timeout])
162
+ retry
163
+ else
164
+ raise(Excon::Errors::Timeout.new("write timeout reached"))
165
+ end
166
+ else
167
+ raise(error)
168
+ end
169
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable
170
+ if IO.select(nil, [@socket], nil, @data[:write_timeout])
171
+ retry
172
+ else
173
+ raise(Excon::Errors::Timeout.new("write timeout reached"))
174
+ end
175
+ else
176
+ # Fast, common case.
177
+ # The >= seems weird, why would it have written MORE than we
178
+ # requested. But we're getting some weird behavior when @socket
179
+ # is an OpenSSL socket, where it seems like it's saying it wrote
180
+ # more (perhaps due to SSL packet overhead?).
181
+ #
182
+ # Pretty weird, but this is a simple defensive mechanism.
183
+ return if written >= data.size
184
+
185
+ # This takes advantage of the fact that most ruby implementations
186
+ # have Copy-On-Write strings. Thusly why requesting a subrange
187
+ # of data, we actually don't copy data because the new string
188
+ # simply references a subrange of the original.
189
+ data = data[written, data.size]
190
+ end
191
+ end
192
+ else
193
+ begin
194
+ Timeout.timeout(@data[:write_timeout]) do
195
+ @socket.write(data)
196
+ end
197
+ rescue Timeout::Error
198
+ Excon::Errors::Timeout.new('write timeout reached')
199
+ end
200
+ end
201
+ end
202
+
203
+ end
204
+ end
205
+ end
206
+ end