socketry 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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