tcp-server 1.0.5-java → 1.0.8-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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