tcp-client 0.5.1 → 0.9.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
  SHA256:
3
- metadata.gz: f36e18e6865766292ff44adf5cd46244e8944049f524553ba4168725454a2d0b
4
- data.tar.gz: abd4ec63f48c1be4e73922aaed7702bdacebe398e6cda13887fcd2efaa62818c
3
+ metadata.gz: 20df105b07989653ac9e9c877672318e1da4eb6a5171b088b137422ff03d22db
4
+ data.tar.gz: 393c2bf3f983f1da7dc8e3517b0cd7b170c2cdede3c546b603def84512e93806
5
5
  SHA512:
6
- metadata.gz: 11b5302d3da2d0d46f35fb2370177916b1044b659b1f62bdbf0a30c39581844dd425487a6c94300b4a7afcf25eafef5828a6ef04cb0ff9bcae4fff01a9a312bb
7
- data.tar.gz: 54b3505c1eb90565e0a80dab5c92a394883b74cf3bb975ed83df4c1affbad15e9acbfcee7ec3d0a5bb79c1486fa46fbfbd958fc9e43106b9a97dc22411c84335
6
+ metadata.gz: c94f0a7c51cd5b33edf0de9fb02d454e539f95b79891ad81c81adb4832829059fc34b8c3d4e141594dd0b95752807f1c32dbc9c5192914a5ec02345bb056075c
7
+ data.tar.gz: 1ab3ef36b1534cf697c911c5d4ce01137dc9860598daa34765521cbec48fe6698294402cf0a0b768f2c460212cb3ce9378adb12ba7e65c76f1531e7bd66a1688
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
- local/
2
1
  tmp/
3
2
  pkg/
3
+ local/
4
+ doc/
5
+ .yardoc/
4
6
  gems.locked
data/README.md CHANGED
@@ -11,24 +11,26 @@ This Gem implements a TCP client with (optional) SSL support. It is an easy to u
11
11
  ```ruby
12
12
  require 'tcp-client'
13
13
 
14
- TCPClient.configure do |cfg|
15
- cfg.connect_timeout = 1 # limit connect time the server to 1 second
16
- cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
17
- end
18
-
19
- TCPClient.open('www.google.com:443') do |client|
20
- # next sequence should not last longer than 0.5 seconds
21
- client.with_deadline(0.5) do
22
- # simple HTTP get request
23
- pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
24
-
25
- # read "HTTP/1.1 " + 3 byte HTTP status code
26
- pp client.read(12)
27
- end
14
+ # create a configuration:
15
+ # - don't use internal buffering
16
+ # - use TLS 1.2 or TLS 1.3
17
+ cfg = TCPClient::Configuration.create(
18
+ buffered: false,
19
+ ssl_params: {min_version: :TLS1_2, max_version: :TLS1_3}
20
+ )
21
+
22
+ # request to Google.com:
23
+ # - limit all network interactions to 1.5 seconds
24
+ # - use the Configuration cfg
25
+ # - send a simple HTTP get request
26
+ # - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
27
+ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
28
+ client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") # >= 40
29
+ client.read(12) # => "HTTP/1.1 200"
28
30
  end
29
31
  ```
30
32
 
31
- ### Installation
33
+ ## Installation
32
34
 
33
35
  Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
34
36
 
@@ -41,13 +43,13 @@ gem 'tcp-client'
41
43
  and install it by running Bundler:
42
44
 
43
45
  ```bash
44
- $ bundle
46
+ bundle
45
47
  ```
46
48
 
47
49
  To install the gem globally use:
48
50
 
49
51
  ```bash
50
- $ gem install tcp-client
52
+ gem install tcp-client
51
53
  ```
52
54
 
53
55
  After that you need only a single line of code in your project to have all tools on board:
@@ -3,9 +3,44 @@
3
3
  require 'socket'
4
4
 
5
5
  class TCPClient
6
+ #
7
+ # The address used by a TCPClient
8
+ #
6
9
  class Address
7
- attr_reader :hostname, :addrinfo
10
+ #
11
+ # @return [String] the host name
12
+ #
13
+ attr_reader :hostname
8
14
 
15
+ #
16
+ # @return [Addrinfo] the address info
17
+ #
18
+ attr_reader :addrinfo
19
+
20
+ #
21
+ # Initializes an address
22
+ # @overload initialize(addr)
23
+ # The addr can be specified as
24
+ # - a valid named address containing the port like +my.host.test:80+
25
+ # - a valid TCPv4 address like +142.250.181.206:80+
26
+ # - a valid TCPv6 address like +[2001:16b8:5093:3500:ad77:abe6:eb88:47b6]:80+
27
+ #
28
+ # @param addr [String] address string
29
+ #
30
+ # @overload initialize(address)
31
+ # Used to create a copy
32
+ #
33
+ # @param address [Address]
34
+ #
35
+ # @overload initialize(addrinfo)
36
+ #
37
+ # @param addrinfo [Addrinfo] containing the addressed host and port
38
+ #
39
+ # @overload initialize(port)
40
+ # Adresses the port on the local machine.
41
+ #
42
+ # @param port [Integer] the addressed port
43
+ #
9
44
  def initialize(addr)
10
45
  case addr
11
46
  when self.class
@@ -20,20 +55,28 @@ class TCPClient
20
55
  @addrinfo.freeze
21
56
  end
22
57
 
58
+ #
59
+ # @return [String] text representation of self as "<host>:<port>"
60
+ #
23
61
  def to_s
24
62
  return "[#{@hostname}]:#{@addrinfo.ip_port}" if @hostname.index(':') # IP6
25
63
  "#{@hostname}:#{@addrinfo.ip_port}"
26
64
  end
27
65
 
66
+ #
67
+ # @return [Hash] containing the host and port
68
+ #
28
69
  def to_h
29
70
  { host: @hostname, port: @addrinfo.ip_port }
30
71
  end
31
72
 
73
+ # @!visibility private
32
74
  def ==(other)
33
75
  to_h == other.to_h
34
76
  end
35
77
  alias eql? ==
36
78
 
79
+ # @!visibility private
37
80
  def equal?(other)
38
81
  self.class == other.class && self == other
39
82
  end
@@ -3,126 +3,285 @@
3
3
  require_relative 'errors'
4
4
 
5
5
  class TCPClient
6
+ #
7
+ # A Configuration is used to configure the behavior of a {TCPClient} instance.
8
+ #
9
+ # It allows to specify to monitor timeout, how to handle exceptions, if SSL
10
+ # should be used and to setup the underlying Socket.
11
+ #
6
12
  class Configuration
13
+ #
14
+ # @overload create(options)
15
+ # Shorthand to create a new configuration with given options.
16
+ #
17
+ # @example
18
+ # config = TCPClient::Configuration.create(buffered: false)
19
+ #
20
+ # @param options [Hash] see {#initialize} for details
21
+ #
22
+ # @return [Configuration] the initialized configuration
23
+ #
24
+ # @overload create(&block)
25
+ # Shorthand to initialize a new configuration.
26
+ #
27
+ # @example
28
+ # config = TCPClient::Configuration.create do |cfg|
29
+ # cfg.buffered = false
30
+ # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
31
+ # end
32
+ #
33
+ # @yieldparam cfg {Configuration}
34
+ #
35
+ # @return [Configuration] the initialized configuration
36
+ #
7
37
  def self.create(options = {})
8
- ret = new(options)
9
- yield(ret) if block_given?
10
- ret
11
- end
12
-
13
- attr_reader :buffered,
14
- :keep_alive,
15
- :reverse_lookup,
16
- :connect_timeout,
17
- :read_timeout,
18
- :write_timeout,
19
- :connect_timeout_error,
20
- :read_timeout_error,
21
- :write_timeout_error
22
- attr_accessor :ssl_params
38
+ cfg = new(options)
39
+ yield(cfg) if block_given?
40
+ cfg
41
+ end
23
42
 
43
+ #
44
+ # Intializes the instance with given options.
45
+ #
46
+ # @param options [Hash]
47
+ # @option options [Boolean] :buffered, see {#buffered}
48
+ # @option options [Boolean] :keep_alive, see {#keep_alive}
49
+ # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
50
+ # @option options [Boolean] :normalize_network_errors, see {#normalize_network_errors}
51
+ # @option options [Numeric] :connect_timeout, see {#connect_timeout}
52
+ # @option options [Exception] :connect_timeout_error, see {#connect_timeout_error}
53
+ # @option options [Numeric] :read_timeout, see {#read_timeout}
54
+ # @option options [Exception] :read_timeout_error, see {#read_timeout_error}
55
+ # @option options [Numeric] :write_timeout, see {#write_timeout}
56
+ # @option options [Exception] :write_timeout_error, see {#write_timeout_error}
57
+ # @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
58
+ #
24
59
  def initialize(options = {})
25
60
  @buffered = @keep_alive = @reverse_lookup = true
26
61
  self.timeout = @ssl_params = nil
27
62
  @connect_timeout_error = ConnectTimeoutError
28
63
  @read_timeout_error = ReadTimeoutError
29
64
  @write_timeout_error = WriteTimeoutError
65
+ @normalize_network_errors = false
30
66
  options.each_pair { |attribute, value| set(attribute, value) }
31
67
  end
32
68
 
33
- def freeze
34
- @ssl_params.freeze
35
- super
36
- end
37
-
38
- def initialize_copy(_org)
39
- super
40
- @ssl_params = Hash[@ssl_params] if @ssl_params
41
- self
42
- end
43
-
44
- def ssl?
45
- @ssl_params ? true : false
46
- end
47
-
48
- def ssl=(value)
49
- @ssl_params =
50
- if Hash === value
51
- Hash[value]
52
- else
53
- value ? {} : nil
54
- end
55
- end
69
+ #
70
+ # Enables/disables use of Socket-level buffers.
71
+ #
72
+ # @return [true] if the connection is allowed to use internal buffers (default)
73
+ # @return [false] if buffering is not allowed
74
+ #
75
+ attr_reader :buffered
56
76
 
57
77
  def buffered=(value)
58
78
  @buffered = value ? true : false
59
79
  end
60
80
 
81
+ #
82
+ # Enables/disables use of Socket-level keep alive handling.
83
+ #
84
+ # @return [true] if the connection is allowed to use keep alive signals (default)
85
+ # @return [false] if the connection should not check keep alive
86
+ #
87
+ attr_reader :keep_alive
88
+
61
89
  def keep_alive=(value)
62
90
  @keep_alive = value ? true : false
63
91
  end
64
92
 
93
+ #
94
+ # Enables/disables address lookup.
95
+ #
96
+ # @return [true] if the connection is allowed to lookup the address (default)
97
+ # @return [false] if the address lookup is not required
98
+ #
99
+ attr_reader :reverse_lookup
100
+
65
101
  def reverse_lookup=(value)
66
102
  @reverse_lookup = value ? true : false
67
103
  end
68
104
 
69
- def timeout=(seconds)
70
- @connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
105
+ #
106
+ # Enables/disables if network exceptions should be raised as {NetworkError}.
107
+ #
108
+ # @return [true] if all network exceptions should be raised as {NetworkError}
109
+ # @return [false] if socket/system errors should not be normalzed (default)
110
+ #
111
+ attr_reader :normalize_network_errors
112
+
113
+ def normalize_network_errors=(value)
114
+ @normalize_network_errors = value ? true : false
71
115
  end
72
116
 
73
- def connect_timeout=(seconds)
74
- @connect_timeout = seconds(seconds)
117
+ #
118
+ # @attribute [w] timeout
119
+ # Shorthand to set timeout value for connect, read and write at once or to disable any timeout monitoring
120
+ #
121
+ # @return [Numeric] maximum time in seconds for any action
122
+ # @return [nil] if all timeout monitoring should be disabled (default)
123
+ #
124
+ # @see #connect_timeout
125
+ # @see #read_timeout
126
+ # @see #write_timeout
127
+ #
128
+ def timeout=(value)
129
+ @connect_timeout = @write_timeout = @read_timeout = seconds(value)
75
130
  end
76
131
 
77
- def read_timeout=(seconds)
78
- @read_timeout = seconds(seconds)
132
+ #
133
+ # @attribute [w] timeout_error
134
+ # Shorthand to configure exception class raised when connect, read or write exceeded the configured timeout
135
+ #
136
+ # @return [Class] exception class raised
137
+ #
138
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
139
+ #
140
+ # @see #connect_timeout_error
141
+ # @see #read_timeout_error
142
+ # @see #write_timeout_error
143
+ #
144
+ def timeout_error=(value)
145
+ raise(NotAnExceptionError, value) unless exception_class?(value)
146
+ @connect_timeout_error =
147
+ @read_timeout_error = @write_timeout_error = value
79
148
  end
80
149
 
81
- def write_timeout=(seconds)
82
- @write_timeout = seconds(seconds)
150
+ #
151
+ # Configures maximum time in seconds to establish a connection.
152
+ #
153
+ # @return [Numeric] maximum time in seconds to establish a connection
154
+ # @return [nil] if the connect time should not be checked (default)
155
+ #
156
+ attr_reader :connect_timeout
157
+
158
+ def connect_timeout=(value)
159
+ @connect_timeout = seconds(value)
83
160
  end
84
161
 
85
- def timeout_error=(exception)
86
- raise(NotAnException, exception) unless exception_class?(exception)
87
- @connect_timeout_error =
88
- @read_timeout_error = @write_timeout_error = exception
162
+ #
163
+ # @return [Class] exception class raised if a {TCPClient#connect} timed out
164
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
165
+ #
166
+ attr_reader :connect_timeout_error
167
+
168
+ def connect_timeout_error=(value)
169
+ raise(NotAnExceptionError, value) unless exception_class?(value)
170
+ @connect_timeout_error = value
171
+ end
172
+
173
+ #
174
+ # Configures maximum time in seconds to finish a {TCPClient#read}.
175
+ #
176
+ # @return [Numeric] maximum time in seconds to finish a {TCPClient#read} request
177
+ # @return [nil] if the read time should not be checked (default)
178
+ #
179
+ attr_reader :read_timeout
180
+
181
+ def read_timeout=(value)
182
+ @read_timeout = seconds(value)
183
+ end
184
+
185
+ #
186
+ # @return [Class] exception class raised if a {TCPClient#read} timed out
187
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
188
+ #
189
+ attr_reader :read_timeout_error
190
+
191
+ def read_timeout_error=(value)
192
+ raise(NotAnExceptionError, value) unless exception_class?(value)
193
+ @read_timeout_error = value
89
194
  end
90
195
 
91
- def connect_timeout_error=(exception)
92
- raise(NotAnException, exception) unless exception_class?(exception)
93
- @connect_timeout_error = exception
196
+ #
197
+ # Configures maximum time in seconds to finish a {TCPClient#write}.
198
+ #
199
+ # @return [Numeric] maximum time in seconds to finish a {TCPClient#write} request
200
+ # @return [nil] if the write time should not be checked (default)
201
+ #
202
+ attr_reader :write_timeout
203
+
204
+ def write_timeout=(value)
205
+ @write_timeout = seconds(value)
94
206
  end
95
207
 
96
- def read_timeout_error=(exception)
97
- raise(NotAnException, exception) unless exception_class?(exception)
98
- @read_timeout_error = exception
208
+ #
209
+ # @return [Class] exception class raised if a {TCPClient#write} timed out
210
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
211
+ #
212
+ attr_reader :write_timeout_error
213
+
214
+ def write_timeout_error=(value)
215
+ raise(NotAnExceptionError, value) unless exception_class?(value)
216
+ @write_timeout_error = value
99
217
  end
100
218
 
101
- def write_timeout_error=(exception)
102
- raise(NotAnException, exception) unless exception_class?(exception)
103
- @write_timeout_error = exception
219
+ #
220
+ # @attribute ssl?
221
+ # @return [Boolean] wheter SSL is configured, see {#ssl_params}
222
+ #
223
+ def ssl?
224
+ @ssl_params ? true : false
225
+ end
226
+
227
+ #
228
+ # Configures the SSL parameters used to initialize a SSL context.
229
+ #
230
+ # @return [Hash<Symbol, Object>] SSL parameters for the SSL context
231
+ # @return [nil] if no SSL should be used (default)
232
+ #
233
+ attr_reader :ssl_params
234
+
235
+ def ssl_params=(value)
236
+ @ssl_params =
237
+ if value.respond_to?(:to_hash)
238
+ Hash[value.to_hash]
239
+ elsif value.respond_to?(:to_h)
240
+ Hash[value.to_h]
241
+ else
242
+ value ? {} : nil
243
+ end
104
244
  end
245
+ alias ssl= ssl_params=
105
246
 
247
+ #
248
+ # @return [Hash] configuration as a Hash
249
+ #
106
250
  def to_h
107
251
  {
108
252
  buffered: @buffered,
109
253
  keep_alive: @keep_alive,
110
254
  reverse_lookup: @reverse_lookup,
111
255
  connect_timeout: @connect_timeout,
112
- read_timeout: @read_timeout,
113
- write_timeout: @write_timeout,
114
256
  connect_timeout_error: @connect_timeout_error,
257
+ read_timeout: @read_timeout,
115
258
  read_timeout_error: @read_timeout_error,
259
+ write_timeout: @write_timeout,
116
260
  write_timeout_error: @write_timeout_error,
117
261
  ssl_params: @ssl_params
118
262
  }
119
263
  end
120
264
 
265
+ # @!visibility private
266
+ def freeze
267
+ @ssl_params.freeze
268
+ super
269
+ end
270
+
271
+ # @!visibility private
272
+ def initialize_copy(_org)
273
+ super
274
+ @ssl_params = Hash[@ssl_params] if @ssl_params
275
+ self
276
+ end
277
+
278
+ # @!visibility private
121
279
  def ==(other)
122
280
  to_h == other.to_h
123
281
  end
124
282
  alias eql? ==
125
283
 
284
+ # @!visibility private
126
285
  def equal?(other)
127
286
  self.class == other.class && self == other
128
287
  end
@@ -136,7 +295,7 @@ class TCPClient
136
295
  def set(attribute, value)
137
296
  public_send("#{attribute}=", value)
138
297
  rescue NoMethodError
139
- raise(UnknownAttribute, attribute)
298
+ raise(UnknownAttributeError, attribute)
140
299
  end
141
300
 
142
301
  def seconds(value)
@@ -29,4 +29,6 @@ class TCPClient
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ private_constant(:Deadline)
32
34
  end
@@ -6,14 +6,36 @@ class TCPClient
6
6
  @default_configuration = Configuration.new
7
7
 
8
8
  class << self
9
+ #
10
+ # @return [Configuration] used by default if no dedicated configuration was specified
11
+ #
9
12
  attr_reader :default_configuration
10
13
 
14
+ #
15
+ # Configure the default configuration which is used if no dedicated
16
+ # configuration was specified.
17
+ #
18
+ # @example
19
+ # TCPClient.configure do |cfg|
20
+ # cfg.buffered = false
21
+ # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
22
+ # end
23
+ #
24
+ # @param options [Hash] see {Configuration#initialize} for details
25
+ #
26
+ # @yieldparam cfg {Configuration} the new configuration
27
+ #
28
+ # @return [Configuration] the new default configuration
29
+ #
11
30
  def configure(options = {}, &block)
12
31
  @default_configuration = Configuration.create(options, &block)
13
32
  end
14
33
  end
15
34
 
16
35
  class Configuration
36
+ #
37
+ # @return [Configuration] used by default if no dedicated configuration was specified
38
+ #
17
39
  def self.default
18
40
  TCPClient.default_configuration
19
41
  end