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,438 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+ class Connection
5
+ VALID_CONNECTION_KEYS = [:body, :family, :headers, :host, :path, :port, :query, :scheme, :user, :password,
6
+ :instrumentor, :instrumentor_name, :ssl_ca_file, :ssl_verify_peer, :chunk_size,
7
+ :nonblock, :retry_limit, :connect_timeout, :read_timeout, :write_timeout, :captures,
8
+ :exception, :expects, :mock, :proxy, :method, :idempotent, :request_block, :response_block,
9
+ :middlewares, :retries_remaining, :connection, :stack, :response, :pipeline]
10
+ attr_reader :data
11
+
12
+ def params
13
+ $stderr.puts("Excon::Connection#params is deprecated use Excon::Connection#data instead (#{caller.first})")
14
+ @data
15
+ end
16
+ def params=(new_params)
17
+ $stderr.puts("Excon::Connection#params= is deprecated use Excon::Connection#data= instead (#{caller.first})")
18
+ @data = new_params
19
+ end
20
+
21
+ def proxy
22
+ $stderr.puts("Excon::Connection#proxy is deprecated use Excon::Connection#data[:proxy] instead (#{caller.first})")
23
+ @data[:proxy]
24
+ end
25
+ def proxy=(new_proxy)
26
+ $stderr.puts("Excon::Connection#proxy= is deprecated use Excon::Connection#data[:proxy]= instead (#{caller.first})")
27
+ @data[:proxy] = new_proxy
28
+ end
29
+
30
+ def assert_valid_keys_for_argument!(argument, valid_keys)
31
+ invalid_keys = argument.keys - valid_keys
32
+ return true if invalid_keys.empty?
33
+ raise ArgumentError, "The following keys are invalid: #{invalid_keys.map(&:inspect).join(', ')}"
34
+ end
35
+ private :assert_valid_keys_for_argument!
36
+
37
+ # Initializes a new Connection instance
38
+ # @param [String] url The destination URL
39
+ # @param [Hash<Symbol, >] params One or more optional params
40
+ # @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
41
+ # @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request
42
+ # @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
43
+ # @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request
44
+ # @option params [Fixnum] :port The port on which to connect, to the destination host
45
+ # @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
46
+ # @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
47
+ # @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
48
+ # @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
49
+ # @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
50
+ # @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
51
+ def initialize(url, params = {})
52
+ assert_valid_keys_for_argument!(params, VALID_CONNECTION_KEYS)
53
+ uri = URI.parse(url)
54
+ @data = Excon.defaults.merge({
55
+ :host => uri.host,
56
+ :path => uri.path,
57
+ :port => uri.port.to_s,
58
+ :query => uri.query,
59
+ :scheme => uri.scheme,
60
+ :user => (URI.decode(uri.user) if uri.user),
61
+ :password => (URI.decode(uri.password) if uri.password),
62
+ }).merge!(params)
63
+ # merge does not deep-dup, so make sure headers is not the original
64
+ @data[:headers] = @data[:headers].dup
65
+
66
+ if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
67
+ @data[:proxy] = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY'])
68
+ elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
69
+ @data[:proxy] = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY'])
70
+ elsif @data.has_key?(:proxy)
71
+ @data[:proxy] = setup_proxy(@data[:proxy])
72
+ end
73
+
74
+ if @data[:proxy]
75
+ @data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
76
+ # https credentials happen in handshake
77
+ if @data[:scheme] == 'http' && (@data[:proxy][:user] || @data[:proxy][:password])
78
+ auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].pack('m').delete(Excon::CR_NL)
79
+ @data[:headers]['Proxy-Authorization'] = 'Basic ' << auth
80
+ end
81
+ end
82
+
83
+ if ENV.has_key?('EXCON_DEBUG') || ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
84
+ @data[:instrumentor] = Excon::StandardInstrumentor
85
+ end
86
+
87
+ # Use Basic Auth if url contains a login
88
+ if uri.user || uri.password
89
+ @data[:headers]['Authorization'] ||= 'Basic ' << ['' << uri.user.to_s << ':' << uri.password.to_s].pack('m').delete(Excon::CR_NL)
90
+ end
91
+
92
+ @socket_key = '' << @data[:host] << ':' << @data[:port]
93
+ reset
94
+ end
95
+
96
+ def request_call(datum)
97
+ begin
98
+ if datum.has_key?(:response)
99
+ # we already have data from a middleware, so bail
100
+ return datum
101
+ else
102
+ socket.data = datum
103
+ # start with "METHOD /path"
104
+ request = datum[:method].to_s.upcase << ' '
105
+ if @data[:proxy]
106
+ request << datum[:scheme] << '://' << @data[:host] << ':' << @data[:port].to_s
107
+ end
108
+ request << datum[:path]
109
+
110
+ # add query to path, if there is one
111
+ case datum[:query]
112
+ when String
113
+ request << '?' << datum[:query]
114
+ when Hash
115
+ request << '?'
116
+ datum[:query].each do |key, values|
117
+ if values.nil?
118
+ request << key.to_s << '&'
119
+ else
120
+ [values].flatten.each do |value|
121
+ request << key.to_s << '=' << CGI.escape(value.to_s) << '&'
122
+ end
123
+ end
124
+ end
125
+ request.chop! # remove trailing '&'
126
+ end
127
+
128
+ # finish first line with "HTTP/1.1\r\n"
129
+ request << HTTP_1_1
130
+
131
+ if datum.has_key?(:request_block)
132
+ datum[:headers]['Transfer-Encoding'] = 'chunked'
133
+ elsif ! (datum[:method].to_s.casecmp('GET') == 0 && datum[:body].nil?)
134
+ # The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
135
+ # if they don't, sending Content-Length:0 can cause issues.
136
+ datum[:headers]['Content-Length'] = detect_content_length(datum[:body])
137
+ end
138
+
139
+ # add headers to request
140
+ datum[:headers].each do |key, values|
141
+ [values].flatten.each do |value|
142
+ request << key.to_s << ': ' << value.to_s << CR_NL
143
+ end
144
+ end
145
+
146
+ # add additional "\r\n" to indicate end of headers
147
+ request << CR_NL
148
+
149
+ # write out the request, sans body
150
+ socket.write(request)
151
+
152
+ # write out the body
153
+ if datum.has_key?(:request_block)
154
+ while true
155
+ chunk = datum[:request_block].call
156
+ if FORCE_ENC
157
+ chunk.force_encoding('BINARY')
158
+ end
159
+ if chunk.length > 0
160
+ socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
161
+ else
162
+ socket.write('0' << CR_NL << CR_NL)
163
+ break
164
+ end
165
+ end
166
+ elsif !datum[:body].nil?
167
+ if datum[:body].is_a?(String)
168
+ unless datum[:body].empty?
169
+ socket.write(datum[:body])
170
+ end
171
+ else
172
+ if datum[:body].respond_to?(:binmode)
173
+ datum[:body].binmode
174
+ end
175
+ if datum[:body].respond_to?(:pos=)
176
+ datum[:body].pos = 0
177
+ end
178
+ while chunk = datum[:body].read(datum[:chunk_size])
179
+ socket.write(chunk)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ rescue => error
185
+ case error
186
+ when Excon::Errors::StubNotFound, Excon::Errors::Timeout
187
+ raise(error)
188
+ else
189
+ raise(Excon::Errors::SocketError.new(error))
190
+ end
191
+ end
192
+
193
+ datum
194
+ end
195
+
196
+ def response_call(datum)
197
+ datum
198
+ end
199
+
200
+ # Sends the supplied request to the destination host.
201
+ # @yield [chunk] @see Response#self.parse
202
+ # @param [Hash<Symbol, >] params One or more optional params, override defaults set in Connection.new
203
+ # @option params [String] :body text to be sent over a socket
204
+ # @option params [Hash<Symbol, String>] :headers The default headers to supply in a request
205
+ # @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
206
+ # @option params [String] :path appears after 'scheme://host:port/'
207
+ # @option params [Fixnum] :port The port on which to connect, to the destination host
208
+ # @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
209
+ # @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
210
+ def request(params, &block)
211
+ # @data has defaults, merge in new params to override
212
+ datum = @data.merge(params)
213
+ assert_valid_keys_for_argument!(params, VALID_CONNECTION_KEYS)
214
+ datum[:headers] = @data[:headers].merge(datum[:headers] || {})
215
+ datum[:headers]['Host'] ||= '' << datum[:host] << ':' << datum[:port]
216
+ datum[:retries_remaining] ||= datum[:retry_limit]
217
+
218
+ # if path is empty or doesn't start with '/', insert one
219
+ unless datum[:path][0, 1] == '/'
220
+ datum[:path].insert(0, '/')
221
+ end
222
+
223
+ if block_given?
224
+ $stderr.puts("Excon requests with a block are deprecated, pass :response_block instead (#{caller.first})")
225
+ datum[:response_block] = Proc.new
226
+ end
227
+
228
+ datum[:connection] = self
229
+
230
+ datum[:stack] = datum[:middlewares].map do |middleware|
231
+ lambda {|stack| middleware.new(stack)}
232
+ end.reverse.inject(self) do |middlewares, middleware|
233
+ middleware.call(middlewares)
234
+ end
235
+ datum = datum[:stack].request_call(datum)
236
+
237
+ unless datum[:pipeline]
238
+ datum = response(datum)
239
+
240
+ if datum[:response][:headers]['Connection'] == 'close'
241
+ reset
242
+ end
243
+
244
+ Excon::Response.new(datum[:response])
245
+ else
246
+ datum
247
+ end
248
+ rescue => request_error
249
+ reset
250
+ if datum[:idempotent] && [Excon::Errors::Timeout, Excon::Errors::SocketError,
251
+ Excon::Errors::HTTPStatusError].any? {|ex| request_error.kind_of? ex } && datum[:retries_remaining] > 1
252
+ datum[:retries_remaining] -= 1
253
+ request(datum, &block)
254
+ else
255
+ if datum.has_key?(:instrumentor)
256
+ datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.error", :error => request_error)
257
+ end
258
+ raise(request_error)
259
+ end
260
+ end
261
+
262
+ # Sends the supplied requests to the destination host using pipelining.
263
+ # @pipeline_params [Array<Hash>] pipeline_params An array of one or more optional params, override defaults set in Connection.new, see #request for details
264
+ def requests(pipeline_params)
265
+ pipeline_params.map do |params|
266
+ request(params.merge!(:pipeline => true))
267
+ end.map do |datum|
268
+ Excon::Response.new(response(datum)[:response])
269
+ end
270
+ end
271
+
272
+ def reset
273
+ (old_socket = sockets.delete(@socket_key)) && old_socket.close
274
+ end
275
+
276
+ # Generate HTTP request verb methods
277
+ Excon::HTTP_VERBS.each do |method|
278
+ class_eval <<-DEF, __FILE__, __LINE__ + 1
279
+ def #{method}(params={}, &block)
280
+ request(params.merge!(:method => :#{method}), &block)
281
+ end
282
+ DEF
283
+ end
284
+
285
+ def retry_limit=(new_retry_limit)
286
+ $stderr.puts("Excon::Connection#retry_limit= is deprecated, pass :retry_limit to the initializer (#{caller.first})")
287
+ @data[:retry_limit] = new_retry_limit
288
+ end
289
+
290
+ def retry_limit
291
+ $stderr.puts("Excon::Connection#retry_limit is deprecated, pass :retry_limit to the initializer (#{caller.first})")
292
+ @data[:retry_limit] ||= DEFAULT_RETRY_LIMIT
293
+ end
294
+
295
+ def inspect
296
+ vars = instance_variables.inject({}) do |accum, var|
297
+ accum.merge!(var.to_sym => instance_variable_get(var))
298
+ end
299
+ if vars[:'@data'][:headers].has_key?('Authorization')
300
+ vars[:'@data'] = vars[:'@data'].dup
301
+ vars[:'@data'][:headers] = vars[:'@data'][:headers].dup
302
+ vars[:'@data'][:headers]['Authorization'] = REDACTED
303
+ end
304
+ inspection = '#<Excon::Connection:'
305
+ inspection << (object_id << 1).to_s(16)
306
+ vars.each do |key, value|
307
+ inspection << ' ' << key.to_s << '=' << value.inspect
308
+ end
309
+ inspection << '>'
310
+ inspection
311
+ end
312
+
313
+ private
314
+
315
+ def detect_content_length(body)
316
+ if body.is_a?(String)
317
+ if FORCE_ENC
318
+ body.force_encoding('BINARY')
319
+ end
320
+ body.length
321
+ elsif body.respond_to?(:size)
322
+ # IO object: File, Tempfile, etc.
323
+ body.size
324
+ else
325
+ begin
326
+ File.size(body) # for 1.8.7 where file does not have size
327
+ rescue
328
+ 0
329
+ end
330
+ end
331
+ end
332
+
333
+ def response(datum={})
334
+ unless datum.has_key?(:response)
335
+ datum[:response] = {
336
+ :body => '',
337
+ :headers => {},
338
+ :status => socket.read(12)[9, 11].to_i,
339
+ :remote_ip => socket.remote_ip
340
+ }
341
+ socket.readline # read the rest of the status line and CRLF
342
+
343
+ until ((data = socket.readline).chop!).empty?
344
+ key, value = data.split(/:\s*/, 2)
345
+ datum[:response][:headers][key] = ([*datum[:response][:headers][key]] << value).compact.join(', ')
346
+ if key.casecmp('Content-Length') == 0
347
+ content_length = value.to_i
348
+ elsif (key.casecmp('Transfer-Encoding') == 0) && (value.casecmp('chunked') == 0)
349
+ transfer_encoding_chunked = true
350
+ end
351
+ end
352
+
353
+ unless (['HEAD', 'CONNECT'].include?(datum[:method].to_s.upcase)) || NO_ENTITY.include?(datum[:response][:status])
354
+
355
+ # check to see if expects was set and matched
356
+ expected_status = !datum.has_key?(:expects) || [*datum[:expects]].include?(datum[:response][:status])
357
+
358
+ # if expects matched and there is a block, use it
359
+ if expected_status && datum.has_key?(:response_block)
360
+ if transfer_encoding_chunked
361
+ # 2 == "/r/n".length
362
+ while (chunk_size = socket.readline.chop!.to_i(16)) > 0
363
+ datum[:response_block].call(socket.read(chunk_size + 2).chop!, nil, nil)
364
+ end
365
+ socket.read(2)
366
+ elsif remaining = content_length
367
+ while remaining > 0
368
+ datum[:response_block].call(socket.read([datum[:chunk_size], remaining].min), [remaining - datum[:chunk_size], 0].max, content_length)
369
+ remaining -= datum[:chunk_size]
370
+ end
371
+ else
372
+ while remaining = socket.read(datum[:chunk_size])
373
+ datum[:response_block].call(remaining, remaining.length, content_length)
374
+ end
375
+ end
376
+ else # no block or unexpected status
377
+ if transfer_encoding_chunked
378
+ while (chunk_size = socket.readline.chop!.to_i(16)) > 0
379
+ datum[:response][:body] << socket.read(chunk_size + 2).chop! # 2 == "/r/n".length
380
+ end
381
+ socket.read(2) # 2 == "/r/n".length
382
+ elsif remaining = content_length
383
+ while remaining > 0
384
+ datum[:response][:body] << socket.read([datum[:chunk_size], remaining].min)
385
+ remaining -= datum[:chunk_size]
386
+ end
387
+ else
388
+ datum[:response][:body] << socket.read
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ datum[:stack].response_call(datum)
395
+ rescue => error
396
+ case error
397
+ when Excon::Errors::HTTPStatusError, Excon::Errors::Timeout
398
+ raise(error)
399
+ else
400
+ raise(Excon::Errors::SocketError.new(error))
401
+ end
402
+ end
403
+
404
+ def socket
405
+ sockets[@socket_key] ||= if @data[:scheme] == HTTPS
406
+ Excon::SSLSocket.new(@data)
407
+ else
408
+ Excon::Socket.new(@data)
409
+ end
410
+ end
411
+
412
+ def sockets
413
+ Thread.current[:_excon_sockets] ||= {}
414
+ end
415
+
416
+ def setup_proxy(proxy)
417
+ case proxy
418
+ when String
419
+ uri = URI.parse(proxy)
420
+ unless uri.host and uri.port and uri.scheme
421
+ raise Excon::Errors::ProxyParseError, "Proxy is invalid"
422
+ end
423
+ {
424
+ :host => uri.host,
425
+ :password => uri.password,
426
+ :port => uri.port.to_s,
427
+ :scheme => uri.scheme,
428
+ :user => uri.user
429
+ }
430
+ else
431
+ proxy
432
+ end
433
+ end
434
+
435
+ end
436
+ end
437
+ end
438
+ end
@@ -0,0 +1,50 @@
1
+ module Skylight
2
+ module Vendor
3
+ module Excon
4
+
5
+ CR_NL = "\r\n"
6
+
7
+ DEFAULT_CA_FILE = File.expand_path("../cacert.pem", __FILE__)
8
+
9
+ DEFAULT_CHUNK_SIZE = 1048576 # 1 megabyte
10
+
11
+ # avoid overwrite if somebody has redefined
12
+ unless const_defined?(:CHUNK_SIZE)
13
+ CHUNK_SIZE = DEFAULT_CHUNK_SIZE
14
+ end
15
+
16
+ DEFAULT_NONBLOCK = OpenSSL::SSL::SSLSocket.public_method_defined?(:connect_nonblock) &&
17
+ OpenSSL::SSL::SSLSocket.public_method_defined?(:read_nonblock) &&
18
+ OpenSSL::SSL::SSLSocket.public_method_defined?(:write_nonblock)
19
+
20
+ DEFAULT_RETRY_LIMIT = 4
21
+
22
+ FORCE_ENC = CR_NL.respond_to?(:force_encoding)
23
+
24
+ HTTP_1_1 = " HTTP/1.1\r\n"
25
+
26
+ HTTP_VERBS = %w{connect delete get head options post put trace patch}
27
+
28
+ HTTPS = 'https'
29
+
30
+ NO_ENTITY = [204, 205, 304].freeze
31
+
32
+ REDACTED = 'REDACTED'
33
+
34
+ VERSION = '0.18.5'
35
+
36
+ unless ::IO.const_defined?(:WaitReadable)
37
+ class ::IO
38
+ module WaitReadable; end
39
+ end
40
+ end
41
+
42
+ unless ::IO.const_defined?(:WaitWritable)
43
+ class ::IO
44
+ module WaitWritable; end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end