tcp-server 1.2.0-java → 1.4.1-java

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.
@@ -6,17 +6,19 @@
6
6
 
7
7
  # =begin
8
8
  #
9
- # Copyright Nels Nelson 2016-2024 but freely usable (see license)
9
+ # Copyright Nels Nelson 2016-2026 but freely usable (see license)
10
10
  #
11
11
  # =end
12
12
 
13
13
  require 'java'
14
14
  require 'netty'
15
15
 
16
+ java_import 'io.netty.handler.codec.Delimiters'
17
+
16
18
  require 'logger'
17
19
 
18
- # The Server module
19
- module Server
20
+ # The TcpServer module
21
+ module TcpServer
20
22
  DEFAULT_LOG_LEVEL = Logger::INFO
21
23
  DEFAULT_HOST = '0.0.0.0'.freeze
22
24
  DEFAULT_PORT = 8080
@@ -26,7 +28,7 @@ module Server
26
28
  DEFAULT_KEEP_ALIVE = false
27
29
  DEFAULT_MAX_QUEUED_INCOMING_CONNECTIONS = 100
28
30
  DEFAULT_MAX_FRAME_LENGTH = 8192
29
- DEFAULT_DELIMITER = Java::io.netty.handler.codec.Delimiters.lineDelimiter
31
+ DEFAULT_DELIMITER = Delimiters.lineDelimiter
30
32
  DEFAULT_LOG_REQUESTS = false
31
33
  DEFAULT_QUIT_COMMANDS = %i[
32
34
  bye cease desist exit leave quit stop terminate
@@ -35,6 +37,7 @@ module Server
35
37
  # rubocop: disable Metrics/MethodLength
36
38
  def server_config
37
39
  @server_config ||= {
40
+ app_name: 'tcp_server',
38
41
  host: DEFAULT_HOST,
39
42
  port: DEFAULT_PORT,
40
43
  ssl: DEFAULT_SSL_ENABLED,
@@ -52,3 +55,4 @@ module Server
52
55
  module_function :server_config
53
56
  # rubocop: enable Metrics/MethodLength
54
57
  end
58
+ # end TcpServer
@@ -6,16 +6,16 @@
6
6
 
7
7
  # =begin
8
8
  #
9
- # Copyright Nels Nelson 2016-2024 but freely usable (see license)
9
+ # Copyright Nels Nelson 2016-2026 but freely usable (see license)
10
10
  #
11
11
  # =end
12
12
 
13
- # The Server module
14
- module Server
13
+ # The TcpServer module
14
+ module TcpServer
15
15
  # The DefaultHandler module
16
16
  module DefaultHandler
17
17
  def channel_active(ctx)
18
- ::Server.log.info "Channel active: #{ctx.channel}"
18
+ TcpServer.log.info "Channel active: #{ctx.channel}"
19
19
  response = 'Hello, world!'
20
20
  log.trace "Sending response: #{response.inspect}"
21
21
  ctx.channel.writeAndFlush("#{response}\n")
@@ -24,7 +24,7 @@ module Server
24
24
  # rubocop: disable Metrics/AbcSize
25
25
  def message_received(ctx, msg)
26
26
  return if msg.nil?
27
- msg.chomp! if msg.respond_to?(:chomp!)
27
+ msg = msg.chomp if msg.respond_to?(:chomp)
28
28
  return if msg.respond_to?(:empty?) && msg.empty?
29
29
  log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
30
30
  return ctx.close() if @options[:quit_commands].include?(msg.downcase.to_sym)
@@ -35,9 +35,10 @@ module Server
35
35
  # rubocop: enable Metrics/AbcSize
36
36
 
37
37
  def exception_caught(_ctx, cause)
38
- ::Server.log.error "Exception caught: #{cause}"
38
+ TcpServer.log.error "Exception caught: #{cause}"
39
39
  cause.backtrace.each { |t| ::Server.log.error t }
40
40
  ctx.close()
41
41
  end
42
42
  end
43
43
  end
44
+ # end TcpServer
@@ -6,17 +6,17 @@
6
6
 
7
7
  # =begin
8
8
  #
9
- # Copyright Nels Nelson 2016-2024 but freely usable (see license)
9
+ # Copyright Nels Nelson 2016-2026 but freely usable (see license)
10
10
  #
11
11
  # =end
12
12
 
13
- require_relative 'server/default_handler'
13
+ require_relative 'default_handler'
14
14
 
15
- # The Server module
16
- module Server
15
+ # The TcpServer module
16
+ module TcpServer
17
17
  # The Demo class
18
18
  class Demo
19
- include Server::DefaultHandler
19
+ include TcpServer::DefaultHandler
20
20
 
21
21
  def initialize(options = {})
22
22
  @options = options
@@ -6,54 +6,61 @@
6
6
 
7
7
  # =begin
8
8
  #
9
- # Copyright Nels Nelson 2016-2024 but freely usable (see license)
9
+ # Copyright Nels Nelson 2016-2026 but freely usable (see license)
10
10
  #
11
11
  # =end
12
12
 
13
13
  require 'java'
14
14
  require 'netty'
15
15
 
16
- require_relative 'channel_initializer'
16
+ java_import 'io.netty.bootstrap.ServerBootstrap'
17
+ java_import 'io.netty.channel.ChannelOption'
18
+ java_import 'io.netty.channel.group.DefaultChannelGroup'
19
+ java_import 'io.netty.channel.nio.NioEventLoopGroup'
20
+ java_import 'io.netty.util.concurrent.GlobalEventExecutor'
21
+ java_import 'io.netty.handler.logging.LoggingHandler'
22
+ java_import 'io.netty.handler.logging.LogLevel'
23
+
24
+ require_relative 'channelizer'
17
25
  require_relative 'default_handler'
18
- require_relative 'shutdown_hook'
19
-
20
- # The Server module
21
- module Server
22
- java_import Java::io.netty.bootstrap.ServerBootstrap
23
- java_import Java::io.netty.channel.ChannelOption
24
- java_import Java::io.netty.channel.group.DefaultChannelGroup
25
- java_import Java::io.netty.channel.nio.NioEventLoopGroup
26
- java_import Java::io.netty.handler.logging.LogLevel
27
- java_import Java::io.netty.handler.logging.LoggingHandler
28
- java_import Java::io.netty.util.concurrent.GlobalEventExecutor
29
26
 
27
+ # The TcpServer module
28
+ module TcpServer
30
29
  # The InstanceMethods module
31
30
  module InstanceMethods
32
- include ::Server::DefaultHandler
31
+ include TcpServer::DefaultHandler
33
32
 
34
33
  # rubocop: disable Metrics/AbcSize
35
34
  def bootstrap
36
35
  @bootstrap = ServerBootstrap.new
37
36
  @bootstrap.group(boss_group, worker_group)
38
37
  @bootstrap.channel(channel_type)
39
- @bootstrap.option(ChannelOption::SO_BACKLOG, max_queued_incoming_connections)
40
- @bootstrap.childOption(ChannelOption::SO_KEEPALIVE, keep_alive) if keep_alive
38
+ @bootstrap.option(backlog_option, max_queued_incoming_connections)
39
+ @bootstrap.childOption(keep_alive_option, keep_alive) if keep_alive
41
40
  @bootstrap.handler(logging_handler) if options.fetch(:log_requests, false)
42
- @bootstrap.childHandler(channel_initializer)
41
+ @bootstrap.childHandler(channelizer)
43
42
  end
44
43
  # rubocop: enable Metrics/AbcSize
45
44
 
45
+ def backlog_option
46
+ ChannelOption::SO_BACKLOG
47
+ end
48
+
49
+ def keep_alive_option
50
+ ChannelOption::SO_KEEPALIVE
51
+ end
52
+
46
53
  def configure_handlers(*handlers, &block)
47
- channel_initializer << block if block_given?
54
+ channelizer << block if block_given?
48
55
  add_listener(*handlers)
49
56
  end
50
57
 
51
58
  def channel_type
52
- @channel_type ||= Server::CHANNEL_TYPE
59
+ @channel_type ||= TcpServer::Server::CHANNEL_TYPE
53
60
  end
54
61
 
55
- def channel_initializer
56
- @channel_initializer ||= ::Server::ChannelInitializer.new(channel_group, @options)
62
+ def channelizer
63
+ @channelizer ||= TcpServer::Channelizer.new(channel_group, @options)
57
64
  end
58
65
 
59
66
  def boss_group
@@ -65,21 +72,30 @@ module Server
65
72
  end
66
73
 
67
74
  def channel_group
68
- @channel_group ||= DefaultChannelGroup.new('server_channels', GlobalEventExecutor::INSTANCE)
75
+ @channel_group ||= DefaultChannelGroup.new(
76
+ 'server_channels', GlobalEventExecutor::INSTANCE)
69
77
  end
70
78
 
71
79
  def logging_handler
72
80
  @logging_handler ||= LoggingHandler.new(LogLevel::INFO)
73
81
  end
74
82
 
83
+ def server_channel(port = self.port)
84
+ @server_channel ||= bootstrap.bind(port).sync().channel()
85
+ end
86
+
75
87
  # rubocop: disable Metrics/AbcSize
76
88
  # rubocop: disable Metrics/MethodLength
77
89
  def run(port = self.port)
78
- channel = bootstrap.bind(port).sync().channel()
90
+ channel = server_channel(port)
79
91
  channel_group.add(channel)
80
- ::Server::ShutdownHook.new(self)
92
+ Signal.trap('INT') { handle_interrupt }
93
+ Signal.trap('TERM') { handle_interrupt }
81
94
  log.info "Listening on #{channel.local_address}"
82
95
  channel.closeFuture().sync()
96
+ rescue Interrupt => _e
97
+ $stdout.write "\r\e[0K"
98
+ $stdout.flush
83
99
  rescue java.lang.IllegalArgumentException => e
84
100
  raise "Invalid argument: #{e.message}"
85
101
  rescue java.net.BindException => e
@@ -96,26 +112,49 @@ module Server
96
112
  @port ||= (@options[:port] || DEFAULT_PORT).to_i
97
113
  end
98
114
 
115
+ def handle_interrupt
116
+ $stdout.write "\r\e[0K"
117
+ $stdout.flush
118
+ shutdown
119
+ end
120
+
99
121
  def shutdown
100
- channel_group.disconnect().awaitUninterruptibly()
101
- channel_group.close().awaitUninterruptibly()
122
+ logger.info 'Shutting down'
123
+ quietly do
124
+ server_channel&.close()
125
+ channel_group.disconnect().awaitUninterruptibly()
126
+ channel_group.close().awaitUninterruptibly()
127
+ end
128
+ rescue StandardError => e
129
+ warn e.message
130
+ $stderr.flush
131
+ end
132
+
133
+ def quietly(preserved_verbose = $VERBOSE)
134
+ $VERBOSE = nil
135
+ yield
136
+ ensure
137
+ $VERBOSE = preserved_verbose
102
138
  end
103
139
 
104
140
  def stop
105
141
  boss_group&.shutdownGracefully()
106
142
  worker_group&.shutdownGracefully()
143
+ rescue StandardError => e
144
+ warn e.message
145
+ $stderr.flush
107
146
  end
108
147
 
109
148
  def <<(handler)
110
- channel_initializer << handler
149
+ channelizer << handler
111
150
  end
112
151
 
113
152
  def add_listener(*listener)
114
- channel_initializer.add_listener(*listener)
153
+ channelizer.add_listener(*listener)
115
154
  end
116
155
 
117
156
  def replace_listeners(*listener)
118
- channel_initializer.replace_listeners(*listener)
157
+ channelizer.replace_listeners(*listener)
119
158
  end
120
159
 
121
160
  def keep_alive
@@ -133,6 +172,6 @@ module Server
133
172
  end
134
173
  end
135
174
  end
136
- # module ServerInstanceMethods
175
+ # module InstanceMethods
137
176
  end
138
- # module Server
177
+ # module TcpServer
@@ -6,14 +6,14 @@
6
6
 
7
7
  # =begin
8
8
  #
9
- # Copyright Nels Nelson 2016-2024 but freely usable (see license)
9
+ # Copyright Nels Nelson 2016-2026 but freely usable (see license)
10
10
  #
11
11
  # =end
12
12
 
13
13
  require 'java'
14
14
 
15
- # The Server module
16
- module Server
15
+ # The TcpServer module
16
+ module TcpServer
17
17
  # The Listenable module
18
18
  module Listenable
19
19
  def listeners
@@ -45,4 +45,4 @@ module Server
45
45
  end
46
46
  # module Listenable
47
47
  end
48
- # module Server
48
+ # module TcpServer
@@ -0,0 +1,310 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ def jruby?
5
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' && defined?(Java)
6
+ end
7
+
8
+ if jruby?
9
+ require 'java'
10
+ require 'apache-log4j-2'
11
+
12
+ java_import 'org.apache.logging.log4j.Level'
13
+ java_import 'org.apache.logging.log4j.core.appender.ConsoleAppender'
14
+ java_import 'org.apache.logging.log4j.core.config.builder.api.' \
15
+ 'ConfigurationBuilderFactory'
16
+ java_import 'org.apache.logging.log4j.core.config.Configurator'
17
+ java_import 'org.apache.logging.log4j.core.config.LoggerConfig'
18
+ java_import 'org.apache.logging.log4j.core.LoggerContext'
19
+ java_import 'org.apache.logging.log4j.LogManager'
20
+ java_import 'org.apache.logging.log4j.ThreadContext'
21
+ end
22
+
23
+ require 'fileutils'
24
+ require 'logger'
25
+
26
+ # The Logging module
27
+ module Logging
28
+ DEFAULT_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'.freeze
29
+ DEFAULT_APP_NAME = 'app'.freeze
30
+ DEFAULT_LOG_PATTERN = '%d{ABSOLUTE} %-5p [%X{category}] %m%n'.freeze
31
+ DEFAULT_RUBY_LOG_FORMAT =
32
+ "%<timestamp>s %-5<severity>s %<category>s] %<msg>s\n".freeze
33
+ EMPTY_STRING = ''.freeze
34
+
35
+ module_function
36
+
37
+ def backend
38
+ @backend ||= init_backend
39
+ end
40
+
41
+ def init_backend
42
+ return init_log4j_backend if jruby?
43
+ init_logger_backend
44
+ end
45
+
46
+ def init_logger_backend
47
+ log = Logger.new($stdout)
48
+ log.formatter = method(:ruby_formatter)
49
+ @backend = log
50
+ apply_backend_level(log_level)
51
+ log
52
+ end
53
+
54
+ def init_log4j_backend
55
+ init_log4j2!
56
+ apply_backend_level(log_level)
57
+ LogManager.getLogger(app_logger_name)
58
+ end
59
+
60
+ def proxy
61
+ @proxy ||= Proxy.new
62
+ end
63
+
64
+ def app_name
65
+ DEFAULT_APP_NAME
66
+ end
67
+
68
+ def app_logger_name
69
+ DEFAULT_APP_NAME
70
+ end
71
+
72
+ def time_format
73
+ DEFAULT_TIME_FORMAT
74
+ end
75
+
76
+ def ruby_log_format
77
+ DEFAULT_RUBY_LOG_FORMAT
78
+ end
79
+
80
+ def java_log_pattern
81
+ DEFAULT_LOG_PATTERN
82
+ end
83
+
84
+ def ruby_formatter(severity, datetime, _progname, msg)
85
+ category = (Thread.current[:logging_category] || app_name).to_s
86
+ timestamp = datetime.strftime(time_format)
87
+ format(ruby_log_format, timestamp: timestamp, severity: severity,
88
+ category: category, msg: msg)
89
+ end
90
+
91
+ def init_log4j2!
92
+ return if @log4j2_initialized
93
+
94
+ Java::java.lang.System.setProperty(
95
+ 'log4j.shutdownHookEnabled', Java::java.lang.Boolean.toString(false))
96
+
97
+ configure_log4j
98
+
99
+ @log4j2_initialized = true
100
+ end
101
+
102
+ def configure_log4j
103
+ @log4j2_context = Configurator.initialize(log4j_configuration.build)
104
+ @log4j2_context.updateLoggers
105
+ end
106
+
107
+ # rubocop: disable Metrics/AbcSize
108
+ # rubocop: disable Metrics/MethodLength
109
+ def log4j_configuration(
110
+ config = ConfigurationBuilderFactory.newConfigurationBuilder)
111
+ layout = config.newLayout('PatternLayout')
112
+ .addAttribute('pattern', java_log_pattern)
113
+ console = config.newAppender('stdout', 'CONSOLE')
114
+ .addAttribute('target', ConsoleAppender::Target::SYSTEM_OUT).add(layout)
115
+ config.add(console)
116
+ root = config.newRootLogger(Level::INFO)
117
+ .add(config.newAppenderRef('stdout'))
118
+ config.add(root)
119
+ app_logger = config.newLogger(app_logger_name, Level::INFO)
120
+ .add(config.newAppenderRef('stdout'))
121
+ .addAttribute('additivity', false)
122
+ config.add(app_logger)
123
+ config
124
+ end
125
+ # rubocop: enable Metrics/AbcSize
126
+ # rubocop: enable Metrics/MethodLength
127
+
128
+ def derive_category(receiver, callsite)
129
+ if receiver.is_a?(Module)
130
+ receiver.name.to_s
131
+ elsif receiver.respond_to?(:class) && receiver.class.respond_to?(:name)
132
+ receiver.class.name.to_s
133
+ else
134
+ EMPTY_STRING
135
+ end.tap do |cat|
136
+ return cat unless cat.empty?
137
+ end
138
+
139
+ File.basename(callsite.path.to_s)
140
+ end
141
+
142
+ def dispatch(level, *args, &block)
143
+ if jruby?
144
+ backend.public_send(level, *args, &block)
145
+ else
146
+ case level
147
+ when :fatal then backend.fatal(*args, &block)
148
+ when :warn then backend.warn(*args, &block)
149
+ else
150
+ backend.public_send(level, *args, &block)
151
+ end
152
+ end
153
+ end
154
+
155
+ # rubocop: disable Metrics/MethodLength
156
+ def with_category(category)
157
+ if jruby?
158
+ prev = ThreadContext.get('category')
159
+
160
+ begin
161
+ ThreadContext.put('category', category)
162
+ yield
163
+ ensure
164
+ if prev.nil?
165
+ ThreadContext.remove('category')
166
+ else
167
+ ThreadContext.put('category', prev)
168
+ end
169
+ end
170
+ else
171
+ prev = Thread.current[:logging_category]
172
+
173
+ begin
174
+ Thread.current[:logging_category] = category
175
+ yield
176
+ ensure
177
+ Thread.current[:logging_category] = prev
178
+ end
179
+ end
180
+ end
181
+ # rubocop: enable Metrics/MethodLength
182
+
183
+ NUMERIC_TO_SYMBOL = {
184
+ 5 => :off,
185
+ 4 => :fatal,
186
+ 3 => :error,
187
+ 2 => :warn,
188
+ 1 => :info,
189
+ 0 => :debug,
190
+ -1 => :trace
191
+ }.freeze
192
+
193
+ SYMBOL_TO_RUBY = {
194
+ off: Logger::FATAL,
195
+ fatal: Logger::FATAL,
196
+ error: Logger::ERROR,
197
+ warn: Logger::WARN,
198
+ info: Logger::INFO,
199
+ debug: Logger::DEBUG,
200
+ trace: Logger::DEBUG,
201
+ all: Logger::DEBUG
202
+ }.freeze
203
+
204
+ def log_level
205
+ @log_level ||= :info
206
+ end
207
+
208
+ def log_level=(level)
209
+ @log_level = case level
210
+ when Integer
211
+ int_to_symbol_level(level)
212
+ when Symbol, String
213
+ level.to_s.downcase.to_sym
214
+ else
215
+ :info
216
+ end
217
+
218
+ apply_backend_level(@log_level)
219
+ end
220
+
221
+ def apply_backend_level(sym)
222
+ if jruby?
223
+ ctx = @log4j2_context || LoggerContext.getContext(false)
224
+ app_cfg = app_config(ctx.getConfiguration, sym)
225
+ app_cfg.setLevel(symbol_to_java_level(sym))
226
+ ctx.updateLoggers
227
+ else
228
+ backend.level = SYMBOL_TO_RUBY.fetch(sym, Logger::INFO)
229
+ end
230
+
231
+ sym
232
+ end
233
+
234
+ def app_config(context_configuration, sym)
235
+ app_cfg = context_configuration.getLoggerConfig(app_logger_name)
236
+ return app_cfg if !app_cfg.nil? && app_cfg.getName == app_logger_name
237
+
238
+ init_app_logging_config(context_configuration, app_logger_name, sym)
239
+ end
240
+
241
+ def init_app_logging_config(context_configuration, app_logger_name, sym)
242
+ app_cfg = LoggerConfig.newBuilder.withConfig(context_configuration)
243
+ .withLoggerName(app_logger_name).withLevel(symbol_to_java_level(sym))
244
+ .withAdditivity(false).build
245
+ if (appender = context_configuration.getAppender('stdout')).nil?
246
+ app_cfg.setAdditive(true)
247
+ else
248
+ app_cfg.addAppender(appender, Level::INFO, nil)
249
+ end
250
+ context_configuration.addLogger(app_logger_name, app_cfg)
251
+ app_cfg
252
+ end
253
+
254
+ def int_to_symbol_level(level)
255
+ NUMERIC_TO_SYMBOL.fetch(level) do
256
+ level > 5 ? :off : :all
257
+ end
258
+ end
259
+
260
+ # rubocop: disable Metrics/CyclomaticComplexity
261
+ def symbol_to_java_level(sym)
262
+ case sym
263
+ when :off then Level::OFF
264
+ when :fatal then Level::FATAL
265
+ when :error then Level::ERROR
266
+ when :warn then Level::WARN
267
+ when :debug then Level::DEBUG
268
+ when :trace then Level::TRACE
269
+ when :all then Level::ALL
270
+ else Level::INFO
271
+ end
272
+ end
273
+ # rubocop: enable Metrics/CyclomaticComplexity
274
+ end
275
+
276
+ # The Logging class
277
+ module Logging
278
+ # The Proxy class
279
+ class Proxy
280
+ SEVERITIES = %i[trace debug info warn error fatal].freeze
281
+
282
+ def method_missing(method_name, *args, &block)
283
+ return super unless SEVERITIES.include?(method_name)
284
+
285
+ receiver = Thread.current[:logging_receiver]
286
+ callsite = Thread.current[:logging_callsite] || caller_locations(2, 1).first
287
+ category = Logging.derive_category(receiver, callsite)
288
+
289
+ Logging.with_category(category) do
290
+ Logging.dispatch(method_name, *args, &block)
291
+ end
292
+ end
293
+
294
+ def respond_to_missing?(method_name, include_private = false)
295
+ SEVERITIES.include?(method_name) || super
296
+ end
297
+ end
298
+ end
299
+
300
+ # Make `log` and `logger` available everywhere, capturing receiver + callsite.
301
+ module LoggingInjection
302
+ def logger
303
+ Thread.current[:logging_receiver] = self
304
+ Thread.current[:logging_callsite] = caller_locations(1, 1).first
305
+ Logging.proxy
306
+ end
307
+ alias log logger
308
+ end
309
+
310
+ Object.include(LoggingInjection)
@@ -6,19 +6,19 @@
6
6
 
7
7
  # =begin
8
8
  #
9
- # Copyright Nels Nelson 2016-2024 but freely usable (see license)
9
+ # Copyright Nels Nelson 2016-2026 but freely usable (see license)
10
10
  #
11
11
  # =end
12
12
 
13
13
  require 'java'
14
14
  require 'netty'
15
15
 
16
- # The Server module
17
- module Server
18
- java_import Java::io.netty.channel.ChannelFutureListener
19
- java_import Java::io.netty.channel.ChannelHandler
20
- java_import Java::io.netty.channel.SimpleChannelInboundHandler
16
+ java_import 'io.netty.channel.ChannelHandler'
17
+ java_import 'io.netty.channel.ChannelFutureListener'
18
+ java_import 'io.netty.channel.SimpleChannelInboundHandler'
21
19
 
20
+ # The TcpServer module
21
+ module TcpServer
22
22
  # The MessageHandler class
23
23
  class MessageHandler < SimpleChannelInboundHandler
24
24
  include ChannelHandler
@@ -40,9 +40,9 @@ module Server
40
40
 
41
41
  def messageReceived(ctx, msg)
42
42
  log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
43
- msg&.chomp!
43
+ msg = msg&.chomp
44
44
  log.info "Received message: #{msg}"
45
- return super(ctx, msg) unless respond_to?(:handle_message) && @handler&.arity == 2
45
+ return super unless respond_to?(:handle_message) && @handler&.arity == 2
46
46
  handle_message(ctx, msg)
47
47
  end
48
48