tcp-client 0.5.1 → 0.6.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
  SHA256:
3
- metadata.gz: f36e18e6865766292ff44adf5cd46244e8944049f524553ba4168725454a2d0b
4
- data.tar.gz: abd4ec63f48c1be4e73922aaed7702bdacebe398e6cda13887fcd2efaa62818c
3
+ metadata.gz: 30592a849daf948c39b138e5f2d634761d59328f7a6eab11d45a9b069b8a747d
4
+ data.tar.gz: 39d0b9afe676700627a9b671c974ab5465cdb72e5ed6ec5c009add638a2b02b3
5
5
  SHA512:
6
- metadata.gz: 11b5302d3da2d0d46f35fb2370177916b1044b659b1f62bdbf0a30c39581844dd425487a6c94300b4a7afcf25eafef5828a6ef04cb0ff9bcae4fff01a9a312bb
7
- data.tar.gz: 54b3505c1eb90565e0a80dab5c92a394883b74cf3bb975ed83df4c1affbad15e9acbfcee7ec3d0a5bb79c1486fa46fbfbd958fc9e43106b9a97dc22411c84335
6
+ metadata.gz: d6a44381c618b1a89128499f7256bd9559dd4ea1efc5f34b82b681cfcca29f1c51b7ab47b13b513977e3276e298ec5ad02f6619f5ed36320f724e9cd192f925e
7
+ data.tar.gz: dfbcb8fb0e4b77770023023afbaee354e71ba4e41fe89daff9e197ce7d7a8a785e7891dbb4801651ae4702b56324d3431695e27acc781fafae9e7336db6c2c52
@@ -29,4 +29,6 @@ class TCPClient
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ private_constant(:Deadline)
32
34
  end
@@ -12,6 +12,13 @@ module IOWithDeadlineMixin
12
12
 
13
13
  def read_with_deadline(bytes_to_read, deadline, exception)
14
14
  raise(exception) unless deadline.remaining_time
15
+ if bytes_to_read.nil?
16
+ return(
17
+ with_deadline(deadline, exception) do
18
+ read_nonblock(65_536, exception: false)
19
+ end
20
+ )
21
+ end
15
22
  result = ''.b
16
23
  while result.bytesize < bytes_to_read
17
24
  read =
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class TCPClient
4
- VERSION = '0.5.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/tcp-client.rb CHANGED
@@ -13,12 +13,25 @@ class TCPClient
13
13
  def self.open(address, configuration = Configuration.default)
14
14
  client = new
15
15
  client.connect(Address.new(address), configuration)
16
- return client unless block_given?
17
- begin
18
- yield(client)
19
- ensure
20
- client.close
16
+ block_given? ? yield(client) : client
17
+ ensure
18
+ client.close if block_given?
19
+ end
20
+
21
+ def self.with_deadline(
22
+ timeout,
23
+ address,
24
+ configuration = Configuration.default
25
+ )
26
+ client = nil
27
+ raise(NoBlockGiven) unless block_given?
28
+ address = Address.new(address)
29
+ client = new
30
+ client.with_deadline(timeout) do
31
+ yield(client.connect(address, configuration))
21
32
  end
33
+ ensure
34
+ client&.close
22
35
  end
23
36
 
24
37
  attr_reader :address, :configuration
@@ -31,12 +44,12 @@ class TCPClient
31
44
  @address&.to_s || ''
32
45
  end
33
46
 
34
- def connect(address, configuration, exception: nil)
47
+ def connect(address, configuration, timeout: nil, exception: nil)
48
+ close if @socket
35
49
  raise(NoOpenSSL) if configuration.ssl? && !defined?(SSLSocket)
36
- close
37
50
  @address = Address.new(address)
38
51
  @configuration = configuration.dup.freeze
39
- @socket = create_socket(exception)
52
+ @socket = create_socket(timeout, exception)
40
53
  self
41
54
  end
42
55
 
@@ -63,48 +76,40 @@ class TCPClient
63
76
  @deadline = previous_deadline
64
77
  end
65
78
 
66
- def read(nbytes, timeout: nil, exception: nil)
79
+ def read(nbytes = nil, timeout: nil, exception: nil)
67
80
  raise(NotConnected) if closed?
68
- timeout.nil? && @deadline and
69
- return read_with_deadline(nbytes, @deadline, exception)
70
- deadline = Deadline.new(timeout || @configuration.read_timeout)
81
+ deadline = create_deadline(timeout, configuration.read_timeout)
71
82
  return @socket.read(nbytes) unless deadline.valid?
72
- read_with_deadline(nbytes, deadline, exception)
83
+ exception ||= configuration.read_timeout_error
84
+ @socket.read_with_deadline(nbytes, deadline, exception)
73
85
  end
74
86
 
75
87
  def write(*msg, timeout: nil, exception: nil)
76
88
  raise(NotConnected) if closed?
77
- timeout.nil? && @deadline and
78
- return write_with_deadline(msg, @deadline, exception)
79
- deadline = Deadline.new(timeout || @configuration.read_timeout)
89
+ deadline = create_deadline(timeout, configuration.write_timeout)
80
90
  return @socket.write(*msg) unless deadline.valid?
81
- write_with_deadline(msg, deadline, exception)
91
+ exception ||= configuration.write_timeout_error
92
+ msg.sum do |chunk|
93
+ @socket.write_with_deadline(chunk.b, deadline, exception)
94
+ end
82
95
  end
83
96
 
84
97
  def flush
85
- @socket.flush unless closed?
98
+ @socket&.flush
86
99
  self
87
100
  end
88
101
 
89
102
  private
90
103
 
91
- def create_socket(exception)
92
- exception ||= @configuration.connect_timeout_error
93
- deadline = Deadline.new(@configuration.connect_timeout)
94
- @socket = TCPSocket.new(@address, @configuration, deadline, exception)
95
- return @socket unless @configuration.ssl?
96
- SSLSocket.new(@socket, @address, @configuration, deadline, exception)
97
- end
98
-
99
- def read_with_deadline(nbytes, deadline, exception)
100
- exception ||= @configuration.read_timeout_error
101
- @socket.read_with_deadline(nbytes, deadline, exception)
104
+ def create_deadline(timeout, default)
105
+ timeout.nil? && @deadline ? @deadline : Deadline.new(timeout || default)
102
106
  end
103
107
 
104
- def write_with_deadline(msg, deadline, exception)
105
- exception ||= @configuration.write_timeout_error
106
- msg.sum do |chunk|
107
- @socket.write_with_deadline(chunk.b, deadline, exception)
108
- end
108
+ def create_socket(timeout, exception)
109
+ deadline = create_deadline(timeout, configuration.connect_timeout)
110
+ exception ||= configuration.connect_timeout_error
111
+ @socket = TCPSocket.new(address, configuration, deadline, exception)
112
+ return @socket unless configuration.ssl?
113
+ SSLSocket.new(@socket, address, configuration, deadline, exception)
109
114
  end
110
115
  end
data/sample/google.rb CHANGED
@@ -2,21 +2,20 @@
2
2
 
3
3
  require_relative '../lib/tcp-client'
4
4
 
5
- TCPClient.configure(
6
- connect_timeout: 0.5, # seconds to connect the server
7
- write_timeout: 0.25, # seconds to write a single data junk
8
- read_timeout: 0.5 # seconds to read some bytes
9
- )
10
-
11
- # the following sequence is not allowed to last longer than 1.25 seconds:
12
- # 0.5 seconds to connect
13
- # + 0.25 seconds to write data
14
- # + 0.5 seconds to read a response
5
+ # global configuration.
6
+ # - 0.5 seconds to connect the server
7
+ # - 0.25 seconds to write a single data junk
8
+ # - 0.25 seconds to read some bytes
9
+ TCPClient.configure do |cfg|
10
+ cfg.connect_timeout = 0.5
11
+ cfg.write_timeout = 0.25
12
+ cfg.read_timeout = 0.25
13
+ end
15
14
 
15
+ # request to Google:
16
+ # - send a simple HTTP get request
17
+ # - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
16
18
  TCPClient.open('www.google.com:80') do |client|
17
- # simple HTTP get request
18
- pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
19
-
20
- # read "HTTP/1.1 " + 3 byte HTTP status code
21
- pp client.read(12)
19
+ p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
20
+ p client.read(12)
22
21
  end
data/sample/google_ssl.rb CHANGED
@@ -2,18 +2,23 @@
2
2
 
3
3
  require_relative '../lib/tcp-client'
4
4
 
5
- TCPClient.configure do |cfg|
6
- cfg.connect_timeout = 1 # limit connect time the server to 1 second
7
- cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
8
- end
9
-
10
- TCPClient.open('www.google.com:443') do |client|
11
- # next sequence should not last longer than 0.5 seconds
12
- client.with_deadline(0.5) do
13
- # simple HTTP get request
14
- pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
5
+ # create a configuration.
6
+ # - use TLS 1.2
7
+ # - don't use internal buffering
8
+ cfg =
9
+ TCPClient::Configuration.create(
10
+ buffered: false,
11
+ ssl_params: {
12
+ ssl_version: :TLSv1_2
13
+ }
14
+ )
15
15
 
16
- # read "HTTP/1.1 " + 3 byte HTTP status code
17
- pp client.read(12)
18
- end
16
+ # request to Google:
17
+ # - limit all interactions to 0.5 seconds
18
+ # - use the Configuration cfg
19
+ # - send a simple HTTP get request
20
+ # - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
21
+ TCPClient.with_deadline(0.5, 'www.google.com:443', cfg) do |client|
22
+ p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
23
+ p client.read(12)
19
24
  end
data/test/helper.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/parallel'
5
+ require_relative '../lib/tcp-client'
6
+
7
+ # this pseudo-server never reads or writes anything
8
+ PseudoServer = TCPServer.new('localhost', 0)
9
+ Minitest.after_run { PseudoServer.close }
10
+
11
+ class Test < MiniTest::Test
12
+ parallelize_me!
13
+ end
14
+
15
+ class Timing
16
+ def initialize
17
+ @start_time = nil
18
+ end
19
+
20
+ def started?
21
+ @start_time != nil
22
+ end
23
+
24
+ def start
25
+ @start_time = now
26
+ end
27
+
28
+ def elapsed
29
+ now - @start_time
30
+ end
31
+
32
+ if defined?(Process::CLOCK_MONOTONIC)
33
+ def now
34
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
35
+ end
36
+ else
37
+ def now
38
+ ::Time.now
39
+ end
40
+ end
41
+ end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../test_helper'
4
-
5
- class AddressTest < MiniTest::Test
6
- parallelize_me!
3
+ require_relative '../helper'
7
4
 
5
+ class AddressTest < Test
8
6
  def test_create_from_integer
9
7
  subject = TCPClient::Address.new(42)
10
8
  assert_equal('localhost:42', subject.to_s)
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../test_helper'
4
-
5
- class ConfigurationTest < MiniTest::Test
6
- parallelize_me!
3
+ require_relative '../helper'
7
4
 
5
+ class ConfigurationTest < Test
8
6
  def test_defaults
9
7
  subject = TCPClient::Configuration.new
10
8
  assert(subject.buffered)
@@ -1,25 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../test_helper'
3
+ require_relative '../helper'
4
4
 
5
- class Deadlineest < MiniTest::Test
6
- parallelize_me!
5
+ class DeadlineTest < Test
6
+ Deadline = TCPClient.const_get(:Deadline)
7
7
 
8
8
  def test_validity
9
- assert(TCPClient::Deadline.new(1).valid?)
10
- assert(TCPClient::Deadline.new(0.0001).valid?)
9
+ assert(Deadline.new(1).valid?)
10
+ assert(Deadline.new(0.0001).valid?)
11
11
 
12
- refute(TCPClient::Deadline.new(0).valid?)
13
- refute(TCPClient::Deadline.new(nil).valid?)
12
+ refute(Deadline.new(0).valid?)
13
+ refute(Deadline.new(nil).valid?)
14
14
  end
15
15
 
16
16
  def test_remaining_time
17
- assert(TCPClient::Deadline.new(1).remaining_time > 0)
17
+ assert(Deadline.new(1).remaining_time > 0)
18
18
 
19
- assert_nil(TCPClient::Deadline.new(0).remaining_time)
20
- assert_nil(TCPClient::Deadline.new(nil).remaining_time)
19
+ assert_nil(Deadline.new(0).remaining_time)
20
+ assert_nil(Deadline.new(nil).remaining_time)
21
21
 
22
- deadline = TCPClient::Deadline.new(0.2)
22
+ deadline = Deadline.new(0.2)
23
23
  sleep(0.2)
24
24
  assert_nil(deadline.remaining_time)
25
25
  end
@@ -1,4 +1,4 @@
1
- require_relative '../test_helper'
1
+ require_relative '../helper'
2
2
 
3
3
  class DefauktConfigurationTest < MiniTest::Test
4
4
  def test_default
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../test_helper'
4
-
5
- class VersionTest < MiniTest::Test
6
- parallelize_me!
3
+ require_relative '../helper'
7
4
 
5
+ class VersionTest < Test
8
6
  def test_format
9
7
  assert_match(/\d+\.\d+\.\d+/, TCPClient::VERSION)
10
8
  end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'test_helper'
3
+ require_relative 'helper'
4
4
 
5
- class TCPClientTest < MiniTest::Test
6
- parallelize_me!
7
-
8
- HUGE_AMOUNT_OF_DATA = Array.new(2024, '?' * 1024).freeze
5
+ class TCPClientTest < Test
6
+ HUGE_AMOUNT_OF_DATA = Array.new(1024, '?' * 2048).freeze
9
7
 
10
8
  attr_reader :config
11
9
 
@@ -17,6 +15,10 @@ class TCPClientTest < MiniTest::Test
17
15
  PseudoServer.local_address.ip_port
18
16
  end
19
17
 
18
+ def address
19
+ "localhost:#{port}"
20
+ end
21
+
20
22
  def test_defaults
21
23
  subject = TCPClient.new
22
24
  assert(subject.closed?)
@@ -30,7 +32,7 @@ class TCPClientTest < MiniTest::Test
30
32
  def create_nonconnected_client
31
33
  client = TCPClient.new
32
34
  client.connect('', config)
33
- client
35
+ :you_should_not_get_this
34
36
  rescue Errno::EADDRNOTAVAIL
35
37
  client
36
38
  end
@@ -49,12 +51,12 @@ class TCPClientTest < MiniTest::Test
49
51
  end
50
52
 
51
53
  def test_connected_state
52
- TCPClient.open("localhost:#{port}", config) do |subject|
54
+ TCPClient.open(address, config) do |subject|
53
55
  refute(subject.closed?)
54
- assert_equal("localhost:#{port}", subject.to_s)
56
+ assert_equal(address, subject.to_s)
55
57
  refute_nil(subject.address)
56
58
  address_when_opened = subject.address
57
- assert_equal("localhost:#{port}", subject.address.to_s)
59
+ assert_equal(address, subject.address.to_s)
58
60
  assert_equal('localhost', subject.address.hostname)
59
61
  assert_instance_of(Addrinfo, subject.address.addrinfo)
60
62
  assert_same(port, subject.address.addrinfo.ip_port)
@@ -66,14 +68,14 @@ class TCPClientTest < MiniTest::Test
66
68
  end
67
69
 
68
70
  def check_read_timeout(timeout)
69
- TCPClient.open("localhost:#{port}", config) do |subject|
71
+ TCPClient.open(address, config) do |subject|
70
72
  refute(subject.closed?)
71
- start_time = nil
73
+ timing = Timing.new
72
74
  assert_raises(TCPClient::ReadTimeoutError) do
73
- start_time = Time.now
75
+ timing.start
74
76
  subject.read(42, timeout: timeout)
75
77
  end
76
- assert_in_delta(timeout, Time.now - start_time, 0.15)
78
+ assert_in_delta(timeout, timing.elapsed, 0.15)
77
79
  end
78
80
  end
79
81
 
@@ -84,14 +86,14 @@ class TCPClientTest < MiniTest::Test
84
86
  end
85
87
 
86
88
  def check_write_timeout(timeout)
87
- TCPClient.open("localhost:#{port}", config) do |subject|
89
+ TCPClient.open(address, config) do |subject|
88
90
  refute(subject.closed?)
89
- start_time = nil
91
+ timing = Timing.new
90
92
  assert_raises(TCPClient::WriteTimeoutError) do
91
- start_time = Time.now
93
+ timing.start
92
94
  subject.write(*HUGE_AMOUNT_OF_DATA, timeout: timeout)
93
95
  end
94
- assert_in_delta(timeout, Time.now - start_time, 0.15)
96
+ assert_in_delta(timeout, timing.elapsed, 0.15)
95
97
  end
96
98
  end
97
99
 
@@ -101,7 +103,7 @@ class TCPClientTest < MiniTest::Test
101
103
  end
102
104
 
103
105
  def test_write_deadline
104
- TCPClient.open("localhost:#{port}", config) do |subject|
106
+ TCPClient.open(address, config) do |subject|
105
107
  refute(subject.closed?)
106
108
  assert_raises(TCPClient::WriteTimeoutError) do
107
109
  subject.with_deadline(0.25) do |*args|
@@ -113,25 +115,25 @@ class TCPClientTest < MiniTest::Test
113
115
  end
114
116
 
115
117
  def test_read_deadline
116
- TCPClient.open("localhost:#{port}", config) do |subject|
118
+ TCPClient.open(address, config) do |subject|
117
119
  refute(subject.closed?)
118
120
  assert_raises(TCPClient::ReadTimeoutError) do
119
121
  subject.with_deadline(0.25) do |*args|
120
122
  assert_equal([subject], args)
121
- loop { subject.read(0) }
123
+ loop { subject.read(42) }
122
124
  end
123
125
  end
124
126
  end
125
127
  end
126
128
 
127
129
  def test_read_write_deadline
128
- TCPClient.open("localhost:#{port}", config) do |subject|
130
+ TCPClient.open(address, config) do |subject|
129
131
  refute(subject.closed?)
130
132
  assert_raises(TCPClient::TimeoutError) do
131
133
  subject.with_deadline(0.25) do |*args|
132
134
  assert_equal([subject], args)
133
135
  loop do
134
- subject.write('HUGE_AMOUNT_OF_DATA')
136
+ subject.write('some data')
135
137
  subject.read(0)
136
138
  end
137
139
  end
@@ -140,12 +142,12 @@ class TCPClientTest < MiniTest::Test
140
142
  end
141
143
 
142
144
  def check_connect_timeout(ssl_config)
143
- start_time = nil
145
+ timing = Timing.new
144
146
  assert_raises(TCPClient::ConnectTimeoutError) do
145
- start_time = Time.now
146
- TCPClient.new.connect("localhost:#{port}", ssl_config)
147
+ timing.start
148
+ TCPClient.new.connect(address, ssl_config)
147
149
  end
148
- assert_in_delta(ssl_config.connect_timeout, Time.now - start_time, 0.25)
150
+ assert_in_delta(ssl_config.connect_timeout, timing.elapsed, 0.25)
149
151
  end
150
152
 
151
153
  def test_connect_ssl_timeout
@@ -160,4 +162,19 @@ class TCPClientTest < MiniTest::Test
160
162
  ssl_config.connect_timeout = 1.5
161
163
  check_connect_timeout(ssl_config)
162
164
  end
165
+
166
+ def test_deadline
167
+ assert(TCPClient.with_deadline(0.15, address, config, &:itself).closed?)
168
+ end
169
+
170
+ def test_deadline_timeout
171
+ timing = Timing.new
172
+ assert_raises(TCPClient::ReadTimeoutError) do
173
+ timing.start
174
+ TCPClient.with_deadline(0.15, address, config) do |client|
175
+ client.read(42)
176
+ end
177
+ end
178
+ assert_in_delta(0.15, timing.elapsed, 0.15)
179
+ end
163
180
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-10 00:00:00.000000000 Z
11
+ date: 2021-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,13 +86,13 @@ files:
86
86
  - sample/google.rb
87
87
  - sample/google_ssl.rb
88
88
  - tcp-client.gemspec
89
+ - test/helper.rb
89
90
  - test/tcp-client/address_test.rb
90
91
  - test/tcp-client/configuration_test.rb
91
92
  - test/tcp-client/deadline_test.rb
92
93
  - test/tcp-client/default_configuration_test.rb
93
94
  - test/tcp-client/version_test.rb
94
95
  - test/tcp_client_test.rb
95
- - test/test_helper.rb
96
96
  homepage: https://github.com/mblumtritt/tcp-client
97
97
  licenses:
98
98
  - BSD-3-Clause
@@ -114,15 +114,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  - !ruby/object:Gem::Version
115
115
  version: '0'
116
116
  requirements: []
117
- rubygems_version: 3.2.22
117
+ rubygems_version: 3.2.28
118
118
  signing_key:
119
119
  specification_version: 4
120
120
  summary: A TCP client implementation with working timeout support.
121
121
  test_files:
122
+ - test/helper.rb
122
123
  - test/tcp-client/address_test.rb
123
124
  - test/tcp-client/configuration_test.rb
124
125
  - test/tcp-client/deadline_test.rb
125
126
  - test/tcp-client/default_configuration_test.rb
126
127
  - test/tcp-client/version_test.rb
127
128
  - test/tcp_client_test.rb
128
- - test/test_helper.rb
data/test/test_helper.rb DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'minitest/autorun'
4
- require 'minitest/parallel'
5
- require_relative '../lib/tcp-client'
6
-
7
- # this pseudo-server never reads or writes anything
8
- PseudoServer = TCPServer.new('localhost', 0)
9
- Minitest.after_run { PseudoServer.close }