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 +4 -4
- data/README.md +14 -9
- data/lib/client.rb +72 -51
- data/lib/log.rb +86 -49
- data/lib/server/argument_parser.rb +8 -10
- data/lib/server/channel_initializer.rb +20 -15
- data/lib/server/config.rb +10 -4
- data/lib/server/instance_methods.rb +11 -5
- data/lib/server/listenable.rb +6 -4
- data/lib/server/message_handler.rb +3 -3
- data/lib/server/modular_handler.rb +9 -12
- data/lib/server/server.rb +1 -1
- data/lib/server/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c55987f7d6c92030cd96937448d5dea7b62c5dd43292e2d47a5b1901cdcbfa8
|
4
|
+
data.tar.gz: 6396aaaa64e18e454801debd881e618558ab65bec55de917fd328f0f4cdc041e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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,
|
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 ./
|
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}" --
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
370
|
-
|
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(
|
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
|
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
|
461
|
+
def initialize(parser = OptionParser.new, options = ::Client.client_config.dup)
|
451
462
|
@parser = parser
|
452
463
|
@options = options
|
453
|
-
|
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)} [
|
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', '--
|
476
|
+
@parser.on('-h', '--hostname=<hostname>', description) do |v|
|
465
477
|
@options[:host] = v
|
466
478
|
end
|
467
479
|
end
|
468
480
|
|
469
|
-
|
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.
|
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::
|
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
|
542
|
-
class
|
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
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
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
|
-
|
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('
|
78
|
+
config.setConfigurationName(Logging.config['name'])
|
37
79
|
|
38
|
-
#
|
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',
|
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
|
-
#
|
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
|
-
#
|
93
|
+
# Create a rolling file appender
|
52
94
|
cron = config.newComponent('CronTriggeringPolicy')
|
53
|
-
cron = cron.addAttribute('schedule',
|
95
|
+
cron = cron.addAttribute('schedule', Logging.config[:schedule])
|
54
96
|
|
55
97
|
size = config.newComponent('SizeBasedTriggeringPolicy')
|
56
|
-
size = size.addAttribute('size',
|
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',
|
64
|
-
appender = appender.addAttribute('filePattern',
|
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.
|
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
|
-
|
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
|
-
|
161
|
+
init_ruby_logger(level)
|
140
162
|
end
|
141
163
|
|
142
|
-
def init_ruby_logger(level
|
143
|
-
logger_instance = Logger.new
|
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(
|
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
|
30
|
-
|
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
|
-
|
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
|
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
|
-
|
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(
|
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
|
-
#
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
100
|
+
channel_initializer.add_listener(listener)
|
95
101
|
end
|
96
102
|
end
|
97
103
|
# module ServerInstanceMethods
|
data/lib/server/listenable.rb
CHANGED
@@ -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(
|
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
|
-
|
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 "##{
|
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
|
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.
|
52
|
-
|
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
|
-
|
107
|
+
IdentifierTemplate = '#<%<class>s:0x%<id>s>'.freeze
|
111
108
|
|
112
109
|
def to_s
|
113
|
-
format(
|
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
|
28
|
+
@options = ::Server.server_config.merge(params.fetch(:options, {}))
|
29
29
|
configure_handlers(&block)
|
30
30
|
end
|
31
31
|
|
data/lib/server/version.rb
CHANGED
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.
|
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-
|
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
|