socketry 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGES.md DELETED
@@ -1,35 +0,0 @@
1
- ## 0.5.1 (2016-11-26)
2
-
3
- * Fix regression in Socketry::SSL::Socket#close
4
-
5
- ## 0.5.0 (2016-11-26)
6
-
7
- * Require Ruby 2.2.6+ (earlier 2.2 versions had async I/O bugs)
8
- * Extract Socketry::SSL::Socket#accept into its own method
9
- * Simplify Socketry::SSL::Socket#from_socket API
10
- * Raise Socketry::SSL::CertificateVerifyError for certificate verification errors
11
- * Specs and bugfixes for Socketry::SSL::Server
12
- * Rename Socketry::UDP::Datagram accessors to `remote_host`, `remote_addr`, and `remote_port`
13
- * Update to RuboCop 0.45.0
14
-
15
- ## 0.4.0 (2016-11-25)
16
-
17
- * Specs and bugfixes for SSL sockets
18
- * Specs and bugfixes for UDP sockets
19
- * Add Socketry::UDP::Datagram class
20
- * Add Socketry::AddressInUseError exception
21
-
22
- ## 0.3.0 (2016-09-24)
23
-
24
- * Implement Socketry::TCP::Socket#read and #write
25
- * Use StandardError as the base class for Socketry::Error
26
- * Add Socketry::ConnectionRefusedError
27
- * Parameterize SSL contexts
28
-
29
- ## 0.2.0 (2016-09-12)
30
-
31
- * Rename Socketry::TCP::Socket#connected? -> #closed?
32
-
33
- ## 0.1.0 (2016-09-11)
34
-
35
- * Initial release
data/Gemfile DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
7
- group :development do
8
- gem "guard-rspec", require: false
9
- gem "pry", require: false
10
- end
11
-
12
- group :test do
13
- gem "rspec", "~> 3", require: false
14
- gem "rubocop", "0.45.0", require: false
15
- gem "coveralls", require: false
16
- end
17
-
18
- group :development, :test do
19
- gem "rake"
20
- end
data/Guardfile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- directories %w(lib spec)
4
- clearing :on
5
-
6
- guard :rspec, cmd: "bundle exec rspec" do
7
- watch(%r{^spec/.+_spec\.rb$})
8
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
9
- watch("spec/spec_helper.rb") { "spec" }
10
- end
data/LICENSE.txt DELETED
@@ -1,22 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2016 Tony Arcieri
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md DELETED
@@ -1,110 +0,0 @@
1
- # ![Socketry](https://raw.github.com/socketry/socketry/master/logo.png)
2
-
3
- [![Gem Version][gem-image]][gem-link] [![Build Status][build-image]][build-link] [![Code Climate][codeclimate-image]][codeclimate-link] [![Coverage Status][coverage-image]][coverage-link] [![MIT licensed][license-image]][license-link]
4
-
5
- [gem-image]: https://badge.fury.io/rb/socketry.svg
6
- [gem-link]: https://rubygems.org/gems/socketry
7
- [build-image]: https://secure.travis-ci.org/socketry/socketry.svg?branch=master
8
- [build-link]: https://travis-ci.org/socketry/socketry
9
- [codeclimate-image]: https://codeclimate.com/github/socketry/socketry.svg?branch=master
10
- [codeclimate-link]: https://codeclimate.com/github/socketry/socketry
11
- [coverage-image]: https://coveralls.io/repos/github/socketry/socketry/badge.svg?branch=master
12
- [coverage-link]: https://coveralls.io/github/socketry/socketry?branch=master
13
- [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
14
- [license-link]: https://github.com/socketry/socketry/blob/master/LICENSE.txt
15
-
16
- High-level Ruby socket library with support for TCP, UDP, and SSL sockets.
17
-
18
- Implements thread-safe timeouts using asynchronous I/O and high-precision monotonic timers.
19
-
20
- ## Motivation
21
-
22
- By default, Ruby sockets do not provide a built-in timeout mechanism. The only
23
- timeout mechanism provided by the language leverages [timeout.rb], which uses
24
- [unsafe multithreaded behaviors] to implement timeouts.
25
-
26
- While Socketry provides a synchronous, blocking API similar to Ruby's own
27
- `TCPSocket` and `UDPSocket` classes, behind the scenes it uses non-blocking I/O
28
- to implement thread-safe timeouts.
29
-
30
- [timeout.rb]: http://ruby-doc.org/stdlib-2.3.1/libdoc/timeout/rdoc/Timeout.html
31
- [unsafe multithreaded behaviors]: http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html
32
-
33
- ## Installation
34
-
35
- Add this line to your application's Gemfile:
36
-
37
- ```ruby
38
- gem "socketry"
39
- ```
40
-
41
- And then execute:
42
-
43
- $ bundle
44
-
45
- Or install it yourself as:
46
-
47
- $ gem install socketry
48
-
49
- ## Basic Usage
50
-
51
- Below is a basic example of how to use Socketry to make an HTTPS request:
52
-
53
- ```ruby
54
- require "socketry"
55
-
56
- socket = Socketry::SSL::Socket.connect("github.com", 443)
57
- socket.writepartial("GET / HTTP/1.0\r\nHost: github.com\r\n\r\n")
58
- p socket.readpartial(1024)
59
- ```
60
-
61
- [TCP], [SSL], and [UDP] servers and sockets also available.
62
-
63
- [TCP]: https://github.com/socketry/socketry/wiki/TCP
64
- [SSL]: https://github.com/socketry/socketry/wiki/SSL
65
- [UDP]: https://github.com/socketry/socketry/wiki/UDP
66
-
67
- ## Documentation
68
-
69
- [Please see the Socketry wiki](https://github.com/socketry/socketry/wiki)
70
- for more detailed documentation and usage notes.
71
-
72
- [YARD API documentation](http://www.rubydoc.info/gems/socketry/)
73
- is also available.
74
-
75
- ## Supported Ruby Versions
76
-
77
- This library aims to support and is [tested against][travis] the following Ruby
78
- versions:
79
-
80
- * Ruby 2.2.6+
81
- * Ruby 2.3.0+
82
- * JRuby 9.1.6.0+
83
-
84
- If something doesn't work on one of these versions, it's a bug.
85
-
86
- This library may inadvertently work (or seem to work) on other Ruby versions,
87
- however support will only be provided for the versions listed above.
88
-
89
- If you would like this library to support another Ruby version or
90
- implementation, you may volunteer to be a maintainer. Being a maintainer
91
- entails making sure all tests run and pass on that implementation. When
92
- something breaks on your implementation, you will be responsible for providing
93
- patches in a timely fashion. If critical issues for a particular implementation
94
- exist at the time of a major release, support for that Ruby version may be
95
- dropped.
96
-
97
- [travis]: http://travis-ci.org/socketry/socketry
98
-
99
- ## Contributing
100
-
101
- * Fork this repository on github
102
- * Make your changes and send us a pull request
103
- * If we like them we'll merge them
104
- * If we've accepted a patch, feel free to ask for commit access
105
-
106
- ## License
107
-
108
- Copyright (c) 2016 Tony Arcieri. Distributed under the MIT License. See
109
- [LICENSE.txt](https://github.com/socketry/socketry/blob/master/LICENSE.txt)
110
- for further details.
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
- require "bundler/gem_tasks"
3
-
4
- require "rspec/core/rake_task"
5
- RSpec::Core::RakeTask.new
6
-
7
- require "rubocop/rake_task"
8
- RuboCop::RakeTask.new
9
-
10
- task default: %w(spec rubocop)
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Socketry
4
- # Generic catch all for all Socketry errors
5
- Error = Class.new(StandardError)
6
-
7
- # Failed to connect to a remote host
8
- ConnectionRefusedError = Class.new(Socketry::Error)
9
-
10
- # Invalid address
11
- AddressError = Class.new(Socketry::Error)
12
-
13
- # Address is already in use
14
- AddressInUseError = Class.new(Socketry::Error)
15
-
16
- # Timeouts performing an I/O operation
17
- TimeoutError = Class.new(Socketry::Error)
18
-
19
- # Cannot perform operation in current state
20
- StateError = Class.new(Socketry::Error)
21
-
22
- # Internal consistency error within the library
23
- InternalError = Class.new(Socketry::Error)
24
-
25
- module Resolver
26
- # DNS resolution errors
27
- Error = Class.new(Socketry::AddressError)
28
- end
29
-
30
- module SSL
31
- # Errors related to SSL
32
- Error = Class.new(Socketry::Error)
33
-
34
- # Certificate could not be verified
35
- CertificateVerifyError = Class.new(Socketry::SSL::Error)
36
-
37
- # Hostname verification error
38
- HostnameError = Class.new(CertificateVerifyError)
39
- end
40
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "resolv"
4
-
5
- module Socketry
6
- module Resolver
7
- # Pure Ruby DNS resolver provided by the standard library
8
- class Resolv
9
- # Resolve a hostname by creating and discaring a Socketry::Resolver::Resolv
10
- # instance. For better performance, create and reuse an instance.
11
- def self.resolve(hostname, **options)
12
- resolver = new
13
- begin
14
- resolver.resolve(hostname, **options)
15
- ensure
16
- resolver.close
17
- end
18
- end
19
-
20
- # Create a new instance of Socketry::Resolver::Resolv.
21
- #
22
- # Arguments are passed directly to Resolv::DNS. See the Ruby documentation
23
- # for more information:
24
- #
25
- # https://ruby-doc.org/stdlib-2.3.1/libdoc/resolv/rdoc/Resolv/DNS.html
26
- #
27
- def initialize(*args)
28
- @hosts = ::Resolv::Hosts.new
29
- @resolver = ::Resolv::DNS.new(*args)
30
- end
31
-
32
- # Resolve a domain name using IPSocket.getaddress. This uses getaddrinfo(3)
33
- # on POSIX operating systems.
34
- #
35
- # @param hostname [String] name of the host whose IP address we'd like to obtain
36
- # @return [IPAddr] resolved IP address
37
- # @raise [Socketry::Resolver::Error] an error occurred resolving the domain name
38
- # @raise [Socketry::TimeoutError] a timeout occured before the name could be resolved
39
- # @raise [Socketry::AddressError] the name was resolved to an unsupported address
40
- def resolve(hostname, timeout: nil)
41
- raise TypeError, "expected String, got #{hostname.class}" unless hostname.is_a?(String)
42
- return IPAddr.new(@hosts.getaddress(hostname).sub(/%.*$/, ""))
43
- rescue ::Resolv::ResolvError
44
- case timeout
45
- when Integer, Float
46
- @resolver.timeouts = timeout
47
- when NilClass
48
- nil # no timeout
49
- else raise TypeError, "expected Numeric, got #{timeout.class}"
50
- end
51
-
52
- begin
53
- IPAddr.new(@resolver.getaddress(hostname).to_s)
54
- rescue ::Resolv::ResolvError => ex
55
- raise Socketry::Resolver::Error, ex.message, ex.backtrace
56
- rescue ::Resolv::ResolvTimeout => ex
57
- raise Socketry::TimeoutError, ex.message, ex.backtrace
58
- end
59
- end
60
-
61
- # Close the resolver
62
- def close
63
- @resolver.close
64
- end
65
- end
66
- end
67
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "timeout"
4
-
5
- module Socketry
6
- module Resolver
7
- # System DNS resolver backed by the POSIX getaddrinfo(3) function
8
- module System
9
- module_function
10
-
11
- # Resolve a domain name using IPSocket.getaddress. This uses getaddrinfo(3)
12
- # on POSIX operating systems.
13
- #
14
- # @param hostname [String] name of the host whose IP address we'd like to obtain
15
- # @return [IPAddr] resolved IP address
16
- # @raise [Socketry::Resolver::Error] an error occurred resolving the domain name
17
- # @raise [Socketry::TimeoutError] a timeout occured before the name could be resolved
18
- # @raise [Socketry::AddressError] the name was resolved to an unsupported address
19
- def resolve(hostname, timeout: nil)
20
- raise TypeError, "expected String, got #{hostname.class}" unless hostname.is_a?(String)
21
-
22
- begin
23
- case timeout
24
- when Integer, Float
25
- # NOTE: ::Timeout is not thread safe. For thread safety, use Socketry::Resolver::Resolv
26
- result = ::Timeout.timeout(timeout) { IPSocket.getaddress(hostname) }
27
- when NilClass
28
- result = IPSocket.getaddress(hostname)
29
- else raise TypeError, "expected Numeric, got #{timeout.class}"
30
- end
31
- rescue ::SocketError => ex
32
- raise Socketry::Resolver::Error, ex.message, ex.backtrace
33
- rescue ::Timeout::Error => ex
34
- raise Socketry::TimeoutError, ex.message, ex.backtrace
35
- end
36
-
37
- begin
38
- IPAddr.new(result)
39
- rescue IPAddr::InvalidAddressError => ex
40
- raise Socketry::AddressError, ex.message, ex.backtrace
41
- end
42
- end
43
- end
44
-
45
- # Use Socketry::Resolver::System as the default resolver
46
- DEFAULT_RESOLVER = System
47
- end
48
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Socketry
4
- # Secure Sockets Layer (a.k.a. Transport Layer Security, or TLS)
5
- module SSL
6
- # SSL Server
7
- class Server < Socketry::TCP::Server
8
- # Create a new SSL server
9
- #
10
- # @return [Socketry::SSL::Server]
11
- def initialize(
12
- hostname_or_port,
13
- port = nil,
14
- ssl_socket_class: OpenSSL::SSL::SSLSocket,
15
- ssl_params: nil,
16
- **args
17
- )
18
- raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)
19
-
20
- @ssl_socket_class = ssl_socket_class
21
- @ssl_params = ssl_params
22
-
23
- super(hostname_or_port, port, **args)
24
- end
25
-
26
- # Accept a connection to the server
27
- #
28
- # Note that this method also performs an SSL handshake and will therefore
29
- # block other sockets which are ready to be accepted.
30
- #
31
- # Multithreaded servers should invoke this method after spawning a thread
32
- # to ensure a slow/malicious connection can't cause a denial-of-service
33
- # attack against the server.
34
- #
35
- # @param timeout [Numeric, NilClass] (default nil, unlimited) seconds to wait before aborting the accept
36
- #
37
- # @return [Socketry::SSL::Socket]
38
- def accept(timeout: nil, **args)
39
- tcp_socket = super(timeout: timeout, **args)
40
-
41
- ssl_socket = Socketry::SSL::Socket.new(
42
- read_timeout: @read_timeout,
43
- write_timeout: @write_timeout,
44
- resolver: @resolver,
45
- ssl_socket_class: @ssl_socket_class,
46
- ssl_params: @ssl_params
47
- )
48
-
49
- ssl_socket.accept(tcp_socket, timeout: timeout)
50
- end
51
- end
52
- end
53
- end
@@ -1,190 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Socketry
4
- # Secure Sockets Layer (a.k.a. Transport Layer Security, or TLS)
5
- module SSL
6
- # SSL Sockets
7
- class Socket < Socketry::TCP::Socket
8
- # Create an unconnected Socketry::SSL::Socket
9
- #
10
- # @param read_timeout [Numeric] Seconds to wait before an uncompleted read errors
11
- # @param write_timeout [Numeric] Seconds to wait before an uncompleted write errors
12
- # @param timer [Object] A timekeeping object to use for measuring timeouts
13
- # @param resolver [Object] A resolver object to use for resolving DNS names
14
- # @param socket_class [Object] Underlying socket class which implements I/O ops
15
- # @param ssl_socket_class [Object] Class which provides the underlying SSL implementation
16
- # @param ssl_context [OpenSSL::SSL::SSLContext] SSL configuration object
17
- # @param ssL_params [Hash] Parameter hash to set on the given SSL context
18
- #
19
- # @return [Socketry::SSL::Socket]
20
- def initialize(
21
- ssl_socket_class: OpenSSL::SSL::SSLSocket,
22
- ssl_context: OpenSSL::SSL::SSLContext.new,
23
- ssl_params: nil,
24
- **args
25
- )
26
- raise TypeError, "invalid SSL context (#{ssl_context.class})" unless ssl_context.is_a?(OpenSSL::SSL::SSLContext)
27
- raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)
28
-
29
- @ssl_socket_class = ssl_socket_class
30
-
31
- @ssl_context = ssl_context
32
- @ssl_context.set_params(ssl_params) if ssl_params && !ssl_params.empty?
33
-
34
- @ssl_socket = nil
35
-
36
- super(**args)
37
- end
38
-
39
- # Make an SSL connection to a remote host
40
- #
41
- # @param remote_addr [String] DNS name or IP address of the host to connect to
42
- # @param remote_port [Fixnum] TCP port to connect to
43
- # @param local_addr [String] DNS name or IP address to bind to locally
44
- # @param local_port [Fixnum] Local TCP port to bind to
45
- # @param timeout [Numeric] Number of seconds to wait before aborting connect
46
- # @param enable_sni [true, false] (default: true) Enables Server Name Indication (SNI)
47
- # @param verify_hostname [true, false] (default: true) Ensure server's hostname matches cert
48
- #
49
- # @raise [Socketry::AddressError] an invalid address was given
50
- # @raise [Socketry::TimeoutError] connect operation timed out
51
- # @raise [Socketry::SSL::Error] an error occurred negotiating an SSL connection
52
- # @return [self]
53
- def connect(
54
- remote_addr,
55
- remote_port,
56
- local_addr: nil,
57
- local_port: nil,
58
- timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:connect],
59
- enable_sni: true,
60
- verify_hostname: true
61
- )
62
- super(remote_addr, remote_port, local_addr: local_addr, local_port: local_port, timeout: timeout)
63
-
64
- @ssl_socket = @ssl_socket_class.new(@socket, @ssl_context)
65
- @ssl_socket.hostname = remote_addr if enable_sni
66
- @ssl_socket.sync_close = true
67
-
68
- begin
69
- @ssl_socket.connect_nonblock
70
- rescue IO::WaitReadable
71
- retry if @socket.wait_readable(timeout)
72
- raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out"
73
- rescue IO::WaitWritable
74
- retry if @socket.wait_writable(timeout)
75
- raise Socketry::TimeoutError, "connection to #{remote_addr}:#{remote_port} timed out"
76
- rescue OpenSSL::SSL::SSLError => ex
77
- raise Socketry::SSL::CertificateVerifyError, ex.message if ex.message.include?("certificate verify failed")
78
- raise Socketry::SSL::Error, ex.message, ex.backtrace
79
- end
80
-
81
- begin
82
- @ssl_socket.post_connection_check(remote_addr) if verify_hostname
83
- rescue OpenSSL::SSL::SSLError => ex
84
- raise Socketry::SSL::HostnameError, ex.message, ex.backtrace
85
- end
86
-
87
- self
88
- rescue => ex
89
- @socket.close rescue nil
90
- @socket = nil
91
- @ssl_socket.close rescue nil
92
- @ssl_socket = nil
93
- raise ex
94
- end
95
-
96
- # Accept an SSL connection from a Socketry or Ruby socket
97
- #
98
- # @param tcp_socket [TCPSocket, Socketry::TCP::Socket] raw TCP socket to begin SSL handshake with
99
- # @param timeout [Numeric, NilClass] (default nil, unlimited) seconds to wait before aborting the accept
100
- #
101
- # @return [self]
102
- def accept(tcp_socket, timeout: nil)
103
- tcp_socket = IO.try_convert(tcp_socket) || raise(TypeError, "couldn't convert #{tcp_socket.class} to IO")
104
- ssl_socket = @ssl_socket_class.new(tcp_socket, @ssl_context)
105
-
106
- begin
107
- ssl_socket.accept_nonblock
108
- rescue IO::WaitReadable
109
- retry if IO.select([tcp_socket], nil, nil, timeout)
110
- raise Socketry::TimeoutError, "failed to complete handshake after #{timeout} seconds"
111
- rescue IO::WaitWritable
112
- retry if IO.select(nil, [tcp_socket], nil, timeout)
113
- raise Socketry::TimeoutError, "failed to complete handshake after #{timeout} seconds"
114
- end
115
-
116
- from_socket(ssl_socket)
117
- end
118
-
119
- # Wrap a Ruby OpenSSL::SSL::SSLSocket (or other low-level SSL socket)
120
- #
121
- # @param ssl_socket [OpenSSL::SSL::SSLSocket] SSL socket class associated with this socket
122
- #
123
- # @return [self]
124
- def from_socket(ssl_socket)
125
- raise TypeError, "expected #{@ssl_socket_class}, got #{ssl_socket.class}" unless ssl_socket.is_a?(@ssl_socket_class)
126
- raise StateError, "already connected" if @socket
127
-
128
- @socket = ssl_socket.to_io
129
- @ssl_socket = ssl_socket
130
- @ssl_socket.sync_close = true
131
-
132
- self
133
- end
134
-
135
- # Perform a non-blocking read operation
136
- #
137
- # @param size [Fixnum] number of bytes to attempt to read
138
- # @param outbuf [String, NilClass] an optional buffer into which data should be read
139
- #
140
- # @raise [Socketry::Error] an I/O operation failed
141
- # @return [String, :wait_readable] data read, or :wait_readable if operation would block
142
- def read_nonblock(size, outbuf: nil)
143
- case outbuf
144
- when String
145
- perform { @ssl_socket.read_nonblock(size, outbuf, exception: false) }
146
- when NilClass
147
- perform { @ssl_socket.read_nonblock(size, exception: false) }
148
- else raise TypeError, "unexpected outbuf class: #{outbuf.class}"
149
- end
150
- end
151
-
152
- # Perform a non-blocking write operation
153
- #
154
- # @param data [String] number of bytes to attempt to read
155
- #
156
- # @raise [Socketry::Error] an I/O operation failed
157
- # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
158
- def write_nonblock(data)
159
- perform { @ssl_socket.write_nonblock(data, exception: false) }
160
- end
161
-
162
- # Close the socket
163
- #
164
- # @return [true, false] true if the socket was open, false if closed
165
- def close
166
- return false unless super
167
- @ssl_socket.close
168
- @ssl_socket = nil
169
- true
170
- end
171
-
172
- private
173
-
174
- # Perform a non-blocking I/O operation
175
- def perform
176
- ensure_connected
177
- yield
178
- # Some buggy Rubies continue to raise this exception
179
- rescue IO::WaitWritable
180
- :wait_writable
181
- # Due to SSL, we may need to write to complete a read (e.g. handshaking, renegotiation)
182
- rescue IO::WaitReadable
183
- :wait_readable
184
- rescue => ex
185
- # TODO: more specific exceptions
186
- raise Socketry::Error, ex.message, ex.backtrace
187
- end
188
- end
189
- end
190
- end