tcp-client 0.5.1 → 0.9.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: 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