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