socketry 0.5.0 → 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,31 +0,0 @@
1
- ## 0.5.0 (2016-11-26)
2
-
3
- * Require Ruby 2.2.6+ (earlier 2.2 versions had async I/O bugs)
4
- * Extract Socketry::SSL::Socket#accept into its own method
5
- * Simplify Socketry::SSL::Socket#from_socket API
6
- * Raise Socketry::SSL::CertificateVerifyError for certificate verification errors
7
- * Specs and bugfixes for Socketry::SSL::Server
8
- * Rename Socketry::UDP::Datagram accessors to `remote_host`, `remote_addr`, and `remote_port`
9
- * Update to RuboCop 0.45.0
10
-
11
- ## 0.4.0 (2016-11-25)
12
-
13
- * Specs and bugfixes for SSL sockets
14
- * Specs and bugfixes for UDP sockets
15
- * Add Socketry::UDP::Datagram class
16
- * Add Socketry::AddressInUseError exception
17
-
18
- ## 0.3.0 (2016-09-24)
19
-
20
- * Implement Socketry::TCP::Socket#read and #write
21
- * Use StandardError as the base class for Socketry::Error
22
- * Add Socketry::ConnectionRefusedError
23
- * Parameterize SSL contexts
24
-
25
- ## 0.2.0 (2016-09-12)
26
-
27
- * Rename Socketry::TCP::Socket#connected? -> #closed?
28
-
29
- ## 0.1.0 (2016-09-11)
30
-
31
- * 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,108 +0,0 @@
1
- # Socketry [![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]
2
-
3
- [gem-image]: https://badge.fury.io/rb/socketry.svg
4
- [gem-link]: https://rubygems.org/gems/socketry
5
- [build-image]: https://secure.travis-ci.org/socketry/socketry.svg?branch=master
6
- [build-link]: https://travis-ci.org/socketry/socketry
7
- [codeclimate-image]: https://codeclimate.com/github/socketry/socketry.svg?branch=master
8
- [codeclimate-link]: https://codeclimate.com/github/socketry/socketry
9
- [coverage-image]: https://coveralls.io/repos/github/socketry/socketry/badge.svg?branch=master
10
- [coverage-link]: https://coveralls.io/github/socketry/socketry?branch=master
11
- [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
12
- [license-link]: https://github.com/socketry/socketry/blob/master/LICENSE.txt
13
-
14
- High-level Ruby socket library with support for TCP, UDP, and SSL sockets.
15
-
16
- Implements thread-safe timeouts using asynchronous I/O and high-precision monotonic timers.
17
-
18
- ## Motivation
19
-
20
- By default, Ruby sockets do not provide a built-in timeout mechanism. The only
21
- timeout mechanism provided by the language leverages [timeout.rb], which uses
22
- [unsafe multithreaded behaviors] to implement timeouts.
23
-
24
- While Socketry provides a synchronous, blocking API similar to Ruby's own
25
- `TCPSocket` and `UDPSocket` classes, behind the scenes it uses non-blocking I/O
26
- to implement thread-safe timeouts.
27
-
28
- [timeout.rb]: http://ruby-doc.org/stdlib-2.3.1/libdoc/timeout/rdoc/Timeout.html
29
- [unsafe multithreaded behaviors]: http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html
30
-
31
- ## Installation
32
-
33
- Add this line to your application's Gemfile:
34
-
35
- ```ruby
36
- gem "socketry"
37
- ```
38
-
39
- And then execute:
40
-
41
- $ bundle
42
-
43
- Or install it yourself as:
44
-
45
- $ gem install socketry
46
-
47
- ## Basic Usage
48
-
49
- Below is a basic example of how to use Socketry to make an HTTPS request:
50
-
51
- ```ruby
52
- require "socketry"
53
-
54
- socket = Socketry::SSL::Socket.connect("github.com", 443)
55
- socket.writepartial("GET / HTTP/1.0\r\nHost: github.com\r\n\r\n")
56
- p socket.readpartial(1024)
57
- ```
58
-
59
- [TCP], [SSL], and [UDP] servers and sockets also available.
60
-
61
- [TCP]: https://github.com/socketry/socketry/wiki/TCP
62
- [SSL]: https://github.com/socketry/socketry/wiki/SSL
63
- [UDP]: https://github.com/socketry/socketry/wiki/UDP
64
-
65
- ## Documentation
66
-
67
- [Please see the Socketry wiki](https://github.com/socketry/socketry/wiki)
68
- for more detailed documentation and usage notes.
69
-
70
- [YARD API documentation](http://www.rubydoc.info/gems/socketry/)
71
- is also available.
72
-
73
- ## Supported Ruby Versions
74
-
75
- This library aims to support and is [tested against][travis] the following Ruby
76
- versions:
77
-
78
- * Ruby 2.2.6+
79
- * Ruby 2.3.x
80
- * JRuby 9.1.6.0+
81
-
82
- If something doesn't work on one of these versions, it's a bug.
83
-
84
- This library may inadvertently work (or seem to work) on other Ruby versions,
85
- however support will only be provided for the versions listed above.
86
-
87
- If you would like this library to support another Ruby version or
88
- implementation, you may volunteer to be a maintainer. Being a maintainer
89
- entails making sure all tests run and pass on that implementation. When
90
- something breaks on your implementation, you will be responsible for providing
91
- patches in a timely fashion. If critical issues for a particular implementation
92
- exist at the time of a major release, support for that Ruby version may be
93
- dropped.
94
-
95
- [travis]: http://travis-ci.org/socketry/socketry
96
-
97
- ## Contributing
98
-
99
- * Fork this repository on github
100
- * Make your changes and send us a pull request
101
- * If we like them we'll merge them
102
- * If we've accepted a patch, feel free to ask for commit access
103
-
104
- ## License
105
-
106
- Copyright (c) 2016 Tony Arcieri. Distributed under the MIT License. See
107
- [LICENSE.txt](https://github.com/socketry/socketry/blob/master/LICENSE.txt)
108
- 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,188 +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
- @ssl_socket.close rescue nil
167
- super
168
- end
169
-
170
- private
171
-
172
- # Perform a non-blocking I/O operation
173
- def perform
174
- ensure_connected
175
- yield
176
- # Some buggy Rubies continue to raise this exception
177
- rescue IO::WaitWritable
178
- :wait_writable
179
- # Due to SSL, we may need to write to complete a read (e.g. handshaking, renegotiation)
180
- rescue IO::WaitReadable
181
- :wait_readable
182
- rescue => ex
183
- # TODO: more specific exceptions
184
- raise Socketry::Error, ex.message, ex.backtrace
185
- end
186
- end
187
- end
188
- end
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Socketry
4
- # Transmission Control Protocol
5
- module TCP
6
- # Transmission Control Protocol servers: Accept connections from the network
7
- class Server
8
- include Socketry::Timeout
9
- alias uptime lifetime
10
-
11
- attr_reader :read_timeout, :write_timeout, :resolver, :socket_class
12
-
13
- # Create a new TCP server, yielding the server socket and auto-closing it
14
- def self.open(hostname_or_port, port = nil, **args)
15
- server = new(hostname_or_port, port, **args)
16
- result = yield server
17
- server.close
18
- result
19
- end
20
-
21
- # Create a new TCP server
22
- #
23
- # @return [Socketry::TCP::Server]
24
- def initialize(
25
- hostname_or_port,
26
- port = nil,
27
- read_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:read],
28
- write_timeout: Socketry::Timeout::DEFAULT_TIMEOUTS[:write],
29
- timer: Socketry::Timeout::DEFAULT_TIMER.new,
30
- resolver: Socketry::Resolver::DEFAULT_RESOLVER,
31
- server_class: ::TCPServer,
32
- socket_class: ::TCPSocket
33
- )
34
- @read_timeout = read_timeout
35
- @write_timeout = write_timeout
36
- @resolver = resolver
37
- @socket_class = socket_class
38
-
39
- if port
40
- @server = server_class.new(@resolver.resolve(hostname_or_port).to_s, port)
41
- else
42
- @server = server_class.new(hostname_or_port)
43
- end
44
-
45
- start_timer(timer)
46
- rescue Errno::EADDRINUSE => ex
47
- raise AddressInUseError, ex.message, ex.backtrace
48
- end
49
-
50
- # Accept a connection to the server
51
- #
52
- # @param timeout [Numeric, NilClass] seconds to wait before aborting the accept
53
- # @return [Socketry::TCP::Socket]
54
- def accept(timeout: nil)
55
- set_timeout(timeout)
56
-
57
- begin
58
- # Note: `exception: false` for TCPServer#accept_nonblock is only supported in Ruby 2.3+
59
- ruby_socket = @server.accept_nonblock
60
- rescue IO::WaitReadable, Errno::EAGAIN
61
- # Ruby 2.2 has trouble using io/wait here
62
- retry if IO.select([@server], nil, nil, time_remaining(timeout))
63
- raise Socketry::TimeoutError, "no connection received after #{timeout} seconds"
64
- end
65
-
66
- Socketry::TCP::Socket.new(
67
- read_timeout: @read_timeout,
68
- write_timeout: @write_timeout,
69
- resolver: @resolver,
70
- socket_class: @socket_class
71
- ).from_socket(ruby_socket)
72
- ensure
73
- clear_timeout(timeout)
74
- end
75
-
76
- # Close the server
77
- def close
78
- return false unless @server
79
- @server.close rescue nil
80
- @server = nil
81
- true
82
- end
83
- end
84
- end
85
- end