vault_ruby_client 0.18.2

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +287 -0
  3. data/LICENSE +364 -0
  4. data/README.md +223 -0
  5. data/lib/vault/api/approle.rb +221 -0
  6. data/lib/vault/api/auth.rb +324 -0
  7. data/lib/vault/api/auth_tls.rb +95 -0
  8. data/lib/vault/api/auth_token.rb +245 -0
  9. data/lib/vault/api/help.rb +36 -0
  10. data/lib/vault/api/kv.rb +230 -0
  11. data/lib/vault/api/logical.rb +153 -0
  12. data/lib/vault/api/secret.rb +171 -0
  13. data/lib/vault/api/sys/audit.rb +94 -0
  14. data/lib/vault/api/sys/auth.rb +119 -0
  15. data/lib/vault/api/sys/health.rb +66 -0
  16. data/lib/vault/api/sys/init.rb +86 -0
  17. data/lib/vault/api/sys/leader.rb +51 -0
  18. data/lib/vault/api/sys/lease.rb +52 -0
  19. data/lib/vault/api/sys/mount.rb +165 -0
  20. data/lib/vault/api/sys/namespace.rb +86 -0
  21. data/lib/vault/api/sys/policy.rb +95 -0
  22. data/lib/vault/api/sys/quota.rb +110 -0
  23. data/lib/vault/api/sys/seal.rb +84 -0
  24. data/lib/vault/api/sys.rb +30 -0
  25. data/lib/vault/api/transform/alphabet.rb +46 -0
  26. data/lib/vault/api/transform/role.rb +45 -0
  27. data/lib/vault/api/transform/template.rb +57 -0
  28. data/lib/vault/api/transform/transformation.rb +64 -0
  29. data/lib/vault/api/transform.rb +32 -0
  30. data/lib/vault/api.rb +17 -0
  31. data/lib/vault/client.rb +460 -0
  32. data/lib/vault/configurable.rb +53 -0
  33. data/lib/vault/defaults.rb +218 -0
  34. data/lib/vault/encode.rb +22 -0
  35. data/lib/vault/errors.rb +87 -0
  36. data/lib/vault/persistent/connection.rb +45 -0
  37. data/lib/vault/persistent/pool.rb +51 -0
  38. data/lib/vault/persistent/timed_stack_multi.rb +73 -0
  39. data/lib/vault/persistent.rb +1161 -0
  40. data/lib/vault/request.rb +47 -0
  41. data/lib/vault/response.rb +92 -0
  42. data/lib/vault/vendor/connection_pool/timed_stack.rb +181 -0
  43. data/lib/vault/vendor/connection_pool/version.rb +8 -0
  44. data/lib/vault/vendor/connection_pool.rb +153 -0
  45. data/lib/vault/version.rb +6 -0
  46. data/lib/vault_ruby_client.rb +53 -0
  47. metadata +158 -0
@@ -0,0 +1,1161 @@
1
+ # Copyright (c) HashiCorp, Inc.
2
+ # SPDX-License-Identifier: MPL-2.0
3
+
4
+ # Vendored and modified from github.com/drbrain/net-http-persistent
5
+ #
6
+ require 'net/http'
7
+ require 'uri'
8
+ require 'cgi' # for escaping
9
+ require 'vault/vendor/connection_pool'
10
+
11
+ begin
12
+ require 'net/http/pipeline'
13
+ rescue LoadError
14
+ end
15
+
16
+ autoload :OpenSSL, 'openssl'
17
+
18
+ ##
19
+ # Persistent connections for Net::HTTP
20
+ #
21
+ # PersistentHTTP maintains persistent connections across all the
22
+ # servers you wish to talk to. For each host:port you communicate with a
23
+ # single persistent connection is created.
24
+ #
25
+ # Multiple PersistentHTTP objects will share the same set of
26
+ # connections.
27
+ #
28
+ # For each thread you start a new connection will be created. A
29
+ # PersistentHTTP connection will not be shared across threads.
30
+ #
31
+ # You can shut down the HTTP connections when done by calling #shutdown. You
32
+ # should name your PersistentHTTP object if you intend to call this
33
+ # method.
34
+ #
35
+ # Example:
36
+ #
37
+ # require 'net/http/persistent'
38
+ #
39
+ # uri = URI 'http://example.com/awesome/web/service'
40
+ #
41
+ # http = PersistentHTTP.new 'my_app_name'
42
+ #
43
+ # # perform a GET
44
+ # response = http.request uri
45
+ #
46
+ # # or
47
+ #
48
+ # get = Net::HTTP::Get.new uri.request_uri
49
+ # response = http.request get
50
+ #
51
+ # # create a POST
52
+ # post_uri = uri + 'create'
53
+ # post = Net::HTTP::Post.new post_uri.path
54
+ # post.set_form_data 'some' => 'cool data'
55
+ #
56
+ # # perform the POST, the URI is always required
57
+ # response http.request post_uri, post
58
+ #
59
+ # Note that for GET, HEAD and other requests that do not have a body you want
60
+ # to use URI#request_uri not URI#path. The request_uri contains the query
61
+ # params which are sent in the body for other requests.
62
+ #
63
+ # == SSL
64
+ #
65
+ # SSL connections are automatically created depending upon the scheme of the
66
+ # URI. SSL connections are automatically verified against the default
67
+ # certificate store for your computer. You can override this by changing
68
+ # verify_mode or by specifying an alternate cert_store.
69
+ #
70
+ # Here are the SSL settings, see the individual methods for documentation:
71
+ #
72
+ # #certificate :: This client's certificate
73
+ # #ca_file :: The certificate-authorities
74
+ # #ca_path :: Directory with certificate-authorities
75
+ # #cert_store :: An SSL certificate store
76
+ # #ciphers :: List of SSl ciphers allowed
77
+ # #min_version :: Minimum SSL version to use
78
+ # #private_key :: The client's SSL private key
79
+ # #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new
80
+ # connection
81
+ # #ssl_timeout :: SSL session lifetime
82
+ # #verify_callback :: For server certificate verification
83
+ # #verify_depth :: Depth of certificate verification
84
+ # #verify_mode :: How connections should be verified
85
+ #
86
+ # == Proxies
87
+ #
88
+ # A proxy can be set through #proxy= or at initialization time by providing a
89
+ # second argument to ::new. The proxy may be the URI of the proxy server or
90
+ # <code>:ENV</code> which will consult environment variables.
91
+ #
92
+ # See #proxy= and #proxy_from_env for details.
93
+ #
94
+ # == Headers
95
+ #
96
+ # Headers may be specified for use in every request. #headers are appended to
97
+ # any headers on the request. #override_headers replace existing headers on
98
+ # the request.
99
+ #
100
+ # The difference between the two can be seen in setting the User-Agent. Using
101
+ # <code>http.headers['User-Agent'] = 'MyUserAgent'</code> will send "Ruby,
102
+ # MyUserAgent" while <code>http.override_headers['User-Agent'] =
103
+ # 'MyUserAgent'</code> will send "MyUserAgent".
104
+ #
105
+ # == Tuning
106
+ #
107
+ # === Segregation
108
+ #
109
+ # By providing an application name to ::new you can separate your connections
110
+ # from the connections of other applications.
111
+ #
112
+ # === Idle Timeout
113
+ #
114
+ # If a connection hasn't been used for this number of seconds it will automatically be
115
+ # reset upon the next use to avoid attempting to send to a closed connection.
116
+ # The default value is 5 seconds. nil means no timeout. Set through #idle_timeout.
117
+ #
118
+ # Reducing this value may help avoid the "too many connection resets" error
119
+ # when sending non-idempotent requests while increasing this value will cause
120
+ # fewer round-trips.
121
+ #
122
+ # === Read Timeout
123
+ #
124
+ # The amount of time allowed between reading two chunks from the socket. Set
125
+ # through #read_timeout
126
+ #
127
+ # === Max Requests
128
+ #
129
+ # The number of requests that should be made before opening a new connection.
130
+ # Typically many keep-alive capable servers tune this to 100 or less, so the
131
+ # 101st request will fail with ECONNRESET. If unset (default), this value has no
132
+ # effect, if set, connections will be reset on the request after max_requests.
133
+ #
134
+ # === Open Timeout
135
+ #
136
+ # The amount of time to wait for a connection to be opened. Set through
137
+ # #open_timeout.
138
+ #
139
+ # === Socket Options
140
+ #
141
+ # Socket options may be set on newly-created connections. See #socket_options
142
+ # for details.
143
+ #
144
+ # === Non-Idempotent Requests
145
+ #
146
+ # By default non-idempotent requests will not be retried per RFC 2616. By
147
+ # setting retry_change_requests to true requests will automatically be retried
148
+ # once.
149
+ #
150
+ # Only do this when you know that retrying a POST or other non-idempotent
151
+ # request is safe for your application and will not create duplicate
152
+ # resources.
153
+ #
154
+ # The recommended way to handle non-idempotent requests is the following:
155
+ #
156
+ # require 'net/http/persistent'
157
+ #
158
+ # uri = URI 'http://example.com/awesome/web/service'
159
+ # post_uri = uri + 'create'
160
+ #
161
+ # http = PersistentHTTP.new 'my_app_name'
162
+ #
163
+ # post = Net::HTTP::Post.new post_uri.path
164
+ # # ... fill in POST request
165
+ #
166
+ # begin
167
+ # response = http.request post_uri, post
168
+ # rescue PersistentHTTP::Error
169
+ #
170
+ # # POST failed, make a new request to verify the server did not process
171
+ # # the request
172
+ # exists_uri = uri + '...'
173
+ # response = http.get exists_uri
174
+ #
175
+ # # Retry if it failed
176
+ # retry if response.code == '404'
177
+ # end
178
+ #
179
+ # The method of determining if the resource was created or not is unique to
180
+ # the particular service you are using. Of course, you will want to add
181
+ # protection from infinite looping.
182
+ #
183
+ # === Connection Termination
184
+ #
185
+ # If you are done using the PersistentHTTP instance you may shut down
186
+ # all the connections in the current thread with #shutdown. This is not
187
+ # recommended for normal use, it should only be used when it will be several
188
+ # minutes before you make another HTTP request.
189
+ #
190
+ # If you are using multiple threads, call #shutdown in each thread when the
191
+ # thread is done making requests. If you don't call shutdown, that's OK.
192
+ # Ruby will automatically garbage collect and shutdown your HTTP connections
193
+ # when the thread terminates.
194
+
195
+ module Vault
196
+ class PersistentHTTP
197
+
198
+ ##
199
+ # The beginning of Time
200
+
201
+ EPOCH = Time.at 0 # :nodoc:
202
+
203
+ ##
204
+ # Is OpenSSL available? This test works with autoload
205
+
206
+ HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc:
207
+
208
+ ##
209
+ # The version of PersistentHTTP you are using
210
+
211
+ VERSION = '3.0.0'
212
+
213
+ ##
214
+ # Exceptions rescued for automatic retry on ruby 2.0.0. This overlaps with
215
+ # the exception list for ruby 1.x.
216
+
217
+ RETRIED_EXCEPTIONS = [ # :nodoc:
218
+ (Net::ReadTimeout if Net.const_defined? :ReadTimeout),
219
+ IOError,
220
+ EOFError,
221
+ Errno::ECONNRESET,
222
+ Errno::ECONNABORTED,
223
+ Errno::EPIPE,
224
+ (OpenSSL::SSL::SSLError if HAVE_OPENSSL),
225
+ Timeout::Error,
226
+ ].compact
227
+
228
+ ##
229
+ # Error class for errors raised by PersistentHTTP. Various
230
+ # SystemCallErrors are re-raised with a human-readable message under this
231
+ # class.
232
+
233
+ class Error < StandardError; end
234
+
235
+ ##
236
+ # Use this method to detect the idle timeout of the host at +uri+. The
237
+ # value returned can be used to configure #idle_timeout. +max+ controls the
238
+ # maximum idle timeout to detect.
239
+ #
240
+ # After
241
+ #
242
+ # Idle timeout detection is performed by creating a connection then
243
+ # performing a HEAD request in a loop until the connection terminates
244
+ # waiting one additional second per loop.
245
+ #
246
+ # NOTE: This may not work on ruby > 1.9.
247
+
248
+ def self.detect_idle_timeout uri, max = 10
249
+ uri = URI uri unless URI::Generic === uri
250
+ uri += '/'
251
+
252
+ req = Net::HTTP::Head.new uri.request_uri
253
+
254
+ http = new 'net-http-persistent detect_idle_timeout'
255
+
256
+ http.connection_for uri do |connection|
257
+ sleep_time = 0
258
+
259
+ http = connection.http
260
+
261
+ loop do
262
+ response = http.request req
263
+
264
+ $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
265
+
266
+ unless Net::HTTPOK === response then
267
+ raise Error, "bad response code #{response.code} detecting idle timeout"
268
+ end
269
+
270
+ break if sleep_time >= max
271
+
272
+ sleep_time += 1
273
+
274
+ $stderr.puts "sleeping #{sleep_time}" if $DEBUG
275
+ sleep sleep_time
276
+ end
277
+ end
278
+ rescue
279
+ # ignore StandardErrors, we've probably found the idle timeout.
280
+ ensure
281
+ return sleep_time unless $!
282
+ end
283
+
284
+ ##
285
+ # This client's OpenSSL::X509::Certificate
286
+
287
+ attr_reader :certificate
288
+
289
+ ##
290
+ # For Net::HTTP parity
291
+
292
+ alias cert certificate
293
+
294
+ ##
295
+ # An SSL certificate authority. Setting this will set verify_mode to
296
+ # VERIFY_PEER.
297
+
298
+ attr_reader :ca_file
299
+
300
+ ##
301
+ # A directory of SSL certificates to be used as certificate authorities.
302
+ # Setting this will set verify_mode to VERIFY_PEER.
303
+
304
+ attr_reader :ca_path
305
+
306
+ ##
307
+ # An SSL certificate store. Setting this will override the default
308
+ # certificate store. See verify_mode for more information.
309
+
310
+ attr_reader :cert_store
311
+
312
+ ##
313
+ # The ciphers allowed for SSL connections
314
+
315
+ attr_reader :ciphers
316
+
317
+ ##
318
+ # Sends debug_output to this IO via Net::HTTP#set_debug_output.
319
+ #
320
+ # Never use this method in production code, it causes a serious security
321
+ # hole.
322
+
323
+ attr_accessor :debug_output
324
+
325
+ ##
326
+ # Current connection generation
327
+
328
+ attr_reader :generation # :nodoc:
329
+
330
+ ##
331
+ # Headers that are added to every request using Net::HTTP#add_field
332
+
333
+ attr_reader :headers
334
+
335
+ ##
336
+ # Maps host:port to an HTTP version. This allows us to enable version
337
+ # specific features.
338
+
339
+ attr_reader :http_versions
340
+
341
+ ##
342
+ # Maximum time an unused connection can remain idle before being
343
+ # automatically closed.
344
+
345
+ attr_accessor :idle_timeout
346
+
347
+ ##
348
+ # Maximum number of requests on a connection before it is considered expired
349
+ # and automatically closed.
350
+
351
+ attr_accessor :max_requests
352
+
353
+ ##
354
+ # The value sent in the Keep-Alive header. Defaults to 30. Not needed for
355
+ # HTTP/1.1 servers.
356
+ #
357
+ # This may not work correctly for HTTP/1.0 servers
358
+ #
359
+ # This method may be removed in a future version as RFC 2616 does not
360
+ # require this header.
361
+
362
+ attr_accessor :keep_alive
363
+
364
+ ##
365
+ # A name for this connection. Allows you to keep your connections apart
366
+ # from everybody else's.
367
+
368
+ attr_reader :name
369
+
370
+ ##
371
+ # Minimum SSL version to use.
372
+
373
+ attr_reader :min_version
374
+
375
+ ##
376
+ # Seconds to wait until a connection is opened. See Net::HTTP#open_timeout
377
+
378
+ attr_accessor :open_timeout
379
+
380
+ ##
381
+ # Headers that are added to every request using Net::HTTP#[]=
382
+
383
+ attr_reader :override_headers
384
+
385
+ ##
386
+ # This client's SSL private key
387
+
388
+ attr_reader :private_key
389
+
390
+ ##
391
+ # For Net::HTTP parity
392
+
393
+ alias key private_key
394
+
395
+ ##
396
+ # The URL through which requests will be proxied
397
+
398
+ attr_reader :proxy_uri
399
+
400
+ ##
401
+ # List of host suffixes which will not be proxied
402
+
403
+ attr_reader :no_proxy
404
+
405
+ ##
406
+ # Test-only accessor for the connection pool
407
+
408
+ attr_reader :pool # :nodoc:
409
+
410
+ ##
411
+ # Seconds to wait until reading one block. See Net::HTTP#read_timeout
412
+
413
+ attr_accessor :read_timeout
414
+
415
+ ##
416
+ # By default SSL sessions are reused to avoid extra SSL handshakes. Set
417
+ # this to false if you have problems communicating with an HTTPS server
418
+ # like:
419
+ #
420
+ # SSL_connect [...] read finished A: unexpected message (OpenSSL::SSL::SSLError)
421
+
422
+ attr_accessor :reuse_ssl_sessions
423
+
424
+ ##
425
+ # An array of options for Socket#setsockopt.
426
+ #
427
+ # By default the TCP_NODELAY option is set on sockets.
428
+ #
429
+ # To set additional options append them to this array:
430
+ #
431
+ # http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
432
+
433
+ attr_reader :socket_options
434
+
435
+ ##
436
+ # Current SSL connection generation
437
+
438
+ attr_reader :ssl_generation # :nodoc:
439
+
440
+ ##
441
+ # SSL session lifetime
442
+
443
+ attr_reader :ssl_timeout
444
+
445
+ ##
446
+ # Where this instance's last-use times live in the thread local variables
447
+
448
+ attr_reader :timeout_key # :nodoc:
449
+
450
+ ##
451
+ # SSL verification callback. Used when ca_file or ca_path is set.
452
+
453
+ attr_reader :verify_callback
454
+
455
+ ##
456
+ # Sets the depth of SSL certificate verification
457
+
458
+ attr_reader :verify_depth
459
+
460
+ ##
461
+ # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER which verifies
462
+ # the server certificate.
463
+ #
464
+ # If no ca_file, ca_path or cert_store is set the default system certificate
465
+ # store is used.
466
+ #
467
+ # You can use +verify_mode+ to override any default values.
468
+
469
+ attr_reader :verify_mode
470
+
471
+ ##
472
+ # Enable retries of non-idempotent requests that change data (e.g. POST
473
+ # requests) when the server has disconnected.
474
+ #
475
+ # This will in the worst case lead to multiple requests with the same data,
476
+ # but it may be useful for some applications. Take care when enabling
477
+ # this option to ensure it is safe to POST or perform other non-idempotent
478
+ # requests to the server.
479
+
480
+ attr_accessor :retry_change_requests
481
+
482
+ ##
483
+ # Creates a new PersistentHTTP.
484
+ #
485
+ # Set +name+ to keep your connections apart from everybody else's. Not
486
+ # required currently, but highly recommended. Your library name should be
487
+ # good enough. This parameter will be required in a future version.
488
+ #
489
+ # +proxy+ may be set to a URI::HTTP or :ENV to pick up proxy options from
490
+ # the environment. See proxy_from_env for details.
491
+ #
492
+ # In order to use a URI for the proxy you may need to do some extra work
493
+ # beyond URI parsing if the proxy requires a password:
494
+ #
495
+ # proxy = URI 'http://proxy.example'
496
+ # proxy.user = 'AzureDiamond'
497
+ # proxy.password = 'hunter2'
498
+ #
499
+ # Set +pool_size+ to limit the maximum number of connections allowed.
500
+ # Defaults to 1/4 the number of allowed file handles. You can have no more
501
+ # than this many threads with active HTTP transactions.
502
+
503
+ def initialize name=nil, proxy=nil, pool_size=Vault::Defaults::DEFAULT_POOL_SIZE, pool_timeout=Vault::Defaults::DEFAULT_POOL_TIMEOUT
504
+ @name = name
505
+
506
+ @debug_output = nil
507
+ @proxy_uri = nil
508
+ @no_proxy = []
509
+ @headers = {}
510
+ @override_headers = {}
511
+ @http_versions = {}
512
+ @keep_alive = 30
513
+ @open_timeout = nil
514
+ @read_timeout = nil
515
+ @idle_timeout = 5
516
+ @max_requests = nil
517
+ @socket_options = []
518
+ @ssl_generation = 0 # incremented when SSL session variables change
519
+
520
+ @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
521
+ Socket.const_defined? :TCP_NODELAY
522
+
523
+ @pool = PersistentHTTP::Pool.new size: pool_size, timeout: pool_timeout do |http_args|
524
+ PersistentHTTP::Connection.new Net::HTTP, http_args, @ssl_generation
525
+ end
526
+
527
+ @certificate = nil
528
+ @ca_file = nil
529
+ @ca_path = nil
530
+ @ciphers = nil
531
+ @min_version = nil
532
+ @private_key = nil
533
+ @ssl_timeout = nil
534
+ @verify_callback = nil
535
+ @verify_depth = nil
536
+ @verify_mode = nil
537
+ @cert_store = nil
538
+
539
+ @generation = 0 # incremented when proxy URI changes
540
+
541
+ if HAVE_OPENSSL then
542
+ @verify_mode = OpenSSL::SSL::VERIFY_PEER
543
+ @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
544
+ end
545
+
546
+ @retry_change_requests = false
547
+
548
+ self.proxy = proxy if proxy
549
+ end
550
+
551
+ ##
552
+ # Sets this client's OpenSSL::X509::Certificate
553
+
554
+ def certificate= certificate
555
+ @certificate = certificate
556
+
557
+ reconnect_ssl
558
+ end
559
+
560
+ # For Net::HTTP parity
561
+ alias cert= certificate=
562
+
563
+ ##
564
+ # Sets the SSL certificate authority file.
565
+
566
+ def ca_file= file
567
+ @ca_file = file
568
+
569
+ reconnect_ssl
570
+ end
571
+
572
+ ##
573
+ # Sets the SSL certificate authority path.
574
+
575
+ def ca_path= path
576
+ @ca_path = path
577
+
578
+ reconnect_ssl
579
+ end
580
+
581
+ ##
582
+ # Overrides the default SSL certificate store used for verifying
583
+ # connections.
584
+
585
+ def cert_store= store
586
+ @cert_store = store
587
+
588
+ reconnect_ssl
589
+ end
590
+
591
+ ##
592
+ # The ciphers allowed for SSL connections
593
+
594
+ def ciphers= ciphers
595
+ @ciphers = ciphers
596
+
597
+ reconnect_ssl
598
+ end
599
+
600
+ ##
601
+ # Creates a new connection for +uri+
602
+
603
+ def connection_for uri
604
+ use_ssl = uri.scheme.downcase == 'https'
605
+
606
+ net_http_args = [uri.hostname, uri.port]
607
+
608
+ net_http_args.concat @proxy_args if
609
+ @proxy_uri and not proxy_bypass? uri.hostname, uri.port
610
+
611
+ connection = @pool.checkout net_http_args
612
+
613
+ http = connection.http
614
+
615
+ connection.ressl @ssl_generation if
616
+ connection.ssl_generation != @ssl_generation
617
+
618
+ if not http.started? then
619
+ ssl http if use_ssl
620
+ start http
621
+ elsif expired? connection then
622
+ reset connection
623
+ end
624
+
625
+ http.read_timeout = @read_timeout if @read_timeout
626
+ http.keep_alive_timeout = @idle_timeout if @idle_timeout
627
+
628
+ return yield connection
629
+ rescue Errno::ECONNREFUSED
630
+ address = http.proxy_address || http.address
631
+ port = http.proxy_port || http.port
632
+
633
+ raise Error, "connection refused: #{address}:#{port}"
634
+ rescue Errno::EHOSTDOWN
635
+ address = http.proxy_address || http.address
636
+ port = http.proxy_port || http.port
637
+
638
+ raise Error, "host down: #{address}:#{port}"
639
+ ensure
640
+ # Only perform checkin if we successfully checked a connection out
641
+ if connection
642
+ @pool.checkin net_http_args
643
+ end
644
+ end
645
+
646
+ ##
647
+ # Returns an error message containing the number of requests performed on
648
+ # this connection
649
+
650
+ def error_message connection
651
+ connection.requests -= 1 # fixup
652
+
653
+ age = Time.now - connection.last_use
654
+
655
+ "after #{connection.requests} requests on #{connection.http.object_id}, " \
656
+ "last used #{age} seconds ago"
657
+ end
658
+
659
+ ##
660
+ # URI::escape wrapper
661
+
662
+ def escape str
663
+ CGI.escape str if str
664
+ end
665
+
666
+ ##
667
+ # URI::unescape wrapper
668
+
669
+ def unescape str
670
+ CGI.unescape str if str
671
+ end
672
+
673
+
674
+ ##
675
+ # Returns true if the connection should be reset due to an idle timeout, or
676
+ # maximum request count, false otherwise.
677
+
678
+ def expired? connection
679
+ return true if @max_requests && connection.requests >= @max_requests
680
+ return false unless @idle_timeout
681
+ return true if @idle_timeout.zero?
682
+
683
+ Time.now - connection.last_use > @idle_timeout
684
+ end
685
+
686
+ ##
687
+ # Starts the Net::HTTP +connection+
688
+
689
+ def start http
690
+ http.set_debug_output @debug_output if @debug_output
691
+ http.open_timeout = @open_timeout if @open_timeout
692
+
693
+ http.start
694
+
695
+ socket = http.instance_variable_get :@socket
696
+
697
+ if socket then # for fakeweb
698
+ @socket_options.each do |option|
699
+ socket.io.setsockopt(*option)
700
+ end
701
+ end
702
+ end
703
+
704
+ ##
705
+ # Finishes the Net::HTTP +connection+
706
+
707
+ def finish connection
708
+ connection.finish
709
+
710
+ connection.http.instance_variable_set :@ssl_session, nil unless
711
+ @reuse_ssl_sessions
712
+ end
713
+
714
+ ##
715
+ # Returns the HTTP protocol version for +uri+
716
+
717
+ def http_version uri
718
+ @http_versions["#{uri.hostname}:#{uri.port}"]
719
+ end
720
+
721
+ ##
722
+ # Is +req+ idempotent according to RFC 2616?
723
+
724
+ def idempotent? req
725
+ case req
726
+ when Net::HTTP::Delete, Net::HTTP::Get, Net::HTTP::Head,
727
+ Net::HTTP::Options, Net::HTTP::Put, Net::HTTP::Trace then
728
+ true
729
+ end
730
+ end
731
+
732
+ ##
733
+ # Is the request +req+ idempotent or is retry_change_requests allowed.
734
+
735
+ def can_retry? req
736
+ @retry_change_requests && !idempotent?(req)
737
+ end
738
+
739
+ ##
740
+ # Adds "http://" to the String +uri+ if it is missing.
741
+
742
+ def normalize_uri uri
743
+ (uri =~ /^https?:/) ? uri : "http://#{uri}"
744
+ end
745
+
746
+ ##
747
+ # Pipelines +requests+ to the HTTP server at +uri+ yielding responses if a
748
+ # block is given. Returns all responses recieved.
749
+ #
750
+ # See
751
+ # Net::HTTP::Pipeline[http://docs.seattlerb.org/net-http-pipeline/Net/HTTP/Pipeline.html]
752
+ # for further details.
753
+ #
754
+ # Only if <tt>net-http-pipeline</tt> was required before
755
+ # <tt>net-http-persistent</tt> #pipeline will be present.
756
+
757
+ def pipeline uri, requests, &block # :yields: responses
758
+ connection_for uri do |connection|
759
+ connection.http.pipeline requests, &block
760
+ end
761
+ end
762
+
763
+ ##
764
+ # Sets this client's SSL private key
765
+
766
+ def private_key= key
767
+ @private_key = key
768
+
769
+ reconnect_ssl
770
+ end
771
+
772
+ # For Net::HTTP parity
773
+ alias key= private_key=
774
+
775
+ ##
776
+ # Sets the proxy server. The +proxy+ may be the URI of the proxy server,
777
+ # the symbol +:ENV+ which will read the proxy from the environment or nil to
778
+ # disable use of a proxy. See #proxy_from_env for details on setting the
779
+ # proxy from the environment.
780
+ #
781
+ # If the proxy URI is set after requests have been made, the next request
782
+ # will shut-down and re-open all connections.
783
+ #
784
+ # The +no_proxy+ query parameter can be used to specify hosts which shouldn't
785
+ # be reached via proxy; if set it should be a comma separated list of
786
+ # hostname suffixes, optionally with +:port+ appended, for example
787
+ # <tt>example.com,some.host:8080</tt>.
788
+
789
+ def proxy= proxy
790
+ @proxy_uri = case proxy
791
+ when :ENV then proxy_from_env
792
+ when URI::HTTP then proxy
793
+ when nil then # ignore
794
+ else raise ArgumentError, 'proxy must be :ENV or a URI::HTTP'
795
+ end
796
+
797
+ @no_proxy.clear
798
+
799
+ if @proxy_uri then
800
+ @proxy_args = [
801
+ @proxy_uri.hostname,
802
+ @proxy_uri.port,
803
+ unescape(@proxy_uri.user),
804
+ unescape(@proxy_uri.password),
805
+ ]
806
+
807
+ @proxy_connection_id = [nil, *@proxy_args].join ':'
808
+
809
+ if @proxy_uri.query then
810
+ @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
811
+ end
812
+ end
813
+
814
+ reconnect
815
+ reconnect_ssl
816
+ end
817
+
818
+ ##
819
+ # Creates a URI for an HTTP proxy server from ENV variables.
820
+ #
821
+ # If +HTTP_PROXY+ is set a proxy will be returned.
822
+ #
823
+ # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the URI is given the
824
+ # indicated user and password unless HTTP_PROXY contains either of these in
825
+ # the URI.
826
+ #
827
+ # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't
828
+ # be reached via proxy; if set it should be a comma separated list of
829
+ # hostname suffixes, optionally with +:port+ appended, for example
830
+ # <tt>example.com,some.host:8080</tt>. When set to <tt>*</tt> no proxy will
831
+ # be returned.
832
+ #
833
+ # For Windows users, lowercase ENV variables are preferred over uppercase ENV
834
+ # variables.
835
+
836
+ def proxy_from_env
837
+ env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
838
+
839
+ return nil if env_proxy.nil? or env_proxy.empty?
840
+
841
+ uri = URI normalize_uri env_proxy
842
+
843
+ env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
844
+
845
+ # '*' is special case for always bypass
846
+ return nil if env_no_proxy == '*'
847
+
848
+ if env_no_proxy then
849
+ uri.query = "no_proxy=#{escape(env_no_proxy)}"
850
+ end
851
+
852
+ unless uri.user or uri.password then
853
+ uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
854
+ uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
855
+ end
856
+
857
+ uri
858
+ end
859
+
860
+ ##
861
+ # Returns true when proxy should by bypassed for host.
862
+
863
+ def proxy_bypass? host, port
864
+ host = host.downcase
865
+ host_port = [host, port].join ':'
866
+
867
+ @no_proxy.each do |name|
868
+ return true if host[-name.length, name.length] == name or
869
+ host_port[-name.length, name.length] == name
870
+ end
871
+
872
+ false
873
+ end
874
+
875
+ ##
876
+ # Forces reconnection of HTTP connections.
877
+
878
+ def reconnect
879
+ @generation += 1
880
+ end
881
+
882
+ ##
883
+ # Forces reconnection of SSL connections.
884
+
885
+ def reconnect_ssl
886
+ @ssl_generation += 1
887
+ end
888
+
889
+ ##
890
+ # Finishes then restarts the Net::HTTP +connection+
891
+
892
+ def reset connection
893
+ http = connection.http
894
+
895
+ finish connection
896
+
897
+ start http
898
+ rescue Errno::ECONNREFUSED
899
+ e = Error.new "connection refused: #{http.address}:#{http.port}"
900
+ e.set_backtrace $@
901
+ raise e
902
+ rescue Errno::EHOSTDOWN
903
+ e = Error.new "host down: #{http.address}:#{http.port}"
904
+ e.set_backtrace $@
905
+ raise e
906
+ end
907
+
908
+ ##
909
+ # Makes a request on +uri+. If +req+ is nil a Net::HTTP::Get is performed
910
+ # against +uri+.
911
+ #
912
+ # If a block is passed #request behaves like Net::HTTP#request (the body of
913
+ # the response will not have been read).
914
+ #
915
+ # +req+ must be a Net::HTTPRequest subclass (see Net::HTTP for a list).
916
+ #
917
+ # If there is an error and the request is idempotent according to RFC 2616
918
+ # it will be retried automatically.
919
+
920
+ def request uri, req = nil, &block
921
+ retried = false
922
+ bad_response = false
923
+
924
+ uri = URI uri
925
+ req = request_setup req || uri
926
+ response = nil
927
+
928
+ connection_for uri do |connection|
929
+ http = connection.http
930
+
931
+ begin
932
+ connection.requests += 1
933
+
934
+ response = http.request req, &block
935
+
936
+ if req.connection_close? or
937
+ (response.http_version <= '1.0' and
938
+ not response.connection_keep_alive?) or
939
+ response.connection_close? then
940
+ finish connection
941
+ end
942
+ rescue Net::HTTPBadResponse => e
943
+ message = error_message connection
944
+
945
+ finish connection
946
+
947
+ raise Error, "too many bad responses #{message}" if
948
+ bad_response or not can_retry? req
949
+
950
+ bad_response = true
951
+ retry
952
+ rescue *RETRIED_EXCEPTIONS => e
953
+ request_failed e, req, connection if
954
+ retried or not can_retry? req
955
+
956
+ reset connection
957
+
958
+ retried = true
959
+ retry
960
+ rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2
961
+ request_failed e, req, connection if retried or not can_retry? req
962
+
963
+ reset connection
964
+
965
+ retried = true
966
+ retry
967
+ rescue Exception => e
968
+ finish connection
969
+
970
+ raise
971
+ ensure
972
+ connection.last_use = Time.now
973
+ end
974
+ end
975
+
976
+ @http_versions["#{uri.hostname}:#{uri.port}"] ||= response.http_version
977
+
978
+ response
979
+ end
980
+
981
+ ##
982
+ # Raises an Error for +exception+ which resulted from attempting the request
983
+ # +req+ on the +connection+.
984
+ #
985
+ # Finishes the +connection+.
986
+
987
+ def request_failed exception, req, connection # :nodoc:
988
+ due_to = "(due to #{exception.message} - #{exception.class})"
989
+ message = "too many connection resets #{due_to} #{error_message connection}"
990
+
991
+ finish connection
992
+
993
+ raise Error, message, exception.backtrace
994
+ end
995
+
996
+ ##
997
+ # Creates a GET request if +req_or_uri+ is a URI and adds headers to the
998
+ # request.
999
+ #
1000
+ # Returns the request.
1001
+
1002
+ def request_setup req_or_uri # :nodoc:
1003
+ req = if URI === req_or_uri then
1004
+ Net::HTTP::Get.new req_or_uri.request_uri
1005
+ else
1006
+ req_or_uri
1007
+ end
1008
+
1009
+ @headers.each do |pair|
1010
+ req.add_field(*pair)
1011
+ end
1012
+
1013
+ @override_headers.each do |name, value|
1014
+ req[name] = value
1015
+ end
1016
+
1017
+ unless req['Connection'] then
1018
+ req.add_field 'Connection', 'keep-alive'
1019
+ req.add_field 'Keep-Alive', @keep_alive
1020
+ end
1021
+
1022
+ req
1023
+ end
1024
+
1025
+ ##
1026
+ # Shuts down all connections
1027
+ #
1028
+ # *NOTE*: Calling shutdown for can be dangerous!
1029
+ #
1030
+ # If any thread is still using a connection it may cause an error! Call
1031
+ # #shutdown when you are completely done making requests!
1032
+
1033
+ def shutdown
1034
+ @pool.available.shutdown do |http|
1035
+ http.finish
1036
+ end
1037
+ end
1038
+
1039
+ ##
1040
+ # Enables SSL on +connection+
1041
+
1042
+ def ssl connection
1043
+ connection.use_ssl = true
1044
+
1045
+ connection.ciphers = @ciphers if @ciphers
1046
+
1047
+ if @min_version
1048
+ if connection.respond_to? :min_version=
1049
+ connection.min_version = @min_version
1050
+ else
1051
+ connection.ssl_version = @min_version
1052
+ end
1053
+ end
1054
+
1055
+ connection.ssl_timeout = @ssl_timeout if @ssl_timeout
1056
+
1057
+ connection.verify_depth = @verify_depth
1058
+ connection.verify_mode = @verify_mode
1059
+
1060
+ if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and
1061
+ not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
1062
+ warn <<-WARNING
1063
+ !!!SECURITY WARNING!!!
1064
+
1065
+ The SSL HTTP connection to:
1066
+
1067
+ #{connection.address}:#{connection.port}
1068
+
1069
+ !!!MAY NOT BE VERIFIED!!!
1070
+
1071
+ On your platform your OpenSSL implementation is broken.
1072
+
1073
+ There is no difference between the values of VERIFY_NONE and VERIFY_PEER.
1074
+
1075
+ This means that attempting to verify the security of SSL connections may not
1076
+ work. This exposes you to man-in-the-middle exploits, snooping on the
1077
+ contents of your connection and other dangers to the security of your data.
1078
+
1079
+ To disable this warning define the following constant at top-level in your
1080
+ application:
1081
+
1082
+ I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
1083
+
1084
+ WARNING
1085
+ end
1086
+
1087
+ connection.ca_file = @ca_file if @ca_file
1088
+ connection.ca_path = @ca_path if @ca_path
1089
+
1090
+ if @ca_file or @ca_path then
1091
+ connection.verify_callback = @verify_callback if @verify_callback
1092
+ end
1093
+
1094
+ if @certificate and @private_key then
1095
+ connection.cert = @certificate
1096
+ connection.key = @private_key
1097
+ end
1098
+
1099
+ connection.cert_store = if @cert_store then
1100
+ @cert_store
1101
+ else
1102
+ store = OpenSSL::X509::Store.new
1103
+ store.set_default_paths
1104
+ store
1105
+ end
1106
+ end
1107
+
1108
+ ##
1109
+ # Minimum SSL version to use
1110
+
1111
+ def min_version= min_version
1112
+ @min_version = min_version
1113
+
1114
+ reconnect_ssl
1115
+ end
1116
+
1117
+ ##
1118
+ # SSL session lifetime
1119
+
1120
+ def ssl_timeout= ssl_timeout
1121
+ @ssl_timeout = ssl_timeout
1122
+
1123
+ reconnect_ssl
1124
+ end
1125
+
1126
+ ##
1127
+ # Sets the depth of SSL certificate verification
1128
+
1129
+ def verify_depth= verify_depth
1130
+ @verify_depth = verify_depth
1131
+
1132
+ reconnect_ssl
1133
+ end
1134
+
1135
+ ##
1136
+ # Sets the HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER.
1137
+ #
1138
+ # Setting this to VERIFY_NONE is a VERY BAD IDEA and should NEVER be used.
1139
+ # Securely transfer the correct certificate and update the default
1140
+ # certificate store or set the ca file instead.
1141
+
1142
+ def verify_mode= verify_mode
1143
+ @verify_mode = verify_mode
1144
+
1145
+ reconnect_ssl
1146
+ end
1147
+
1148
+ ##
1149
+ # SSL verification callback.
1150
+
1151
+ def verify_callback= callback
1152
+ @verify_callback = callback
1153
+
1154
+ reconnect_ssl
1155
+ end
1156
+
1157
+ end
1158
+ end
1159
+
1160
+ require_relative 'persistent/connection'
1161
+ require_relative 'persistent/pool'