socketry 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3035f86108ed663a3bae8c6ef3d50b09925e9628
4
- data.tar.gz: f21df11ab03cbddfe484f6f73ec2c08e501d7d88
3
+ metadata.gz: c144331b5b09954959008e255335d5ac43c70be3
4
+ data.tar.gz: 704cb38c89f0cc0779aaeba5859877f189f5664c
5
5
  SHA512:
6
- metadata.gz: a659f5c5b1be2398364efe1a0a088476d1ec85594c8986c88836baea15fe235d99d58e29b8e0a765de89691a843f35658757dab17586f490bb21adf7be80997d
7
- data.tar.gz: 4e96517be39871436dcd8afe7bf22752a58f906fc1da5aef8e0edf3189987fe11905fb7cb6cd2f01332083e1146e4312280c41d51f9c4152bce24aa10ff6a6ac
6
+ metadata.gz: f49d7c5ee328c0c6b7f6366aca43d147a56045b1b1f3dca05219caef2216e791454585e512a7195f61dc1fb767230a2dde82f5130e1a174474d26b6ad0fbbabb
7
+ data.tar.gz: 7147ec4e25e1d4af36d27452be851adbec957ce65747f95ce873e91936ec962b3576d62b0f171304079dad998030289874a55267682c7842debb7fe7d7ac0413
@@ -14,6 +14,9 @@ Style/AccessorMethodName:
14
14
  Style/ConditionalAssignment:
15
15
  Enabled: false
16
16
 
17
+ Style/NumericPredicate:
18
+ Enabled: false
19
+
17
20
  Style/RescueModifier:
18
21
  Enabled: false
19
22
 
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.3.0 (2016-09-24)
2
+
3
+ * Implement Socketry::TCP::Socket#read and #write
4
+ * Use StandardError as the base class for Socketry::Error
5
+ * Add Socketry::ConnectionRefusedError
6
+ * Parameterize SSL contexts
7
+
1
8
  ## 0.2.0 (2016-09-12)
2
9
 
3
10
  * Rename Socketry::TCP::Socket#connected? -> #closed?
data/README.md CHANGED
@@ -64,11 +64,11 @@ socket.writepartial("GET / HTTP/1.0\r\nHost: github.com\r\n\r\n")
64
64
  p socket.readpartial(1024)
65
65
  ```
66
66
 
67
- [TCP], [UDP], and [SSL] servers and sockets also available.
67
+ [TCP], [SSL], and [UDP] servers and sockets also available.
68
68
 
69
69
  [TCP]: https://github.com/socketry/socketry/wiki/TCP
70
- [UDP]: https://github.com/socketry/socketry/wiki/UDP
71
70
  [SSL]: https://github.com/socketry/socketry/wiki/SSL
71
+ [UDP]: https://github.com/socketry/socketry/wiki/UDP
72
72
 
73
73
  ## Documentation
74
74
 
@@ -2,7 +2,10 @@
2
2
 
3
3
  module Socketry
4
4
  # Generic catch all for all Socketry errors
5
- Error = Class.new(::IOError)
5
+ Error = Class.new(StandardError)
6
+
7
+ # Failed to connect to a remote host
8
+ ConnectionRefusedError = Class.new(Socketry::Error)
6
9
 
7
10
  # Invalid address
8
11
  AddressError = Class.new(Socketry::Error)
@@ -12,14 +12,16 @@ module Socketry
12
12
  hostname_or_port,
13
13
  port = nil,
14
14
  ssl_socket_class: OpenSSL::SSL::SSLSocket,
15
+ ssl_context: OpenSSL::SSL::SSLContext.new,
15
16
  ssl_params: nil,
16
17
  **args
17
18
  )
19
+ raise TypeError, "invalid SSL context (#{ssl_context.class})" unless ssl_context.is_a?(OpenSSL::SSL::SSLContext)
18
20
  raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)
19
21
 
20
22
  @ssl_socket_class = ssl_socket_class
21
- @ssl_context = OpenSSL::SSL::SSLContext.new
22
- @ssl_context.set_params(ssl_params) if ssl_params
23
+ @ssl_context = ssl_context
24
+ @ssl_context.set_params(ssl_params) if ssl_params && !ssl_params.empty?
23
25
  @ssl_context.freeze
24
26
 
25
27
  super(hostname_or_port, port, **args)
@@ -7,18 +7,27 @@ module Socketry
7
7
  class Socket < Socketry::TCP::Socket
8
8
  # Create an unconnected Socketry::SSL::Socket
9
9
  #
10
- # @param read_timeout [Numeric] Seconds to wait before an uncompleted read errors
10
+ # @param read_timeout [Numeric] Seconds to wait before an uncompleted read errors
11
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
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
15
18
  # @return [Socketry::SSL::Socket]
16
- def initialize(ssl_socket_class: OpenSSL::SSL::SSLSocket, ssl_params: nil, **args)
19
+ def initialize(
20
+ ssl_socket_class: OpenSSL::SSL::SSLSocket,
21
+ ssl_context: OpenSSL::SSL::SSLContext.new,
22
+ ssl_params: nil,
23
+ **args
24
+ )
25
+ raise TypeError, "invalid SSL context (#{ssl_context.class})" unless ssl_context.is_a?(OpenSSL::SSL::SSLContext)
17
26
  raise TypeError, "expected Hash, got #{ssl_params.class}" if ssl_params && !ssl_params.is_a?(Hash)
18
27
 
19
28
  @ssl_socket_class = ssl_socket_class
20
- @ssl_context = OpenSSL::SSL::SSLContext.new
21
- @ssl_context.set_params(ssl_params) if ssl_params
29
+ @ssl_context = ssl_context
30
+ @ssl_context.set_params(ssl_params) if ssl_params && !ssl_params.empty?
22
31
  @ssl_context.freeze
23
32
  @ssl_socket = nil
24
33
 
@@ -27,12 +36,12 @@ module Socketry
27
36
 
28
37
  # Make an SSL connection to a remote host
29
38
  #
30
- # @param remote_addr [String] DNS name or IP address of the host to connect to
31
- # @param remote_port [Fixnum] TCP port to connect to
32
- # @param local_addr [String] DNS name or IP address to bind to locally
33
- # @param local_port [Fixnum] Local TCP port to bind to
34
- # @param timeout [Numeric] Number of seconds to wait before aborting connect
35
- # @param socket_class [Class] Custom low-level socket class
39
+ # @param remote_addr [String] DNS name or IP address of the host to connect to
40
+ # @param remote_port [Fixnum] TCP port to connect to
41
+ # @param local_addr [String] DNS name or IP address to bind to locally
42
+ # @param local_port [Fixnum] Local TCP port to bind to
43
+ # @param timeout [Numeric] Number of seconds to wait before aborting connect
44
+ # @param socket_class [Class] Custom low-level socket class
36
45
  # @raise [Socketry::AddressError] an invalid address was given
37
46
  # @raise [Socketry::TimeoutError] connect operation timed out
38
47
  # @raise [Socketry::SSL::Error] an error occurred negotiating an SSL connection
@@ -98,6 +98,8 @@ module Socketry
98
98
  # Note: `exception: false` for Socket#connect_nonblock is only supported in Ruby 2.3+
99
99
  begin
100
100
  socket.connect_nonblock(remote_sockaddr)
101
+ rescue Errno::ECONNREFUSED => ex
102
+ raise Socketry::ConnectionRefusedError, "connection to #{remote_addr}:#{remote_port} refused", ex.backtrace
101
103
  rescue Errno::EINPROGRESS, Errno::EALREADY
102
104
  # Earlier JRuby 9.x versions do not seem to correctly support Socket#wait_writable in this case
103
105
  # Newer versions seem to behave correctly
@@ -162,14 +164,16 @@ module Socketry
162
164
  # Read a partial amounth of data, blocking until it becomes available
163
165
  #
164
166
  # @param size [Fixnum] number of bytes to attempt to read
167
+ # @param outbuf [String] an output buffer to read data into
168
+ # @param timeout [Numeric] Number of seconds to wait for read operation to complete
165
169
  # @raise [Socketry::Error] an I/O operation failed
166
- # @return [String]
170
+ # @return [String, :eof] bytes read, or :eof if socket closed while reading
167
171
  def readpartial(size, outbuf: nil, timeout: @read_timeout)
168
172
  set_timeout(timeout)
169
173
 
170
174
  begin
171
175
  while (result = read_nonblock(size, outbuf: outbuf)) == :wait_readable
172
- next if @socket.wait_readable(read_timeout)
176
+ next if @socket.wait_readable(time_remaining(timeout))
173
177
  raise TimeoutError, "read timed out after #{timeout} seconds"
174
178
  end
175
179
  ensure
@@ -179,9 +183,35 @@ module Socketry
179
183
  result || :eof
180
184
  end
181
185
 
186
+ # Read all of the data in a given string to a socket unless timeout or EOF
187
+ #
188
+ # @param size [Fixnum] number of bytes to attempt to read
189
+ # @param outbuf [String] an output buffer to read data into
190
+ # @param timeout [Numeric] Number of seconds to wait for read operation to complete
191
+ # @raise [Socketry::Error] an I/O operation failed
192
+ # @return [String, :eof] bytes read, or :eof if socket closed while reading
193
+ def read(size, outbuf: String.new, timeout: @write_timeout)
194
+ outbuf.clear
195
+ deadline = lifetime + timeout if timeout
196
+
197
+ begin
198
+ until outbuf.size == size
199
+ time_remaining = deadline - lifetime if deadline
200
+ raise Socketry::TimeoutError, "read timed out after #{timeout} seconds" if timeout && time_remaining <= 0
201
+
202
+ chunk = readpartial(size - outbuf.size, timeout: time_remaining)
203
+ return :eof if chunk == :eof
204
+
205
+ outbuf << chunk
206
+ end
207
+ end
208
+
209
+ outbuf
210
+ end
211
+
182
212
  # Perform a non-blocking write operation
183
213
  #
184
- # @param data [String] number of bytes to attempt to read
214
+ # @param data [String] data to write to the socket
185
215
  # @raise [Socketry::Error] an I/O operation failed
186
216
  # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
187
217
  def write_nonblock(data)
@@ -196,15 +226,16 @@ module Socketry
196
226
 
197
227
  # Write a partial amounth of data, blocking until it's completed
198
228
  #
199
- # @param data [String] number of bytes to attempt to read
229
+ # @param data [String] data to write to the socket
230
+ # @param timeout [Numeric] Number of seconds to wait for write operation to complete
200
231
  # @raise [Socketry::Error] an I/O operation failed
201
- # @return [Fixnum, :wait_writable] number of bytes written, or :wait_writable if op would block
232
+ # @return [Fixnum, :eof] number of bytes written, or :eof if socket closed during writing
202
233
  def writepartial(data, timeout: @write_timeout)
203
234
  set_timeout(timeout)
204
235
 
205
236
  begin
206
237
  while (result = write_nonblock(data)) == :wait_writable
207
- next if @socket.wait_writable(read_timeout)
238
+ next if @socket.wait_writable(time_remaining(timeout))
208
239
  raise TimeoutError, "write timed out after #{timeout} seconds"
209
240
  end
210
241
  ensure
@@ -214,6 +245,32 @@ module Socketry
214
245
  result || :eof
215
246
  end
216
247
 
248
+ # Write all of the data in a given string to a socket unless timeout or EOF
249
+ #
250
+ # @param data [String] data to write to the socket
251
+ # @param timeout [Numeric] Number of seconds to wait for write operation to complete
252
+ # @raise [Socketry::Error] an I/O operation failed
253
+ # @return [Fixnum] number of bytes written, or :eof if socket closed during writing
254
+ def write(data, timeout: @write_timeout)
255
+ total_written = data.size
256
+ deadline = lifetime + timeout if timeout
257
+
258
+ begin
259
+ until data.empty?
260
+ time_remaining = deadline - lifetime if deadline
261
+ raise Socketry::TimeoutError, "write timed out after #{timeout} seconds" if timeout && time_remaining <= 0
262
+
263
+ bytes_written = writepartial(data, timeout: time_remaining)
264
+ return :eof if bytes_written == :eof
265
+
266
+ break if bytes_written == data.bytesize
267
+ data = data.byteslice(bytes_written..-1)
268
+ end
269
+ end
270
+
271
+ total_written
272
+ end
273
+
217
274
  # Check whether Nagle's algorithm has been disabled
218
275
  #
219
276
  # @return [true] Nagle's algorithm has been explicitly disabled
@@ -44,6 +44,8 @@ module Socketry
44
44
  def set_timeout(timeout)
45
45
  raise Socketry::InternalError, "deadline already set" if @deadline
46
46
  return unless timeout
47
+ raise Socketry::TimeoutError, "time expired" if timeout < 0
48
+
47
49
  @deadline = lifetime + timeout
48
50
  end
49
51
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Socketry
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: socketry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-12 00:00:00.000000000 Z
11
+ date: 2016-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hitimes