tcp-server 1.0.6-java → 1.0.9-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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adb40677b97f1b1ce0fb1fad9308d527d3086c927044f5e71d1e378c5e0d8e25
4
- data.tar.gz: 7e489022262138faae081772672bb99ca5eef415f943ff2b9cf96120390cb984
3
+ metadata.gz: c707938b3d92a5bd20d128d6f09bfcbbc50ff56f0d4c34006c40b27815b299c3
4
+ data.tar.gz: 01bb7897ee596c304ff32bf6dbb2ac5b380bc9f45cad86d0f39479515a4f07b5
5
5
  SHA512:
6
- metadata.gz: 1d8afb5280d5a8bfe5dd234dcb5f0dfb26558543074b917de3c39bd08cd5331319a32bd764400d7183fa8814d8b978a57532306dfb2b7464c28cc58aacc5c732
7
- data.tar.gz: 8a6c94c3ee130ea33a8c4e95009b75414d9622916b4277a38664f3f08d3f238d787941c9c0e7ed5c566a2c3efd2b2d064dda83f0cdce513fd95036c08c959e09
6
+ metadata.gz: 8cff7fdc5fbae7e1e7521bda5cd6b335a3c7f7822e1362b5a284d62a5a81b71f466dd52519c2c6f12389eb425fa262fc066ec0b756bf502391ae04841b581e37
7
+ data.tar.gz: bc42f07bfe23e91f9626de066f233ace69987e624950ed81d7724ee2ec8e5095d2475446f58a7a5b8d35c123b9201c98abf5a7a8d11fb6cb01636313c0584532
data/README.md CHANGED
@@ -18,7 +18,9 @@ You may run the websocket server in a container. The [`colima`](https://github.c
18
18
 
19
19
  ```sh
20
20
  colima start
21
- docker-compose up
21
+ docker-compose up --detach
22
+ nc localhost 4000
23
+ docker-compose down
22
24
  ```
23
25
 
24
26
  Building the image or running the container:
@@ -57,8 +59,7 @@ Download and install the latest version of [JRuby].
57
59
 
58
60
  ```sh
59
61
  asdf plugin add ruby
60
- pushd "${HOME}/.asdf/plugins/ruby/ruby-build"; git fetch origin; git pull origin master; popd
61
- # ~/.asdf/plugins/ruby/bin/list-all
62
+ asdf plugin update --all
62
63
  asdf list all ruby
63
64
  asdf install ruby jruby-9.3.4.0
64
65
  ```
@@ -123,7 +124,7 @@ Here is a bird's-eye view of the project layout.
123
124
 
124
125
  ```sh
125
126
  # date && tree -I "logs|vendor|tmp"
126
- Thu Jun 16 00:03:23 CDT 2022
127
+ Wed Jul 20 13:18:23 CDT 2022
127
128
  .
128
129
  ├── Dockerfile
129
130
  ├── Gemfile
@@ -157,14 +158,14 @@ Thu Jun 16 00:03:23 CDT 2022
157
158
  │ ├── test_spec.rb
158
159
  │ └── verify
159
160
  │ └── verify_spec.rb
160
- ├── tcp-server-1.0.2-java.gem
161
161
  ├── tcp-server-jruby.gemspec
162
162
  ├── tcp_server.png
163
163
  └── tcp_server.rb
164
164
 
165
- 5 directories, 31 files
165
+ 5 directories, 30 files
166
166
  ```
167
167
 
168
+
168
169
  ## CI linting
169
170
 
170
171
  Use the GitLab CI Linting API to validate the syntax of a CI definition file.
@@ -173,12 +174,13 @@ Use the GitLab CI Linting API to validate the syntax of a CI definition file.
173
174
  jq --null-input --arg yaml "$(<.gitlab/ci/gem.gitlab-ci.yml)" '.content=$yaml' | curl --silent --location https://gitlab.com/api/v4/ci/lint --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" --header "Content-Type: application/json" --data @- | jq --raw-output '.errors[0]'
174
175
  ```
175
176
 
177
+
176
178
  ## CI configuration
177
179
 
178
180
  Generate a deploy key.
179
181
 
180
182
  ```sh
181
- ssh-keygen -t ed25519 -C deploy_key
183
+ ssh-keygen -t ed25519 -P '' -C deploy_key -f deploy_key_ed25519
182
184
  ```
183
185
 
184
186
  Use the GitLab Project-level Variables API to add the deploy key as a ssh private key variable.
@@ -186,18 +188,21 @@ Use the GitLab Project-level Variables API to add the deploy key as a ssh privat
186
188
  ```sh
187
189
  project_path="nelsnelson/$(basename $(pwd))"
188
190
 
191
+ # Test auth token validity
192
+ curl --silent --show-error --location "https://gitlab.com/api/v4/projects" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" | jq '.[0]["id"]'
193
+
189
194
  project=$(curl --silent --show-error --location "https://gitlab.com/api/v4/search?scope=projects&search=${project_path}" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" | jq --arg project_path "${project_path}" '.[] | select(.path_with_namespace == $project_path)')
190
195
 
191
196
  project_id=$(curl --silent --show-error --location "https://gitlab.com/api/v4/search?scope=projects&search=${project_path}" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" | jq --arg project_path "${project_path}" '.[] | select(.path_with_namespace == $project_path) | .id')
192
197
 
193
198
  # Add the deploy_token as a CI variable:
194
- curl --silent --show-error --location --request POST "https://gitlab.com/api/v4/projects/${project_id}/variables" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" --form "key=SSH_PRIVATE_KEY" --form "value=$(cat ./deploy_token)" --form "protected=true" | jq
199
+ curl --silent --show-error --location --request POST "https://gitlab.com/api/v4/projects/${project_id}/variables" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" --form "key=SSH_PRIVATE_KEY" --form "value=$(cat ./deploy_key_ed25519)" --form "protected=true" | jq
195
200
  ```
196
201
 
197
202
  Use the Deploy keys API to add a the public deploy key as a deploy key for the project.
198
203
 
199
204
  ```sh
200
- curl --silent --show-error --location --request POST "https://gitlab.com/api/v4/projects/${project_id}/deploy_keys" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" --data '{"title": "deploy_key", "key": "$(cat ./deploy_token.pub)", "can_push": "true"}' | jq
205
+ curl --silent --show-error --location --request POST "https://gitlab.com/api/v4/projects/${project_id}/deploy_keys" --header "PRIVATE-TOKEN: ${GITLAB_COM_API_PRIVATE_TOKEN}" --form "title=deploy_key" --form "key=$(cat ./deploy_key_ed25519.pub)" --form "can_push=true" | jq
201
206
  ```
202
207
 
203
208
  [license]: https://gitlab.com/nelsnelson/tcp-server-jruby/blob/master/LICENSE
data/lib/client.rb CHANGED
@@ -12,11 +12,12 @@
12
12
  #
13
13
  # =end
14
14
 
15
- require 'optparse'
16
-
17
15
  require 'java'
18
16
  require 'netty'
19
17
 
18
+ require 'logger'
19
+ require 'optparse'
20
+
20
21
  require 'log'
21
22
 
22
23
  # The Client module
@@ -26,16 +27,18 @@ end
26
27
 
27
28
  # The Client module
28
29
  module Client
29
- # The Config module
30
- module Config
31
- DEFAULTS = {
30
+ def client_config
31
+ @client_config ||= {
32
+ host: '0.0.0.0',
32
33
  port: 8080,
33
- host: 'localhost',
34
34
  ssl: false,
35
- quit_commands: %i[bye quit],
36
- log_level: Logger::INFO
35
+ log_level: Logger::INFO,
36
+ quit_commands: %i[bye cease desist exit leave quit stop terminate],
37
+ max_frame_length: 8192,
38
+ delimiter: Java::io.netty.handler.codec.Delimiters.lineDelimiter
37
39
  }.freeze
38
40
  end
41
+ module_function :client_config
39
42
  end
40
43
 
41
44
  # The Client module
@@ -77,8 +80,8 @@ module Client
77
80
  end
78
81
 
79
82
  def configure_handlers(*handlers, &block)
80
- ::Client::ChannelInitializer::DefaultHandler.add_listener(self)
81
- listeners.addAll(handlers)
83
+ channel_initializer.default_handler.add_listener(self)
84
+ channel_initializer.default_handler.listeners.addAll(handlers)
82
85
  @user_app = block
83
86
  @application_handler = lambda do |ctx, msg|
84
87
  if @user_app.nil? || @user_app.arity == 1
@@ -100,7 +103,7 @@ module Client
100
103
  # The InstanceMethods module
101
104
  module InstanceMethods
102
105
  def puts(msg)
103
- sleep 0.1 until @channel.isActive()
106
+ wait_until_channel_is_active
104
107
  msg.chomp!
105
108
  log.trace "#puts msg: #{msg.inspect}"
106
109
  raise 'Message is empty!' if msg.nil? || msg.empty?
@@ -115,6 +118,10 @@ module Client
115
118
  nil
116
119
  end
117
120
 
121
+ def wait_until_channel_is_active(timeout = 5, give_up = Time.now + timeout)
122
+ sleep 0.1 until @channel.active? || Time.now > give_up
123
+ end
124
+
118
125
  def connect(host = @options[:host], port = @options[:port])
119
126
  return unless @channel.nil?
120
127
  # Start the connection attempt.
@@ -143,7 +150,7 @@ module Client
143
150
 
144
151
  def session
145
152
  when_client_has_shut_down(@client_group) do |group|
146
- log.debug "Channel group has shut down: #{group.inspect}"
153
+ log.debug "Channel group has shut down: #{group}"
147
154
  end
148
155
  @user_app.nil? ? read_user_commands : invoke_user_app
149
156
  end
@@ -176,13 +183,13 @@ module Client
176
183
 
177
184
  def channel_unregistered(ctx)
178
185
  log.trace "##{__method__} channel: #{ctx.channel}"
179
- shutdown
186
+ # shutdown
180
187
  end
181
188
 
182
189
  def message_received(ctx, message)
183
190
  notify :message_received, ctx, message
184
191
  if @application_handler.nil?
185
- $stdout.puts message.chomp unless message.nil?
192
+ $stdout.print message.chomp unless message.nil?
186
193
  else
187
194
  @application_handler.call(ctx, message)
188
195
  end
@@ -197,10 +204,11 @@ module Client
197
204
  def read_user_commands
198
205
  log.trace 'Reading user commands'
199
206
  loop do
207
+ log.debug 'Waiting for user input...'
200
208
  input = $stdin.gets
201
209
  raise 'Poll failure from stdin' if input.nil?
202
210
  break unless @channel.active?
203
- break if execute_command(input).is_a?(AbstractChannel::CloseFuture)
211
+ break unless execute_command(input).nil?
204
212
  end
205
213
  end
206
214
 
@@ -222,23 +230,32 @@ module Client
222
230
  @listeners ||= java.util.concurrent.CopyOnWriteArrayList.new
223
231
  end
224
232
 
225
- def add_listener(listener)
226
- listeners << listener
233
+ def add_listener(*listener)
234
+ listeners.addAll(listener)
235
+ ensure
236
+ log.trace "Listeners: #{listeners}"
227
237
  end
228
238
 
229
- def remove_listener(listener)
230
- listeners.delete(listener)
239
+ def remove_listener(*listener)
240
+ listeners.removeAll(listener)
231
241
  end
232
242
 
233
- def notify(message, *args)
234
- return if listeners.empty?
235
- log.trace "Notifying listeners (#{listeners}) of message: #{message}"
243
+ def replace_listeners(*listener)
244
+ listeners.clear
245
+ add_listener(*listener)
246
+ end
247
+
248
+ def notify(event, *args)
236
249
  listeners.each do |listener|
237
- listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
250
+ next unless listener.respond_to?(event.to_sym)
251
+ log.trace "Notifying listener #{listener} of event: #{event}"
252
+ listener.send(event.to_sym, *args)
238
253
  end
239
254
  end
240
255
  end
256
+ # module Listenable
241
257
  end
258
+ # module Client
242
259
 
243
260
  # The Client module
244
261
  module Client
@@ -342,13 +359,7 @@ module Client
342
359
 
343
360
  # The ChannelInitializer class
344
361
  class ChannelInitializer < Java::io.netty.channel.ChannelInitializer
345
- DefaultHandler = ::Client::ModularHandler.new
346
- FrameDecoderBufferSize = 8192 # bytes
347
- # The encoder and decoder are sharable. If they were not, then
348
- # constant definitions could not be used.
349
- Decoder = StringDecoder.new
350
- Encoder = StringEncoder.new
351
- attr_accessor :handlers
362
+ attr_accessor :decoder, :encoder, :handlers
352
363
 
353
364
  def initialize(options = {})
354
365
  super()
@@ -356,6 +367,8 @@ module Client
356
367
  @host = @options[:host]
357
368
  @port = @options[:port]
358
369
  @handlers = []
370
+ @decoder = StringDecoder.new
371
+ @encoder = StringEncoder.new
359
372
  end
360
373
 
361
374
  def <<(handler)
@@ -365,13 +378,19 @@ module Client
365
378
  def initChannel(channel)
366
379
  pipeline = channel.pipeline
367
380
  pipeline.addLast(ssl_handler(channel)) if @options[:ssl]
368
- pipeline.addLast(
369
- DelimiterBasedFrameDecoder.new(FrameDecoderBufferSize, Delimiters.lineDelimiter()),
370
- Decoder,
371
- Encoder
372
- )
381
+ # pipeline.addLast(frame_decoder)
382
+ pipeline.addLast(decoder)
383
+ pipeline.addLast(encoder)
373
384
  add_user_handlers(pipeline)
374
- pipeline.addLast(DefaultHandler)
385
+ pipeline.addLast(default_handler)
386
+ end
387
+
388
+ def frame_decoder
389
+ DelimiterBasedFrameDecoder.new(@options[:max_frame_length], @options[:delimiter])
390
+ end
391
+
392
+ def default_handler
393
+ @default_handler ||= ::Client::ModularHandler.new
375
394
  end
376
395
 
377
396
  protected
@@ -424,7 +443,7 @@ module TCP
424
443
  include ::Client::Listenable
425
444
 
426
445
  def initialize(options = {}, *handlers, &block)
427
- init(::Client::Config::DEFAULTS.merge(options))
446
+ init(::Client.client_config.merge(options))
428
447
  configure_handlers(*handlers, &block)
429
448
  connect
430
449
  session
@@ -444,32 +463,32 @@ end
444
463
  module Client
445
464
  # The ArgumentsParser class
446
465
  class ArgumentsParser
447
- Flags = %i[banner port ssl log_level help version].freeze
448
466
  attr_reader :parser, :options
449
467
 
450
- def initialize(parser = OptionParser.new, options = ::Client::Config::DEFAULTS.dup)
468
+ def initialize(parser = OptionParser.new, options = ::Client.client_config.dup)
451
469
  @parser = parser
452
470
  @options = options
453
- Flags.each { |method_name| method(method_name).call }
471
+ @flags = %i[banner port ssl log_level help version]
472
+ @flags.each { |method_name| method(method_name)&.call if respond_to?(method_name) }
454
473
  end
455
474
 
456
475
  def banner
457
- @parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [host] [port] [options]"
476
+ @parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] <hostname> [port]"
458
477
  @parser.separator ''
459
478
  @parser.separator 'Options:'
460
479
  end
461
480
 
462
481
  def host
463
482
  description = "Connect to server at this host; default: #{@options[:host]}"
464
- @parser.on('-h', '--host=<host>s', description) do |v|
483
+ @parser.on('-h', '--hostname=<hostname>', description) do |v|
465
484
  @options[:host] = v
466
485
  end
467
486
  end
468
487
 
469
- IntegerPattern = /^\d+$/.freeze
488
+ def validated_port(val, integer_pattern = /^\d+$/)
489
+ raise OptionParser::InvalidArgument, "Invalid port: #{val}" unless \
490
+ integer_pattern.match?(val.to_s) && val.positive? && val < 65_536
470
491
 
471
- def validated_port(val)
472
- raise "Invalid port: #{v}" unless IntegerPattern.match?(val.to_s) && val.positive? && val < 65_536
473
492
  val
474
493
  end
475
494
 
@@ -488,6 +507,7 @@ module Client
488
507
 
489
508
  def log_level
490
509
  @parser.on_tail('-v', '--verbose', 'Increase verbosity') do
510
+ @options[:log_level] ||= 0
491
511
  @options[:log_level] -= 1
492
512
  end
493
513
  end
@@ -505,19 +525,28 @@ module Client
505
525
  exit
506
526
  end
507
527
  end
528
+
529
+ def parse_positional_arguments!
530
+ @options[:host] = ARGV.shift or raise OptionParser::MissingArgument, 'hostname'
531
+ return if (given_port = ARGV.shift&.to_i).nil?
532
+
533
+ @options[:port] = validated_port(given_port).to_i
534
+ end
508
535
  end
509
536
  # class ArgumentsParser
510
537
 
511
- # rubocop: disable Metrics/AbcSize
512
538
  def parse_arguments(arguments_parser = ArgumentsParser.new)
513
539
  arguments_parser.parser.parse!(ARGV)
514
- arguments_parser.options[:host] = v.to_s unless (v = ARGV.shift).nil?
515
- arguments_parser.options[:port] = validated_port(v).to_i unless (v = ARGV.shift).nil?
540
+ arguments_parser.parse_positional_arguments!
516
541
  arguments_parser.options
517
- rescue OptionParser::InvalidOption, OptionParser::AmbiguousOption => e
542
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption,
543
+ OptionParser::MissingArgument, OptionParser::NeedlessArgument => e
544
+ puts e.message
545
+ puts parser
546
+ exit
547
+ rescue OptionParser::AmbiguousOption => e
518
548
  abort e.message
519
549
  end
520
- # rubocop: enable Metrics/AbcSize
521
550
  end
522
551
  # module Client
523
552
 
@@ -538,12 +567,12 @@ end
538
567
 
539
568
  # The Client module
540
569
  module Client
541
- # The ConsoleHandler class
542
- class ConsoleHandler
570
+ # The Console class
571
+ class Console
543
572
  def message_received(ctx, message)
544
573
  log.trace "##{__method__} channel: #{ctx.channel}, message: #{message}"
545
574
  log.debug "Received message: #{message}"
546
- puts message.chomp unless message.nil?
575
+ $stdout.print message unless message.nil?
547
576
  end
548
577
  end
549
578
  end
@@ -557,8 +586,7 @@ module Client
557
586
  # rubocop: disable Metrics/MethodLength
558
587
  def main(args = parse_arguments)
559
588
  Logging.log_level = args[:log_level]
560
- handlers = [::Client::Monitor.new, ::Client::ConsoleHandler.new]
561
- ::TCP::Client.new(args, *handlers)
589
+ ::TCP::Client.new(args, ::Client::Console.new, ::Client::Monitor.new)
562
590
  rescue Interrupt => e
563
591
  warn format(InterruptTemplate, class: e.class)
564
592
  exit
data/lib/log.rb CHANGED
@@ -12,56 +12,98 @@ require 'logger'
12
12
 
13
13
  require 'log4j-2'
14
14
 
15
+ require 'fileutils'
16
+
17
+ # The Logging module
18
+ module Logging
19
+ # rubocop: disable Metrics/MethodLength
20
+ def config
21
+ @config ||= begin
22
+ lib_dir_path = File.expand_path(__dir__)
23
+ project_dir_path = File.expand_path(File.dirname(lib_dir_path))
24
+ logs_dir_path = File.expand_path(File.join(project_dir_path, 'logs'))
25
+ server_log_file = File.expand_path(File.join(logs_dir_path, 'server.log'))
26
+ {
27
+ level: :info,
28
+ name: 'tcp-server',
29
+ lib_dir_path: lib_dir_path,
30
+ project_dir_path: project_dir_path,
31
+ logs_dir_path: logs_dir_path,
32
+ server_log_file: server_log_file,
33
+ rolling_log_file_name_template: 'server-%d{yyyy-MM-dd}.log.gz',
34
+ logger_pattern_template: '%d{ABSOLUTE} %-5p [%c{1}] %m%n',
35
+ schedule: '0 0 0 * * ?',
36
+ size: '100M'
37
+ }
38
+ end
39
+ end
40
+ module_function :config
41
+ # rubocop: enable Metrics/MethodLength
42
+ end
43
+
15
44
  # The LogInitialization module
16
45
  module LogInitialization
17
- LibDirPath = File.expand_path(__dir__) unless defined?(LibDirPath)
18
- ProjectDirPath = File.expand_path(File.dirname(LibDirPath)) unless defined?(ProjectDirPath)
19
- LogsDirPath = File.expand_path(File.join(ProjectDirPath, 'logs'))
20
- Dir.mkdir(LogsDirPath) unless File.exist?(LogsDirPath)
21
- ServerLogFile = File.join(LogsDirPath, 'server.log')
22
- RollLogFileNameTemplate = 'server-%d{yyyy-MM-dd}.log.gz'.freeze
23
- RollingLogFilePath = File.join(LogsDirPath, RollLogFileNameTemplate)
24
- File.write(ServerLogFile, '') unless File.file? ServerLogFile
25
- LoggerPatternTemplate = '%d{ABSOLUTE} %-5p [%c{1}] %m%n'.freeze
46
+ def init
47
+ init_log_file
48
+ init_log4j if defined? Java
49
+ end
50
+ module_function :init
51
+
52
+ def init_log_file
53
+ FileUtils.mkdir_p(Logging.config[:logs_dir_path])
54
+ return if File.file?(Logging.config[:server_log_file])
55
+
56
+ File.write(Logging.config[:server_log_file], '')
57
+ end
58
+ module_function :init_log_file
26
59
 
27
60
  # rubocop: disable Metrics/AbcSize
28
61
  # rubocop: disable Metrics/MethodLength
29
- def self.init_log4j(log_level = org.apache.logging.log4j.Level::INFO)
62
+ def init_log4j(log_level = org.apache.logging.log4j.Level::INFO)
63
+ server_log_file = Logging.config[:server_log_file]
64
+ logs_dir_path = Logging.config[:logs_dir_path]
65
+ rolling_log_file_name_template = Logging.config[:rolling_log_file_name_template]
66
+ rolling_log_file_path = File.join(logs_dir_path, rolling_log_file_name_template)
67
+
30
68
  java.lang::System.setProperty('log4j.shutdownHookEnabled', java.lang::Boolean.toString(false))
31
69
  factory = org.apache.logging.log4j.core.config.builder.api::ConfigurationBuilderFactory
32
70
  config = factory.newConfigurationBuilder()
33
71
 
34
- log_level = org.apache.logging.log4j.Level.to_level(log_level.to_s.upcase) if log_level.is_a?(Symbol)
72
+ if log_level.is_a?(Symbol)
73
+ log_level = org.apache.logging.log4j.Level.to_level(
74
+ log_level.to_s.upcase
75
+ )
76
+ end
35
77
  config.setStatusLevel(log_level)
36
- config.setConfigurationName('websocket')
78
+ config.setConfigurationName(Logging.config['name'])
37
79
 
38
- # create a console appender
80
+ # Create a console appender
39
81
  target = org.apache.logging.log4j.core.appender::ConsoleAppender::Target::SYSTEM_OUT
40
82
  layout = config.newLayout('PatternLayout')
41
- layout = layout.addAttribute('pattern', LoggerPatternTemplate)
83
+ layout = layout.addAttribute('pattern', Logging.config[:logger_pattern_template])
42
84
  appender = config.newAppender('stdout', 'CONSOLE')
43
85
  appender = appender.addAttribute('target', target)
44
86
  appender = appender.add(layout)
45
87
  config.add(appender)
46
88
 
47
- # create a root logger
89
+ # Create a root logger
48
90
  root_logger = config.newRootLogger(log_level)
49
91
  root_logger = root_logger.add(config.newAppenderRef('stdout'))
50
92
 
51
- # create a rolling file appender
93
+ # Create a rolling file appender
52
94
  cron = config.newComponent('CronTriggeringPolicy')
53
- cron = cron.addAttribute('schedule', '0 0 0 * * ?')
95
+ cron = cron.addAttribute('schedule', Logging.config[:schedule])
54
96
 
55
97
  size = config.newComponent('SizeBasedTriggeringPolicy')
56
- size = size.addAttribute('size', '100M')
98
+ size = size.addAttribute('size', Logging.config[:size])
57
99
 
58
100
  policies = config.newComponent('Policies')
59
101
  policies = policies.addComponent(cron)
60
102
  policies = policies.addComponent(size)
61
103
 
62
104
  appender = config.newAppender('rolling_file', 'RollingFile')
63
- appender = appender.addAttribute('fileName', ServerLogFile)
64
- appender = appender.addAttribute('filePattern', RollingLogFilePath)
105
+ appender = appender.addAttribute('fileName', server_log_file)
106
+ appender = appender.addAttribute('filePattern', rolling_log_file_path)
65
107
  appender = appender.add(layout)
66
108
  appender = appender.addComponent(policies)
67
109
  config.add(appender)
@@ -77,10 +119,11 @@ module LogInitialization
77
119
  # rubocop: enable Metrics/AbcSize
78
120
  # rubocop: enable Metrics/MethodLength
79
121
  # def init_log4j
122
+ module_function :init_log4j
80
123
  end
81
124
  # module LogInitialization
82
125
 
83
- ::LogInitialization.init_log4j if defined? Java
126
+ ::LogInitialization.init
84
127
 
85
128
  # The Apache log4j Logger class
86
129
  # rubocop: disable Style/ClassAndModuleChildren
@@ -113,35 +156,14 @@ end
113
156
 
114
157
  # The Logging module
115
158
  module Logging
116
- # rubocop: disable Style/MutableConstant
117
- Configuration = {
118
- level: Logger::INFO
119
- }
120
- # rubocop: enable Style/MutableConstant
121
-
122
- def self.log_level=(log_level)
123
- Logging::Configuration[:level] = log_level
124
- end
125
-
126
- def self.log_level
127
- Logging::Configuration[:level]
128
- end
129
-
130
- def log(level = Logging.log_level, log_name = nil)
131
- @log ||= init_logger(level, log_name)
132
- end
133
- alias logger log
134
-
135
- protected
136
-
137
- def init_logger(level = Logging.log_level, logger_name = nil)
159
+ def init_logger(level = :all, logger_name = nil)
138
160
  return init_java_logger(level, logger_name, caller[2]) if defined?(Java)
139
- Logging.init_ruby_logger(level, logger_name)
161
+ init_ruby_logger(level)
140
162
  end
141
163
 
142
- def init_ruby_logger(level, logger_name = nil)
143
- logger_instance = Logger.new(logger_name)
144
- logger_instance.level = level
164
+ def init_ruby_logger(level)
165
+ logger_instance = Logger.new
166
+ logger_instance.level = Logging::Level.to_level(level.to_s.upcase)
145
167
  logger_instance
146
168
  end
147
169
 
@@ -149,9 +171,8 @@ module Logging
149
171
  def init_java_logger(level, logger_name = nil, source_location = nil)
150
172
  logger_name = get_formatted_logger_name(logger_name)
151
173
  logger_name = source_location.split(/\//).last if logger_name.empty?
152
- level_name = symbolize_numeric_log_level(level).to_s.upcase
153
174
  logger_instance = org.apache.logging.log4j.LogManager.getLogger(logger_name)
154
- logger_instance.level = org.apache.logging.log4j.Level.to_level(level_name)
175
+ logger_instance.level = org.apache.logging.log4j.Level.to_level(level.to_s.upcase)
155
176
  logger_instance
156
177
  end
157
178
  # rubocop: enable Metrics/AbcSize
@@ -185,6 +206,22 @@ module Logging
185
206
  end
186
207
  end
187
208
  # rubocop: enable Metrics/CyclomaticComplexity
209
+ module_function :symbolize_numeric_log_level
210
+
211
+ def log_level=(log_level)
212
+ Logging.config[:level] = symbolize_numeric_log_level(log_level)
213
+ end
214
+ module_function :log_level=
215
+
216
+ def log_level
217
+ Logging.config[:level]
218
+ end
219
+ module_function :log_level
220
+
221
+ def log(level = Logging.log_level, log_name = nil)
222
+ @log ||= init_logger(level, log_name)
223
+ end
224
+ alias logger log
188
225
  end
189
226
  # module Logging
190
227
 
@@ -18,16 +18,13 @@ require_relative 'config'
18
18
  module Server
19
19
  # The ArgumentsParser class
20
20
  class ArgumentsParser
21
- Flags = %i[
22
- banner port ssl idle_reading idle_writing log_requests log_level help
23
- version
24
- ].freeze
25
21
  attr_reader :parser, :options
26
22
 
27
23
  def initialize(option_parser = OptionParser.new)
28
24
  @parser = option_parser
29
- @options = ::Server::Config::DEFAULTS.dup
30
- Flags.each { |method_name| method(method_name).call }
25
+ @options = ::Server.server_config.dup
26
+ @flags = %i[banner port ssl idle_reading idle_writing log_requests log_level help version]
27
+ @flags.each { |method_name| method(method_name).call }
31
28
  end
32
29
 
33
30
  def banner
@@ -36,10 +33,10 @@ module Server
36
33
  @parser.separator 'Options:'
37
34
  end
38
35
 
39
- IntegerPattern = /^\d+$/.freeze
36
+ def validated_port(val, integer_pattern = /^\d+$/)
37
+ raise OptionParser::InvalidArgument, "Invalid port: #{v}" unless \
38
+ integer_pattern.match?(val.to_s) && val.positive? && val < 65_536
40
39
 
41
- def validated_port(val)
42
- raise "Invalid port: #{v}" unless IntegerPattern.match?(val.to_s) && val.positive? && val < 65_536
43
40
  val
44
41
  end
45
42
 
@@ -76,7 +73,8 @@ module Server
76
73
 
77
74
  def log_level
78
75
  @parser.on_tail('-v', '--verbose', 'Increase verbosity') do
79
- @options[:log_level] -= 1
76
+ current_level = @options.fetch(:log_level, 0)
77
+ @options[:log_level] = current_level - 1
80
78
  end
81
79
  end
82
80
 
@@ -30,19 +30,16 @@ module Server
30
30
 
31
31
  # The ChannelInitializer class
32
32
  class ChannelInitializer < Java::io.netty.channel.ChannelInitializer
33
- FrameDecoderBufferBytesSize = 8192
34
- # The encoder and decoder are sharable. If they were not, then
35
- # constant definitions could not be used.
36
- Decoder = StringDecoder.new
37
- Encoder = StringEncoder.new
38
- attr_accessor :user_handlers
33
+ attr_accessor :decoder, :encoder, :user_handlers
39
34
  attr_reader :options
40
35
 
41
36
  def initialize(channel_group, options = {})
42
37
  super()
43
38
  @channel_group = channel_group
44
39
  @options = options
45
- @user_handlers = []
40
+ @user_handlers = @options.fetch(:handlers, [])
41
+ @decoder = StringDecoder.new
42
+ @encoder = StringEncoder.new
46
43
  end
47
44
 
48
45
  def <<(handler)
@@ -52,21 +49,25 @@ module Server
52
49
  def initChannel(channel)
53
50
  pipeline = channel.pipeline
54
51
  pipeline.addLast(ssl_handler(channel)) if @options[:ssl]
55
- pipeline.addLast(
56
- DelimiterBasedFrameDecoder.new(FrameDecoderBufferBytesSize, Delimiters.lineDelimiter),
57
- Decoder,
58
- Encoder
59
- )
52
+ pipeline.addLast(frame_decoder, @decoder, @encoder)
60
53
  add_user_handlers(pipeline)
61
54
  pipeline.addLast(default_handler)
62
55
  end
63
56
 
57
+ def frame_decoder
58
+ DelimiterBasedFrameDecoder.new(@options[:max_frame_length], @options[:delimiter])
59
+ end
60
+
64
61
  def default_handler
65
62
  @default_handler ||= ::Server::ModularHandler.new(@channel_group)
66
63
  end
67
64
 
68
- def add_listener(listener)
69
- default_handler.add_listener(listener)
65
+ def add_listener(*listeners)
66
+ default_handler.add_listener(*listeners)
67
+ end
68
+
69
+ def replace_listeners(*listeners)
70
+ default_handler.replace_listener(*listeners)
70
71
  end
71
72
 
72
73
  protected
data/lib/server/config.rb CHANGED
@@ -10,13 +10,15 @@
10
10
  #
11
11
  # =end
12
12
 
13
+ require 'netty'
14
+
13
15
  require 'logger'
14
16
 
15
17
  # The Server module
16
18
  module Server
17
- # The Config module
18
- module Config
19
- DEFAULTS = {
19
+ # rubocop: disable Metrics/MethodLength
20
+ def server_config
21
+ @server_config ||= {
20
22
  host: '0.0.0.0',
21
23
  port: 8080,
22
24
  ssl: false,
@@ -24,7 +26,11 @@ module Server
24
26
  idle_writing: 30, # seconds
25
27
  log_requests: false,
26
28
  log_level: Logger::INFO,
27
- quit_commands: %i[bye quit]
29
+ quit_commands: %i[bye cease desist exit leave quit stop terminate],
30
+ max_frame_length: 8192,
31
+ delimiter: Java::io.netty.handler.codec.Delimiters.lineDelimiter
28
32
  }.freeze
29
33
  end
34
+ module_function :server_config
35
+ # rubocop: enable Metrics/MethodLength
30
36
  end
@@ -96,8 +96,12 @@ module Server
96
96
  channel_initializer << handler
97
97
  end
98
98
 
99
- def add_listener(listener)
100
- channel_initializer.add_listener(listener)
99
+ def add_listener(*listeners)
100
+ channel_initializer.add_listener(*listeners)
101
+ end
102
+
103
+ def replace_listeners(*listeners)
104
+ channel_initializer.replace_listener(*listeners)
101
105
  end
102
106
  end
103
107
  # module ServerInstanceMethods
@@ -20,19 +20,26 @@ module Server
20
20
  @listeners ||= java.util.concurrent.CopyOnWriteArrayList.new
21
21
  end
22
22
 
23
- def add_listener(listener)
24
- listeners << listener
23
+ def add_listener(*listener)
24
+ listeners.addAll(listener)
25
+ ensure
26
+ log.trace "Listeners: #{listeners}"
25
27
  end
26
28
 
27
- def remove_listener(listener)
28
- listeners.delete(listener)
29
+ def remove_listener(*listener)
30
+ listeners.removeAll(listener)
29
31
  end
30
32
 
31
- def notify(message, *args)
32
- return if listeners.empty?
33
- log.trace "Notifying listeners (#{listeners}) of message: #{message}"
33
+ def replace_listeners(*listener)
34
+ listeners.clear
35
+ add_listener(*listener)
36
+ end
37
+
38
+ def notify(event, *args)
34
39
  listeners.each do |listener|
35
- listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
40
+ next unless listener.respond_to?(event.to_sym)
41
+ log.trace "Notifying listener #{listener} of event: #{event}"
42
+ listener.send(event.to_sym, *args)
36
43
  end
37
44
  end
38
45
  end
@@ -39,17 +39,17 @@ module Server
39
39
  end
40
40
 
41
41
  def messageReceived(ctx, msg)
42
- log.trace "##{__method} channel: #{ctx.channel}, message: #{msg.inspect}"
42
+ log.trace "##{__method__} channel: #{ctx.channel}, message: #{msg.inspect}"
43
43
  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(ctx, msg) unless respond_to?(:handle_message) && @handler&.arity == 2
46
46
  handle_message(ctx, msg)
47
47
  end
48
48
 
49
49
  def handle_message(ctx, message)
50
50
  request = message.to_s.strip
51
51
  response = @handler.call(ctx.channel, request).to_s.chomp
52
- log.debug "response: #{response}"
52
+ log.debug "##{__method__} response: #{response}"
53
53
  ctx.channel.writeAndFlush("#{response}\n")
54
54
  end
55
55
  end
@@ -45,7 +45,7 @@ module Server
45
45
  end
46
46
 
47
47
  def channelActive(ctx)
48
- log.trace "##{__method__} channel: #{ctx.channel}"
48
+ log.info "##{__method__} channel: #{ctx.channel}"
49
49
  @channel_group.add(ctx.channel)
50
50
  notify :channel_active, ctx
51
51
  super(ctx)
data/lib/server/server.rb CHANGED
@@ -25,7 +25,7 @@ module Server
25
25
  attr_reader :options
26
26
 
27
27
  def initialize(params = {}, &block)
28
- @options = ::Server::Config::DEFAULTS.merge(params.fetch(:options, {}))
28
+ @options = ::Server.server_config.merge(params.fetch(:options, {}))
29
29
  configure_handlers(&block)
30
30
  end
31
31
 
@@ -12,5 +12,5 @@
12
12
 
13
13
  # The Server module
14
14
  module Server
15
- VERSION = '1.0.6'.freeze
15
+ VERSION = '1.0.9'.freeze
16
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcp-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.9
5
5
  platform: java
6
6
  authors:
7
7
  - Nels Nelson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-22 00:00:00.000000000 Z
11
+ date: 2022-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement