blur 1.8.6 → 2.1.6

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.
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
4
  class Network
@@ -13,14 +13,23 @@ module Blur
13
13
  class Connection < EM::Protocols::LineAndTextProtocol
14
14
  SSLValidationError = Class.new StandardError
15
15
 
16
+ # @return [Float] the default connection timeout interval in seconds.
17
+ DEFAULT_CONNECT_TIMEOUT_INTERVAL = 30
18
+
16
19
  # Check whether or not connection is established.
17
- def established?; @connected == true end
20
+ def established?
21
+ @connected == true
22
+ end
18
23
 
19
24
  # EventMachine instantiates this class, and then sends event messages to
20
25
  # that instance.
21
26
  def initialize network
22
27
  @network = network
23
28
  @connected = false
29
+ connect_timeout = network.options.fetch 'connect_timeout',
30
+ DEFAULT_CONNECT_TIMEOUT_INTERVAL
31
+
32
+ self.pending_connect_timeout = connect_timeout
24
33
 
25
34
  super
26
35
  end
@@ -28,19 +37,18 @@ module Blur
28
37
  # Called when a new connection is being set up, all we're going to use
29
38
  # it for is to enable SSL/TLS on our connection.
30
39
  def post_init
31
- if @network.secure?
32
- verify_peer = (@network.options[:ssl_no_verify] ? false : true)
40
+ return unless @network.secure?
33
41
 
34
- start_tls verify_peer: verify_peer
35
- end
42
+ verify_peer = (@network.options[:ssl_no_verify] ? false : true)
43
+ start_tls verify_peer: verify_peer
36
44
  end
37
45
 
38
46
  # Called when a line was received, the connection sends it to the network
39
47
  # delegate which then sends it to the client.
40
48
  def receive_line line
41
- command = Command.parse line
49
+ message = IRCParser::Message.parse line
42
50
 
43
- @network.got_command command
51
+ @network.got_message message
44
52
  end
45
53
 
46
54
  # Called when the SSL handshake was completed with the remote server,
@@ -61,12 +69,8 @@ module Blur
61
69
  ssl_cert_file = @network.options[:ssl_cert_file]
62
70
  peer_certificate = OpenSSL::X509::Certificate.new peer_cert
63
71
 
64
- if ssl_cert_file
65
- unless File.readable? ssl_cert_file
66
- raise SSLValidationError, "Could not read the CA certificate file."
67
-
68
- return false
69
- end
72
+ if ssl_cert_file && !File.readable?(ssl_cert_file)
73
+ raise SSLValidationError, 'Could not read the CA certificate file.'
70
74
  end
71
75
 
72
76
  if fingerprint_verification?
@@ -75,9 +79,7 @@ module Blur
75
79
 
76
80
  if fingerprint != peer_fingerprint
77
81
  raise SSLValidationError,
78
- "Expected fingerprint '#{fingerprint}', but got '#{peer_fingerprint}'"
79
-
80
- return false
82
+ "Expected fingerprint '#{fingerprint}', but got '#{peer_fingerprint}'"
81
83
  end
82
84
  end
83
85
 
@@ -85,11 +87,7 @@ module Blur
85
87
  ca_certificate = OpenSSL::X509::Certificate.new File.read ssl_cert_file
86
88
  valid_signature = peer_certificate.verify ca_certificate.public_key
87
89
 
88
- if not valid_signature
89
- raise SSLValidationError, "Certificate verify failed"
90
-
91
- return false
92
- end
90
+ raise SSLValidationError, 'Certificate verify failed' unless valid_signature
93
91
  end
94
92
 
95
93
  true
@@ -98,9 +96,7 @@ module Blur
98
96
  # Called once the connection is finally established.
99
97
  def connection_completed
100
98
  # We aren't completely connected yet if the connection is encrypted.
101
- unless @network.secure?
102
- connected!
103
- end
99
+ connected! unless @network.secure?
104
100
  end
105
101
 
106
102
  # Called just as the connection is being terminated, either by remote or
@@ -112,7 +108,8 @@ module Blur
112
108
  super
113
109
  end
114
110
 
115
- private
111
+ private
112
+
116
113
  # Called when connection has been established.
117
114
  def connected!
118
115
  @connected = true
@@ -122,12 +119,12 @@ module Blur
122
119
 
123
120
  # Returns true if we're expected to verify the certificate fingerprint.
124
121
  def fingerprint_verification?
125
- not @network.options[:ssl_fingerprint].nil?
122
+ !@network.options[:ssl_fingerprint].nil?
126
123
  end
127
124
 
128
125
  # Returns true if we should verify the peer certificate.
129
126
  def certificate_verification?
130
- not @network.options[:ssl_cert_file].nil?
127
+ !@network.options[:ssl_cert_file].nil?
131
128
  end
132
129
 
133
130
  # Get the hexadecimal representation of the certificates public key.
@@ -143,9 +140,8 @@ module Blur
143
140
  fingerprint = @network.options[:ssl_fingerprint]
144
141
 
145
142
  raise SSLValidationError,
146
- "Expected fingerprint '#{fingerprint}' but got '#{peer_fingerprint}'"
143
+ "Expected fingerprint '#{fingerprint}' but got '#{peer_fingerprint}'"
147
144
  end
148
-
149
145
  end
150
146
  end
151
147
  end
@@ -1,20 +1,22 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
2
4
 
3
5
  module Blur
4
6
  class Network
5
7
  # ISupport class that enables servers to announce what they support.
6
- #
8
+ #
7
9
  # @see https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03
8
10
  class ISupport < Hash
9
11
  # Return the network reference.
10
12
  attr_accessor :network
11
13
 
12
14
  # ISUPPORT parameters which should always be casted to numeric values.
13
- NumericParams = %w[CHANNELLEN MODES NICKLEN KICKLEN TOPICLEN AWAYLEN
14
- MAXCHANNELS MAXBANS MAXPARA MAXTARGETS].freeze
15
+ NUMERIC_PARAMS = %w[CHANNELLEN MODES NICKLEN KICKLEN TOPICLEN AWAYLEN
16
+ MAXCHANNELS MAXBANS MAXPARA MAXTARGETS].freeze
15
17
 
16
18
  # Our parsers for parameters that require special treatment.
17
- Parsers = {
19
+ PARSERS = {
18
20
  # CHANLIMIT=pfx:num[,pfx:num,...]
19
21
  #
20
22
  # This parameter specifies the maximum number of channels that a client
@@ -24,10 +26,10 @@ module Blur
24
26
  # the client may join in total. If there is no limit to the number of
25
27
  # certain channel type(s) a client may join, the limit should be
26
28
  # specified as the empty string, for example "#:".
27
- %w[CHANLIMIT] => -> (value) do
28
- Hash.new.tap do |result|
29
- params = value.split ?,
30
- mappings = params.map{|param| param.split ?: }
29
+ %w[CHANLIMIT] => lambda do |value|
30
+ {}.tap do |result|
31
+ params = value.split ','
32
+ mappings = params.map { |param| param.split ':' }
31
33
 
32
34
  mappings.each do |prefixes, limit|
33
35
  prefixes.each_char do |prefix|
@@ -48,10 +50,12 @@ module Blur
48
50
  #
49
51
  # The order of the modes is from that which gives most privileges on
50
52
  # the channel, to that which gives the least.
51
- %w[PREFIX] => -> (value) do
52
- Hash.new.tap do |result|
53
- if value =~ /^\((.+)\)(.*)/
54
- modes, prefix = $~[1..2]
53
+ %w[PREFIX] => lambda do |value|
54
+ {}.tap do |result|
55
+ match = value.match(/^\((.+)\)(.*)/)
56
+
57
+ if match
58
+ modes, prefix = match[1..2]
55
59
 
56
60
  modes.chars.each_with_index do |char, index|
57
61
  result[char] = prefix[index]
@@ -77,39 +81,41 @@ module Blur
77
81
  # mode is removed both in the client's and server's MODE command.
78
82
  # o Type D: Modes that change a setting on the channel. These modes
79
83
  # never take a parameter.
80
- %w[CHANMODES] => -> (value) do
81
- Hash.new.tap do |r|
82
- r["A"], r["B"], r["C"], r["D"] = value.split(?,).map &:chars
84
+ %w[CHANMODES] => lambda do |value|
85
+ {}.tap do |r|
86
+ r['A'], r['B'], r['C'], r['D'] = value.split(',').map &:chars
83
87
  end
84
88
  end,
85
89
 
86
90
  # Cast known params that are numeric, to a numeric value.
87
- NumericParams => -> (value) do
91
+ NUMERIC_PARAMS => lambda do |value|
88
92
  value.to_i
89
93
  end
90
- }
94
+ }.freeze
91
95
 
92
96
  # Initialize a new ISupport with a network reference.
93
- #
97
+ #
94
98
  # @param network [Network] The parent network.
95
99
  def initialize network
100
+ super
101
+
96
102
  @network = network
97
103
 
98
104
  # Set default ISUPPORT values.
99
105
  #
100
106
  # @see
101
107
  # https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03#appendix-A
102
- self["MODES"] = 3
103
- self["PREFIX"] = { "o" => "@", "v" => "+" }
104
- self["KICKLEN"] = 200
105
- self["NICKLEN"] = 9
106
- self["MAXLIST"] = { "#" => Float::INFINITY, "&" => Float::INFINITY }
108
+ self['MODES'] = 3
109
+ self['PREFIX'] = { 'o' => '@', 'v' => '+' }
110
+ self['KICKLEN'] = 200
111
+ self['NICKLEN'] = 9
112
+ self['MAXLIST'] = { '#' => Float::INFINITY, '&' => Float::INFINITY }
107
113
  self['TOPICLEN'] = 200
108
- self["CHANMODES"] = {}
109
- self["CHANTYPES"] = %w{# &}
110
- self["CHANLIMIT"] = { "#" => Float::INFINITY, "&" => Float::INFINITY }
111
- self["CHANNELLEN"] = 200
112
- self["CASEMAPPING"] = "rfc1459"
114
+ self['CHANMODES'] = {}
115
+ self['CHANTYPES'] = %w[# &]
116
+ self['CHANLIMIT'] = { '#' => Float::INFINITY, '&' => Float::INFINITY }
117
+ self['CHANNELLEN'] = 200
118
+ self['CASEMAPPING'] = 'rfc1459'
113
119
  end
114
120
 
115
121
  # Parse a list of parameters to see what the server supports.
@@ -117,12 +123,12 @@ module Blur
117
123
  # @param parameters [Array] The list of parameters.
118
124
  def parse *params
119
125
  params.each do |parameter|
120
- name, value = parameter.split ?=
126
+ name, value = parameter.split '='
121
127
 
122
128
  if value
123
- _, parser = Parsers.find{|key, value| key.include? name }
129
+ _, parser = PARSERS.find { |key, _value| key.include? name }
124
130
 
125
- self[name] = parser.nil? ? value : parser.(value)
131
+ self[name] = parser.nil? ? value : parser.call(value)
126
132
  else
127
133
  self[name] = true
128
134
  end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Blur
4
4
  # The +Network+ module is to be percieved as an IRC network.
@@ -6,44 +6,81 @@ module Blur
6
6
  # Although the connection is a part of the network module, it is mainly used
7
7
  # for network-related structures, such as {User}, {Channel} and {Command}.
8
8
  class Network
9
- include Logging
10
-
11
9
  # +ConnectionError+ should only be triggered from within {Connection}.
12
10
  class ConnectionError < StandardError; end
13
11
 
12
+ DEFAULT_PING_INTERVAL = 30
13
+ DEFAULT_RECONNECT = true
14
+
15
+ # Returns a unique identifier for this network.
16
+ #
17
+ # You can override the id in your network configuration by setting an 'id'
18
+ # key with the id you want.. If no id is specified, the the id will be
19
+ # constructed from the hostname and port number
20
+ # in the format "<host>:<port>"
21
+ #
22
+ # @return [String] the unique identifier for this network.
23
+ attr_reader :id
24
+ # @return [String] the current nickname.
25
+ attr_accessor :nickname
14
26
  # @return [Hash] the network options.
15
27
  attr_accessor :options
16
- # @return [Array] the list of channels the client is in.
28
+ # @return [Hash] the map of users that is known.
29
+ attr_accessor :users
30
+ # @return [Hash] the map of channels the client is in.
17
31
  attr_accessor :channels
18
- # @return [Array] the list of private messages the client remembers.
19
- attr_accessor :dialogues
20
- # @return [Client] the client delegate.
21
- attr_accessor :delegate
32
+ # @return [Client] the client reference.
33
+ attr_accessor :client
22
34
  # @return [Network::Connection] the connection instance.
23
35
  attr_accessor :connection
24
36
  # @return [Network::ISupport] the network isupport specs.
25
37
  attr_accessor :isupport
38
+ # @return [Array<String>] list of capabilities supported by the network.
39
+ attr_accessor :capabilities
40
+ # @return [Boolean] true if we're waiting for a capability negotiation.
41
+ attr_reader :waiting_for_cap
42
+ # @return [Time] the last time a pong was sent or received.
43
+ attr_accessor :last_pong_time
44
+ # The max PING interval for the server. This is used to determine when the
45
+ # client will attempt to send its own PING command.
46
+ #
47
+ # @note the actual time until a client PING is sent can vary by an
48
+ # additional 0-30 seconds.
49
+ # @return [Number] the max interval between pings from a server.
50
+ attr_accessor :server_ping_interval_max
26
51
 
27
52
  # Check whether or not connection is established.
28
- def connected?; @connection and @connection.established? end
53
+ def connected?
54
+ @connection&.established?
55
+ end
29
56
 
30
57
  # Get the remote hostname.
31
58
  #
32
59
  # @return [String] the remote hostname.
33
- def host; @options[:hostname] end
60
+ def host
61
+ @options['hostname']
62
+ end
34
63
 
35
64
  # Get the remote port.
36
65
  # If no port is specified, it returns 6697 if using a secure connection,
37
66
  # returns 6667 otherwise.
38
67
  #
39
68
  # @return [Fixnum] the remote port
40
- def port; @options[:port] ||= secure? ? 6697 : 6667 end
41
-
69
+ def port
70
+ @options['port'] ||= secure? ? 6697 : 6667
71
+ end
72
+
42
73
  # Check to see if it's a secure connection.
43
- def secure?; @options[:secure] == true end
74
+ def secure?
75
+ @options['secure'] == true
76
+ end
44
77
 
45
- # Check to see if FiSH encryption is enabled.
46
- def fish?; not @options[:fish].nil? end
78
+ # @return [Boolean] whether we want to authenticate with SASL.
79
+ def sasl?
80
+ @options['sasl'] &&
81
+ @options['sasl']['username'] &&
82
+ @options['sasl']['password']
83
+ end
47
84
 
48
85
  # Instantiates the network.
49
86
  #
@@ -54,51 +91,64 @@ module Blur
54
91
  # @option options [optional, String] :username (Copies :nickname)
55
92
  # The username to use. This is also known as the ident.
56
93
  # @option options [optional, String] :realname (Copies :username)
57
- # The real name that we want to use. This is usually what shows up
94
+ # The "real name" that we want to use. This is usually what shows up
58
95
  # as "Name" when you whois a user.
59
96
  # @option options [optional, String] :password The password for the network.
60
97
  # This is sometimes needed for private networks.
61
- # @option options [optional, Fixnum] :port (6697 if ssl, otherwise 6667)
98
+ # @option options [optional, Fixnum] :port (6697 if ssl, otherwise 6667)
62
99
  # The remote port we want to connect to.
63
100
  # @option options [optional, Boolean] :secure Set whether this is a secure
64
101
  # (SSL-encrypted) connection.
65
102
  # @option options [optional, String] :ssl_cert_file Local path of a
66
103
  # readable file that contains a X509 CA certificate to validate against.
67
- # @option options [optional, String] :ssl_fingerprint Validate that the
104
+ # @option options [optional, String] :ssl_fingerprint Validate that the
68
105
  # remote certificate matches the specified fingerprint.
69
106
  # @option options [optional, Boolean] :ssl_no_verify Disable verification
70
107
  # alltogether.
71
- def initialize options
72
- @options = options
73
- @channels = []
108
+ def initialize options, client = nil
109
+ @client = client
110
+ @options = options
111
+ # @log = ::Logging.logger[self]
112
+ @users = {}
113
+ @channels = {}
74
114
  @isupport = ISupport.new self
75
-
76
- unless options[:nickname]
77
- raise ArgumentError, "nickname is missing from the networks option block"
115
+ @capabilities = []
116
+ @reconnect_interval = 3
117
+ @server_ping_interval_max = @options.fetch('server_ping_interval',
118
+ 150).to_i
119
+
120
+ unless options['nickname']
121
+ raise ArgumentError, 'Network configuration for ' \
122
+ "`#{id}' is missing a nickname"
78
123
  end
79
-
80
- @options[:username] ||= @options[:nickname]
81
- @options[:realname] ||= @options[:username]
82
- @options[:channels] ||= []
124
+
125
+ @nickname = options['nickname']
126
+ @options['username'] ||= @options['nickname']
127
+ @options['realname'] ||= @options['username']
128
+ @options['channels'] ||= []
129
+ @id = options.fetch 'id', "#{host}:#{port}"
83
130
  end
84
-
131
+
85
132
  # Send a message to a recipient.
86
133
  #
87
134
  # @param [String, #to_s] recipient the recipient.
88
135
  # @param [String] message the message.
89
136
  def say recipient, message
90
- if recipient.is_a? Channel and recipient.encrypted?
91
- message = "+OK #{recipient.encryption.encrypt message}"
92
- end
93
-
94
137
  transmit :PRIVMSG, recipient.to_s, message
95
138
  end
96
-
139
+
140
+ # Forwards the received message to the client instance.
141
+ #
97
142
  # Called when the network connection has enough data to form a command.
98
- def got_command command
99
- @delegate.got_command self, command
143
+ def got_message message
144
+ @client.got_message self, message
145
+ rescue StandardError => e
146
+ puts "#{e.class}: #{e.message}"
147
+ puts
148
+ puts '---'
149
+ puts e.backtrace
100
150
  end
101
-
151
+
102
152
  # Find a channel by its name.
103
153
  #
104
154
  # @param [String] name the channel name.
@@ -106,7 +156,7 @@ module Blur
106
156
  def channel_by_name name
107
157
  @channels.find { |channel| channel.name == name }
108
158
  end
109
-
159
+
110
160
  # Find all instances of channels in which there is a user with the nick
111
161
  # +nick+.
112
162
  #
@@ -120,65 +170,153 @@ module Blur
120
170
  #
121
171
  # @return [Array<String>] a list of user prefixes.
122
172
  def user_prefixes
123
- isupport["PREFIX"].values
173
+ isupport['PREFIX'].values
124
174
  end
125
175
 
126
176
  # Returns a list of user modes that also gives a users nick a prefix.
127
177
  #
128
178
  # @return [Array<String>] a list of user modes.
129
179
  def user_prefix_modes
130
- isupport["PREFIX"].keys
180
+ isupport['PREFIX'].keys
131
181
  end
132
182
 
133
183
  # Returns a list of channel flags (channel mode D).
134
184
  #
135
185
  # @return [Array<String>] a list of channel flags.
136
186
  def channel_flags
137
- isupport["CHANMODES"]["D"]
187
+ isupport['CHANMODES']['D']
138
188
  end
139
189
 
140
190
  # Attempt to establish a connection and send initial data.
141
191
  #
142
192
  # @see Connection
143
193
  def connect
144
- @connection = EventMachine.connect host, port, Connection, self
194
+ # @log.info "Connecting to #{self}"
195
+
196
+ begin
197
+ @connection = EventMachine.connect host, port, Connection, self
198
+ rescue EventMachine::ConnectionError => e
199
+ warn "Establishing connection to #{self} failed!"
200
+ warn e.message
201
+
202
+ schedule_reconnect
203
+ return
204
+ end
205
+
206
+ @ping_timer = EventMachine.add_periodic_timer DEFAULT_PING_INTERVAL do
207
+ periodic_ping_check
208
+ end
209
+ end
210
+
211
+ # Schedules a reconnect after a user-specified number of seconds.
212
+ def schedule_reconnect
213
+ # @log.info "Reconnecting to #{self} in #{@reconnect_interval} seconds"
214
+
215
+ EventMachine.add_timer @reconnect_interval do
216
+ connect
217
+ end
218
+ end
219
+
220
+ def server_connection_timeout
221
+ @connection.close_connection
222
+
223
+ warn "Connection to #{self} timed out"
224
+ end
225
+
226
+ def periodic_ping_check
227
+ now = Time.now
228
+ seconds_since_pong = now - @last_pong_time
229
+
230
+ return unless seconds_since_pong >= @server_ping_interval_max
231
+
232
+ # @log.info "No PING request from the server in #{seconds_since_pong}s!"
233
+
234
+ transmit 'PING', now.to_s
235
+
236
+ # Wait 15 seconds and declare a timeout if we didn't get a PONG.
237
+ previous_pong_time = @last_pong_time.dup
238
+
239
+ EventMachine.add_timer 15 do
240
+ server_connection_timeout if @last_pong_time == previous_pong_time
241
+ end
145
242
  end
146
243
 
147
244
  # Called when the connection was successfully established.
148
245
  def connected!
149
- transmit :PASS, @options[:password] if @options[:password]
150
- transmit :NICK, @options[:nickname]
151
- transmit :USER, @options[:username], :void, :void, @options[:realname]
246
+ @waiting_for_cap = true
247
+ @capabilities.clear
248
+
249
+ transmit :CAP, 'LS'
250
+ transmit :PASS, @options['password'] if @options['password']
251
+ transmit :NICK, @options['nickname']
252
+ transmit :USER, @options['username'], 'void', 'void', @options['realname']
253
+
254
+ @last_pong_time = Time.now
255
+ end
256
+
257
+ # Called when the server doesn't support capability negotiation.
258
+ def abort_cap_neg
259
+ @waiting_for_cap = false
260
+
261
+ puts 'Server does not support capability negotiation'
262
+ end
263
+
264
+ # Called when we're done with capability negotiation.
265
+ def cap_end
266
+ @waiting_for_cap = false
267
+
268
+ transmit :CAP, 'END'
152
269
  end
153
270
 
154
271
  # Called when the connection was closed.
155
272
  def disconnected!
156
- @channels.each { |channel| channel.users.clear }
273
+ @channels.each { |_, channel| channel.users.clear }
157
274
  @channels.clear
275
+ @users.clear
276
+ @ping_timer.cancel
277
+
278
+ # @log.debug "Connection to #{self} lost!"
279
+ @client.network_connection_closed self
158
280
 
159
- @delegate.network_connection_closed self
281
+ return unless @options.fetch('reconnect', DEFAULT_RECONNECT)
282
+
283
+ schedule_reconnect
160
284
  end
161
-
285
+
162
286
  # Terminate the connection and clear all channels and users.
163
287
  def disconnect
164
288
  @connection.close_connection_after_writing
165
289
  end
166
-
290
+
167
291
  # Transmit a command to the server.
168
292
  #
169
293
  # @param [Symbol, String] name the command name.
170
294
  # @param [...] arguments all the prepended parameters.
171
295
  def transmit name, *arguments
172
- command = Command.new name, arguments
173
- log "#{'→' ^ :red} #{command.name.to_s.ljust(8, ' ') ^ :light_gray} #{command.params.map(&:inspect).join ' '}"
174
-
175
- @connection.send_data "#{command}\r\n"
296
+ message = IRCParser::Message.new command: name.to_s, parameters: arguments
297
+
298
+ if @client.verbose
299
+ formatted_command = message.command.to_s.ljust 8, ' '
300
+ formatted_params = message.parameters.map(&:inspect).join ' '
301
+ puts "→ #{formatted_command} #{formatted_params}"
302
+ end
303
+
304
+ @connection.send_data "#{message}\r\n"
305
+ end
306
+
307
+ # Send a private message.
308
+ def send_privmsg recipient, message
309
+ transmit :PRIVMSG, recipient, message
310
+ end
311
+
312
+ # Join a channel.
313
+ def join channel
314
+ transmit :JOIN, channel
176
315
  end
177
316
 
178
-
179
317
  # Convert it to a debug-friendly format.
180
318
  def to_s
181
- %{#<#{self.class.name} "#{host}":#{port}>}
319
+ %(#<#{self.class.name} "#{host}":#{port}>)
182
320
  end
183
321
  end
184
322
  end