tcp-server 1.0.5-java → 1.0.8-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: 4915c8f9c555eccbae820aef2e73e1742a27da1765616019d5a48c9b62e40264
4
- data.tar.gz: 79a2ef5e23262aea916f25b0244e4ba965d4ecdeffd609c254899ca51a5f5d06
3
+ metadata.gz: 3c55987f7d6c92030cd96937448d5dea7b62c5dd43292e2d47a5b1901cdcbfa8
4
+ data.tar.gz: 6396aaaa64e18e454801debd881e618558ab65bec55de917fd328f0f4cdc041e
5
5
  SHA512:
6
- metadata.gz: d8436967a6786d2321eda7d4ffd7248d8f128d5c2d8b48dc1b3b30a064553067ce5abd5f1a09d87fb3161106614c1370465c17216c703e2d6333a5314a7a7f8e
7
- data.tar.gz: 47989e7e6028d4f421cea50e328e7b35abcc5007c86f90864ceb1ebc72a408310ad256aea949e9e9a7b9eb1082b211797eae96097afb447b3ea479713d0e234a
6
+ metadata.gz: 115f8b7d5bc5f8f48cfdb72eb4b81d6128f4b69af919eeaed79693bfb04aa1d09d893ba8970682f74afd417cb46c4176b73ca2b70ed082d0adec9268e8d53010
7
+ data.tar.gz: e1c144c6b38c6adca2e96c5e99cbd4a31d52aebe2076e7bd7cc82d2700c0e1f9c11a20d93f32943b6995a6a0e8ba4c20300e6d35170130982403736f64815050
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
 
@@ -224,17 +232,19 @@ module Client
224
232
 
225
233
  def add_listener(listener)
226
234
  listeners << listener
235
+ ensure
236
+ log.trace "Listeners: #{listeners}"
227
237
  end
228
238
 
229
239
  def remove_listener(listener)
230
240
  listeners.delete(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 notify(event, *args)
236
244
  listeners.each do |listener|
237
- listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
245
+ next unless listener.respond_to?(event.to_sym)
246
+ log.trace "Notifying listener #{listener} of event: #{event}"
247
+ listener.send(event.to_sym, *args)
238
248
  end
239
249
  end
240
250
  end
@@ -342,13 +352,7 @@ module Client
342
352
 
343
353
  # The ChannelInitializer class
344
354
  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
355
+ attr_accessor :decoder, :encoder, :handlers
352
356
 
353
357
  def initialize(options = {})
354
358
  super()
@@ -356,6 +360,8 @@ module Client
356
360
  @host = @options[:host]
357
361
  @port = @options[:port]
358
362
  @handlers = []
363
+ @decoder = StringDecoder.new
364
+ @encoder = StringEncoder.new
359
365
  end
360
366
 
361
367
  def <<(handler)
@@ -365,13 +371,19 @@ module Client
365
371
  def initChannel(channel)
366
372
  pipeline = channel.pipeline
367
373
  pipeline.addLast(ssl_handler(channel)) if @options[:ssl]
368
- pipeline.addLast(
369
- DelimiterBasedFrameDecoder.new(FrameDecoderBufferSize, Delimiters.lineDelimiter()),
370
- Decoder,
371
- Encoder
372
- )
374
+ # pipeline.addLast(frame_decoder)
375
+ pipeline.addLast(decoder)
376
+ pipeline.addLast(encoder)
373
377
  add_user_handlers(pipeline)
374
- pipeline.addLast(DefaultHandler)
378
+ pipeline.addLast(default_handler)
379
+ end
380
+
381
+ def frame_decoder
382
+ DelimiterBasedFrameDecoder.new(@options[:max_frame_length], @options[:delimiter])
383
+ end
384
+
385
+ def default_handler
386
+ @default_handler ||= ::Client::ModularHandler.new
375
387
  end
376
388
 
377
389
  protected
@@ -424,7 +436,7 @@ module TCP
424
436
  include ::Client::Listenable
425
437
 
426
438
  def initialize(options = {}, *handlers, &block)
427
- init(::Client::Config::DEFAULTS.merge(options))
439
+ init(::Client.client_config.merge(options))
428
440
  configure_handlers(*handlers, &block)
429
441
  connect
430
442
  session
@@ -444,32 +456,32 @@ end
444
456
  module Client
445
457
  # The ArgumentsParser class
446
458
  class ArgumentsParser
447
- Flags = %i[banner port ssl log_level help version].freeze
448
459
  attr_reader :parser, :options
449
460
 
450
- def initialize(parser = OptionParser.new, options = ::Client::Config::DEFAULTS.dup)
461
+ def initialize(parser = OptionParser.new, options = ::Client.client_config.dup)
451
462
  @parser = parser
452
463
  @options = options
453
- Flags.each { |method_name| method(method_name).call }
464
+ @flags = %i[banner port ssl log_level help version]
465
+ @flags.each { |method_name| method(method_name)&.call if respond_to?(method_name) }
454
466
  end
455
467
 
456
468
  def banner
457
- @parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [host] [port] [options]"
469
+ @parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] <hostname> [port]"
458
470
  @parser.separator ''
459
471
  @parser.separator 'Options:'
460
472
  end
461
473
 
462
474
  def host
463
475
  description = "Connect to server at this host; default: #{@options[:host]}"
464
- @parser.on('-h', '--host=<host>s', description) do |v|
476
+ @parser.on('-h', '--hostname=<hostname>', description) do |v|
465
477
  @options[:host] = v
466
478
  end
467
479
  end
468
480
 
469
- IntegerPattern = /^\d+$/.freeze
481
+ def validated_port(val, integer_pattern = /^\d+$/)
482
+ raise OptionParser::InvalidArgument, "Invalid port: #{val}" unless \
483
+ integer_pattern.match?(val.to_s) && val.positive? && val < 65_536
470
484
 
471
- def validated_port(val)
472
- raise "Invalid port: #{v}" unless IntegerPattern.match?(val.to_s) && val.positive? && val < 65_536
473
485
  val
474
486
  end
475
487
 
@@ -488,6 +500,7 @@ module Client
488
500
 
489
501
  def log_level
490
502
  @parser.on_tail('-v', '--verbose', 'Increase verbosity') do
503
+ @options[:log_level] ||= 0
491
504
  @options[:log_level] -= 1
492
505
  end
493
506
  end
@@ -505,19 +518,28 @@ module Client
505
518
  exit
506
519
  end
507
520
  end
521
+
522
+ def parse_positional_arguments!
523
+ @options[:host] = ARGV.shift or raise OptionParser::MissingArgument, 'hostname'
524
+ return if (given_port = ARGV.shift&.to_i).nil?
525
+
526
+ @options[:port] = validated_port(given_port).to_i
527
+ end
508
528
  end
509
529
  # class ArgumentsParser
510
530
 
511
- # rubocop: disable Metrics/AbcSize
512
531
  def parse_arguments(arguments_parser = ArgumentsParser.new)
513
532
  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?
533
+ arguments_parser.parse_positional_arguments!
516
534
  arguments_parser.options
517
- rescue OptionParser::InvalidOption, OptionParser::AmbiguousOption => e
535
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption,
536
+ OptionParser::MissingArgument, OptionParser::NeedlessArgument => e
537
+ puts e.message
538
+ puts parser
539
+ exit
540
+ rescue OptionParser::AmbiguousOption => e
518
541
  abort e.message
519
542
  end
520
- # rubocop: enable Metrics/AbcSize
521
543
  end
522
544
  # module Client
523
545
 
@@ -538,12 +560,12 @@ end
538
560
 
539
561
  # The Client module
540
562
  module Client
541
- # The ConsoleHandler class
542
- class ConsoleHandler
563
+ # The Console class
564
+ class Console
543
565
  def message_received(ctx, message)
544
566
  log.trace "##{__method__} channel: #{ctx.channel}, message: #{message}"
545
567
  log.debug "Received message: #{message}"
546
- puts message.chomp unless message.nil?
568
+ $stdout.print message unless message.nil?
547
569
  end
548
570
  end
549
571
  end
@@ -557,8 +579,7 @@ module Client
557
579
  # rubocop: disable Metrics/MethodLength
558
580
  def main(args = parse_arguments)
559
581
  Logging.log_level = args[:log_level]
560
- handlers = [::Client::Monitor.new, ::Client::ConsoleHandler.new]
561
- ::TCP::Client.new(args, *handlers)
582
+ ::TCP::Client.new(args, ::Client::Console.new, ::Client::Monitor.new)
562
583
  rescue Interrupt => e
563
584
  warn format(InterruptTemplate, class: e.class)
564
585
  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
- DefaultServerHandler = ModularHandler.new
34
- FrameDecoderBufferBytesSize = 8192
35
- # The encoder and decoder are sharable. If they were not, then
36
- # constant definitions could not be used.
37
- Decoder = StringDecoder.new
38
- Encoder = StringEncoder.new
39
- attr_accessor :user_handlers
33
+ attr_accessor :decoder, :encoder, :user_handlers
40
34
  attr_reader :options
41
35
 
42
- def initialize(options = {})
36
+ def initialize(channel_group, options = {})
43
37
  super()
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,13 +49,21 @@ 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
- pipeline.addLast(DefaultServerHandler)
54
+ pipeline.addLast(default_handler)
55
+ end
56
+
57
+ def frame_decoder
58
+ DelimiterBasedFrameDecoder.new(@options[:max_frame_length], @options[:delimiter])
59
+ end
60
+
61
+ def default_handler
62
+ @default_handler ||= ::Server::ModularHandler.new(@channel_group)
63
+ end
64
+
65
+ def add_listener(listener)
66
+ default_handler.add_listener(listener)
62
67
  end
63
68
 
64
69
  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
@@ -20,9 +20,11 @@ require_relative 'shutdown_hook'
20
20
  module Server
21
21
  java_import Java::io.netty.bootstrap.ServerBootstrap
22
22
  java_import Java::io.netty.channel.ChannelOption
23
+ java_import Java::io.netty.channel.group.DefaultChannelGroup
23
24
  java_import Java::io.netty.channel.nio.NioEventLoopGroup
24
25
  java_import Java::io.netty.handler.logging.LogLevel
25
26
  java_import Java::io.netty.handler.logging.LoggingHandler
27
+ java_import Java::io.netty.util.concurrent.GlobalEventExecutor
26
28
 
27
29
  # The InstanceMethods module
28
30
  module InstanceMethods
@@ -41,7 +43,7 @@ module Server
41
43
  end
42
44
 
43
45
  def channel_initializer
44
- @channel_initializer ||= ::Server::ChannelInitializer.new(@options)
46
+ @channel_initializer ||= ::Server::ChannelInitializer.new(channel_group, @options)
45
47
  end
46
48
 
47
49
  def boss_group
@@ -52,6 +54,10 @@ module Server
52
54
  @worker_group ||= NioEventLoopGroup.new
53
55
  end
54
56
 
57
+ def channel_group
58
+ @channel_group ||= DefaultChannelGroup.new('server_channels', GlobalEventExecutor::INSTANCE)
59
+ end
60
+
55
61
  def logging_handler
56
62
  @logging_handler ||= LoggingHandler.new(LogLevel::INFO)
57
63
  end
@@ -60,7 +66,7 @@ module Server
60
66
  # rubocop: disable Metrics/MethodLength
61
67
  def run(port = @options[:port])
62
68
  channel = bootstrap.bind(port).sync().channel()
63
- ::Server::Channels.add(channel)
69
+ channel_group.add(channel)
64
70
  ::Server::ShutdownHook.new(self)
65
71
  log.info "Listening on #{channel.local_address}"
66
72
  channel.closeFuture().sync()
@@ -77,8 +83,8 @@ module Server
77
83
  # rubocop: enable Metrics/MethodLength
78
84
 
79
85
  def shutdown
80
- ::Server::Channels.disconnect().awaitUninterruptibly()
81
- ::Server::Channels.close().awaitUninterruptibly()
86
+ channel_group.disconnect().awaitUninterruptibly()
87
+ channel_group.close().awaitUninterruptibly()
82
88
  end
83
89
 
84
90
  def stop
@@ -91,7 +97,7 @@ module Server
91
97
  end
92
98
 
93
99
  def add_listener(listener)
94
- ::Server::ChannelInitializer::DefaultServerHandler.add_listener(listener)
100
+ channel_initializer.add_listener(listener)
95
101
  end
96
102
  end
97
103
  # module ServerInstanceMethods
@@ -22,17 +22,19 @@ module Server
22
22
 
23
23
  def add_listener(listener)
24
24
  listeners << listener
25
+ ensure
26
+ log.trace "Listeners: #{listeners}"
25
27
  end
26
28
 
27
29
  def remove_listener(listener)
28
30
  listeners.delete(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 notify(event, *args)
34
34
  listeners.each do |listener|
35
- listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
35
+ next unless listener.respond_to?(event.to_sym)
36
+ log.trace "Notifying listener #{listener} of event: #{event}"
37
+ listener.send(event.to_sym, *args)
36
38
  end
37
39
  end
38
40
  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
@@ -18,19 +18,16 @@ require_relative 'listenable'
18
18
  # The Server module
19
19
  module Server
20
20
  java_import Java::io.netty.channel.SimpleChannelInboundHandler
21
- java_import Java::io.netty.channel.group.DefaultChannelGroup
22
- java_import Java::io.netty.util.concurrent.GlobalEventExecutor
23
-
24
- # rubocop: disable Style/IfUnlessModifier
25
- unless defined?(::Server::Channels)
26
- Channels = DefaultChannelGroup.new('channels', GlobalEventExecutor::INSTANCE)
27
- end
28
- # rubocop: enable Style/IfUnlessModifier
29
21
 
30
22
  # The ModularHandler class notifies listeners about events.
31
23
  class ModularHandler < SimpleChannelInboundHandler
32
24
  include ::Server::Listenable
33
25
 
26
+ def initialize(channel_group)
27
+ super()
28
+ @channel_group = channel_group
29
+ end
30
+
34
31
  def isSharable
35
32
  true
36
33
  end
@@ -48,8 +45,8 @@ module Server
48
45
  end
49
46
 
50
47
  def channelActive(ctx)
51
- log.trace "##{__method__} channel: #{ctx.channel}"
52
- ::Server::Channels.add(ctx.channel)
48
+ log.info "##{__method__} channel: #{ctx.channel}"
49
+ @channel_group.add(ctx.channel)
53
50
  notify :channel_active, ctx
54
51
  super(ctx)
55
52
  end
@@ -107,10 +104,10 @@ module Server
107
104
  super(ctx, cause) if listeners.nil? || listeners.empty?
108
105
  end
109
106
 
110
- IdentiferTemplate = '#<%<class>s:0x%<id>s>'.freeze
107
+ IdentifierTemplate = '#<%<class>s:0x%<id>s>'.freeze
111
108
 
112
109
  def to_s
113
- format(IdentiferTemplate, class: self.class.name, id: object_id.to_s(16))
110
+ format(IdentifierTemplate, class: self.class.name, id: object_id.to_s(16))
114
111
  end
115
112
  alias inspect to_s
116
113
  end
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.5'.freeze
15
+ VERSION = '1.0.8'.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.5
4
+ version: 1.0.8
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-20 00:00:00.000000000 Z
11
+ date: 2022-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement