websocket-server 1.0.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/Rakefile +56 -0
- data/exe/websocket +41 -0
- data/lib/log.rb +244 -0
- data/lib/server/mime_types.rb +38 -0
- data/lib/websocket/arguments_parser.rb +142 -0
- data/lib/websocket/channel_initializer.rb +73 -0
- data/lib/websocket/config.rb +59 -0
- data/lib/websocket/encoding.rb +21 -0
- data/lib/websocket/file_server_channel_progressive_future_listener.rb +32 -0
- data/lib/websocket/frame_handler.rb +71 -0
- data/lib/websocket/header_helpers.rb +70 -0
- data/lib/websocket/http_static_file_server_handler.rb +50 -0
- data/lib/websocket/http_static_file_server_handler_instance_methods.rb +160 -0
- data/lib/websocket/idle_handler.rb +41 -0
- data/lib/websocket/idle_state_user_event_handler.rb +47 -0
- data/lib/websocket/instance_methods.rb +127 -0
- data/lib/websocket/listenable.rb +41 -0
- data/lib/websocket/message_handler.rb +47 -0
- data/lib/websocket/response_helpers.rb +83 -0
- data/lib/websocket/server.rb +26 -0
- data/lib/websocket/shutdown_hook.rb +36 -0
- data/lib/websocket/ssl_cipher_inspector.rb +44 -0
- data/lib/websocket/ssl_context_initialization.rb +106 -0
- data/lib/websocket/telnet_proxy.rb +22 -0
- data/lib/websocket/validation_helpers.rb +51 -0
- data/lib/websocket/version.rb +16 -0
- data/lib/websocket-server.rb +13 -0
- data/lib/websocket_client.rb +478 -0
- data/lib/websocket_server.rb +50 -0
- data/web/client.html +43 -0
- data/web/css/client/console.css +167 -0
- data/web/css/client/parchment.css +112 -0
- data/web/favicon.ico +0 -0
- data/web/fonts/droidsansmono.v4.woff +0 -0
- data/web/js/client/ansispan.js +103 -0
- data/web/js/client/client.js +144 -0
- data/web/js/client/console.js +393 -0
- data/web/js/client/websocket.js +76 -0
- data/web/js/jquery.min.js +2 -0
- metadata +145 -0
@@ -0,0 +1,478 @@
|
|
1
|
+
#! /usr/bin/env jruby
|
2
|
+
|
3
|
+
# encoding: utf-8
|
4
|
+
# frozen_string_literal: false
|
5
|
+
|
6
|
+
# -*- mode: ruby -*-
|
7
|
+
# vi: set ft=ruby :
|
8
|
+
|
9
|
+
# =begin
|
10
|
+
#
|
11
|
+
# Copyright Nels Nelson 2016-2022 but freely usable (see license)
|
12
|
+
#
|
13
|
+
# =end
|
14
|
+
|
15
|
+
require 'optparse'
|
16
|
+
|
17
|
+
require 'java'
|
18
|
+
require 'netty'
|
19
|
+
|
20
|
+
require 'log'
|
21
|
+
|
22
|
+
# Simple websocket client ported from the example from Netty-io.
|
23
|
+
|
24
|
+
# The WebSocket module
|
25
|
+
module WebSocket
|
26
|
+
# rubocop: disable Metrics/AbcSize
|
27
|
+
def client_config
|
28
|
+
@client_config ||= {
|
29
|
+
log_level: Logger::INFO,
|
30
|
+
uri: java.net.URI.new('ws://127.0.0.1:4000/websocket'),
|
31
|
+
# Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
|
32
|
+
# If you change it to V00, ping is not supported and remember to change
|
33
|
+
# HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
|
34
|
+
websocket_version: Java::io.netty.handler.codec.http.websocketx.WebSocketVersion::V13,
|
35
|
+
subprotocol: nil,
|
36
|
+
allow_extensions: true,
|
37
|
+
default_headers: Java::io.netty.handler.codec.http.DefaultHttpHeaders.new,
|
38
|
+
prompt: '>'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
module_function :client_config
|
42
|
+
# rubocop: enable Metrics/AbcSize
|
43
|
+
end
|
44
|
+
# module WebSocket
|
45
|
+
|
46
|
+
# The WebSocket module
|
47
|
+
module WebSocket
|
48
|
+
SUPPORTED_SCHEMES = %w[ws wss].freeze
|
49
|
+
CHANNEL_TYPE = Java::io.netty.channel.socket.nio.NioSocketChannel.java_class
|
50
|
+
PING_MSG_CONTENT = [8, 1, 8, 1].to_java(:byte)
|
51
|
+
UNEXPECTED_FULL_RESPONSE_ERROR =
|
52
|
+
'Unexpected FullHttpResponse (getStatus=%<status>s, content=%<content>s)'.freeze
|
53
|
+
end
|
54
|
+
# module WebSocket
|
55
|
+
|
56
|
+
# The WebSocket module
|
57
|
+
module WebSocket
|
58
|
+
java_import Java::io.netty.bootstrap.Bootstrap
|
59
|
+
java_import Java::io.netty.channel.nio.NioEventLoopGroup
|
60
|
+
java_import Java::io.netty.handler.ssl.SslContext
|
61
|
+
java_import Java::io.netty.handler.ssl.SslContextBuilder
|
62
|
+
java_import Java::io.netty.handler.ssl.util.InsecureTrustManagerFactory
|
63
|
+
|
64
|
+
# The ClientInitializationMethods module
|
65
|
+
module ClientInitializationMethods
|
66
|
+
def init(options)
|
67
|
+
@options = options
|
68
|
+
configure_from_uri
|
69
|
+
@queue = java.util.concurrent.LinkedBlockingQueue.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def bootstrap
|
73
|
+
@bootstrap = Bootstrap.new
|
74
|
+
@bootstrap.group(client_group)
|
75
|
+
@bootstrap.channel(::WebSocket::CHANNEL_TYPE)
|
76
|
+
@bootstrap.handler(logging_handler) if @options[:log_requests]
|
77
|
+
@bootstrap.handler(channel_initializer)
|
78
|
+
end
|
79
|
+
|
80
|
+
def client_group
|
81
|
+
@client_group ||= NioEventLoopGroup.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def logging_handler
|
85
|
+
@logging_handler ||= LoggingHandler.new(LogLevel::INFO)
|
86
|
+
end
|
87
|
+
|
88
|
+
def channel_initializer
|
89
|
+
@channel_initializer ||= ::WebSocket::ClientChannelInitializer.new(@host, @port, @scheme, @options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_scheme(scheme)
|
93
|
+
raise 'Only WS(S) is supported' unless ::WebSocket::SUPPORTED_SCHEMES.include?(scheme)
|
94
|
+
end
|
95
|
+
|
96
|
+
def configure_from_uri
|
97
|
+
@uri = @options[:uri]
|
98
|
+
@scheme = validate_scheme(@uri.getScheme()&.downcase || 'ws')
|
99
|
+
@host = @uri.getHost() || '127.0.0.1'
|
100
|
+
@port = @uri.getPort() || case @scheme
|
101
|
+
when /ws/i then 80
|
102
|
+
when /wss/i then 443
|
103
|
+
else -1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def configure_handlers(*handlers, &block)
|
108
|
+
channel_initializer.default_handler.add_listener(self)
|
109
|
+
channel_initializer.default_handler.listeners.addAll(handlers)
|
110
|
+
@user_app = block
|
111
|
+
@application_handler = lambda do |ctx, msg|
|
112
|
+
if @user_app.nil? || @user_app.arity == 1
|
113
|
+
@queue.add(msg.chomp)
|
114
|
+
else
|
115
|
+
@user_app.call(ctx, msg)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
# module ClientInitializationMethods
|
121
|
+
end
|
122
|
+
# module WebSocket
|
123
|
+
|
124
|
+
# The WebSocket module
|
125
|
+
module WebSocket
|
126
|
+
java_import Java::io.netty.buffer.Unpooled
|
127
|
+
java_import Java::io.netty.channel.AbstractChannel
|
128
|
+
java_import Java::io.netty.handler.codec.http.websocketx.CloseWebSocketFrame
|
129
|
+
java_import Java::io.netty.handler.codec.http.websocketx.PingWebSocketFrame
|
130
|
+
java_import Java::io.netty.handler.codec.http.websocketx.PongWebSocketFrame
|
131
|
+
java_import Java::io.netty.handler.codec.http.websocketx.TextWebSocketFrame
|
132
|
+
|
133
|
+
# The ClientInstanceMethods module
|
134
|
+
module ClientInstanceMethods
|
135
|
+
def puts(msg)
|
136
|
+
sleep 0.1 until @channel.isActive()
|
137
|
+
msg.chomp!
|
138
|
+
log.trace "#puts msg: #{msg.inspect}"
|
139
|
+
raise 'Message is empty!' if msg.nil? || msg.empty?
|
140
|
+
@last_write_future = @channel.writeAndFlush(TextWebSocketFrame.new(msg))
|
141
|
+
end
|
142
|
+
|
143
|
+
def gets
|
144
|
+
log.debug 'Waiting for response from server'
|
145
|
+
@queue.take
|
146
|
+
rescue StandardError => e
|
147
|
+
warn "Unexpected error waiting for message: #{e.message}"
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
|
151
|
+
def connect
|
152
|
+
@channel = bootstrap.connect(@host, @port).sync().channel()
|
153
|
+
channel_initializer.default_handler.handshake_future.sync()
|
154
|
+
rescue AbstractChannel::AnnotatedConnectException => e
|
155
|
+
raise e.message
|
156
|
+
rescue StandardError => e
|
157
|
+
raise "Connection failure: #{e.message}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def close(channel = @channel)
|
161
|
+
log.debug 'Closing primary channel'
|
162
|
+
channel.writeAndFlush(CloseWebSocketFrame.new)
|
163
|
+
channel.closeFuture().sync()
|
164
|
+
ensure
|
165
|
+
shutdown
|
166
|
+
end
|
167
|
+
|
168
|
+
def shutdown
|
169
|
+
log.debug 'Shutting down gracefully'
|
170
|
+
@client_group&.shutdownGracefully()
|
171
|
+
ensure
|
172
|
+
client_has_shut_down
|
173
|
+
end
|
174
|
+
|
175
|
+
def ping
|
176
|
+
@channel.writeAndFlush(PingWebSocketFrame.new(Unpooled.wrappedBuffer(PING_MSG_CONTENT)))
|
177
|
+
end
|
178
|
+
|
179
|
+
def session
|
180
|
+
when_client_has_shut_down(@client_group) do |group|
|
181
|
+
log.debug "Channel group has shut down: #{group.inspect}"
|
182
|
+
end
|
183
|
+
@user_app.nil? ? read_user_commands : invoke_user_app
|
184
|
+
end
|
185
|
+
|
186
|
+
def invoke_user_app
|
187
|
+
@user_app&.call(self)
|
188
|
+
ensure
|
189
|
+
close
|
190
|
+
end
|
191
|
+
|
192
|
+
def shut_down_callbacks
|
193
|
+
@shut_down_callbacks ||= []
|
194
|
+
end
|
195
|
+
|
196
|
+
def when_client_has_shut_down(*args, &block)
|
197
|
+
shut_down_callbacks << {
|
198
|
+
block: block,
|
199
|
+
args: args
|
200
|
+
}
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
def client_has_shut_down
|
205
|
+
shut_down_callbacks.take_while do |callback|
|
206
|
+
callback[:block]&.call(*callback.fetch(:args, []))
|
207
|
+
end
|
208
|
+
rescue StandardError => e
|
209
|
+
log.error e.message
|
210
|
+
end
|
211
|
+
|
212
|
+
def channelActive(ctx)
|
213
|
+
log.info "Connected to #{ctx.channel().remoteAddress0()}"
|
214
|
+
end
|
215
|
+
|
216
|
+
def channelInactive(ctx)
|
217
|
+
log.info "Disconnected from #{ctx.channel().remoteAddress0()}"
|
218
|
+
end
|
219
|
+
|
220
|
+
def messageReceived(ctx, msg)
|
221
|
+
log.trace "Received message: #{msg}"
|
222
|
+
case msg
|
223
|
+
when TextWebSocketFrame then @application_handler.call(ctx, msg.text())
|
224
|
+
when PongWebSocketFrame then puts 'pong!'
|
225
|
+
when CloseWebSocketFrame then ctx.channel().close()
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def execute_command(str, websocket = self)
|
230
|
+
return if str.empty?
|
231
|
+
case str
|
232
|
+
when /bye/i, /quit/i
|
233
|
+
websocket.puts "#{str}\r\n"
|
234
|
+
close
|
235
|
+
when /ping/i then ping
|
236
|
+
else websocket.puts "#{str}\r\n"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def read_user_commands(websocket = self)
|
241
|
+
loop do
|
242
|
+
print @options[:prompt]
|
243
|
+
input = $stdin.gets.chomp
|
244
|
+
raise 'Poll failure from stdin' if input.nil?
|
245
|
+
break unless @channel.active?
|
246
|
+
break if execute_command(input).is_a?(AbstractChannel::CloseFuture)
|
247
|
+
$stdout.puts websocket.gets
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
# module ClientInstanceMethods
|
252
|
+
end
|
253
|
+
# module WebSocket
|
254
|
+
|
255
|
+
# The WebSocket module
|
256
|
+
module WebSocket
|
257
|
+
# The Listenable module
|
258
|
+
module Listenable
|
259
|
+
def listeners
|
260
|
+
@listeners ||= java.util.concurrent.CopyOnWriteArrayList.new
|
261
|
+
end
|
262
|
+
|
263
|
+
def add_listener(listener)
|
264
|
+
listeners << listener
|
265
|
+
end
|
266
|
+
|
267
|
+
def remove_listener(listener)
|
268
|
+
listeners.delete(listener)
|
269
|
+
end
|
270
|
+
|
271
|
+
def notify(message, *args)
|
272
|
+
return if listeners.empty?
|
273
|
+
log.trace "Notifying listeners (#{listeners}) of message: #{message}"
|
274
|
+
listeners.each do |listener|
|
275
|
+
listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# The WebSocket module
|
282
|
+
module WebSocket
|
283
|
+
java_import Java::io.netty.channel.ChannelHandlerContext
|
284
|
+
java_import Java::io.netty.channel.SimpleChannelInboundHandler
|
285
|
+
java_import Java::io.netty.handler.codec.http.FullHttpResponse
|
286
|
+
java_import Java::io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker
|
287
|
+
java_import Java::io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory
|
288
|
+
java_import Java::io.netty.handler.codec.http.websocketx.WebSocketHandshakeException
|
289
|
+
java_import Java::io.netty.util.CharsetUtil
|
290
|
+
|
291
|
+
# The ClientChannelHandler class
|
292
|
+
class ClientChannelHandler < SimpleChannelInboundHandler
|
293
|
+
include Listenable
|
294
|
+
attr_reader :handshake_future
|
295
|
+
|
296
|
+
def initialize(handshaker)
|
297
|
+
super()
|
298
|
+
@handshaker = handshaker
|
299
|
+
end
|
300
|
+
|
301
|
+
def handlerAdded(ctx)
|
302
|
+
@handshake_future = ctx.newPromise()
|
303
|
+
end
|
304
|
+
|
305
|
+
def channelActive(ctx)
|
306
|
+
@handshaker.handshake(ctx.channel())
|
307
|
+
notify(:channelActive, ctx)
|
308
|
+
end
|
309
|
+
|
310
|
+
def channelInactive(ctx)
|
311
|
+
notify(:channelInactive, ctx)
|
312
|
+
end
|
313
|
+
|
314
|
+
# Please keep in mind that this method will be renamed to
|
315
|
+
# messageReceived(ChannelHandlerContext, I) in 5.0.
|
316
|
+
#
|
317
|
+
# java_signature 'protected void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception'
|
318
|
+
def channelRead0(ctx, msg)
|
319
|
+
messageReceived(ctx, msg)
|
320
|
+
end
|
321
|
+
|
322
|
+
def messageReceived(ctx, msg)
|
323
|
+
return finish_handshake(ctx, msg) unless @handshaker.isHandshakeComplete()
|
324
|
+
validate_message(msg)
|
325
|
+
notify(:messageReceived, ctx, msg)
|
326
|
+
end
|
327
|
+
|
328
|
+
def exceptionCaught(ctx, cause)
|
329
|
+
log.info "##{__method__} wtf1"
|
330
|
+
cause.printStackTrace()
|
331
|
+
@handshake_future.setFailure(cause) unless @handshake_future.isDone()
|
332
|
+
ctx.close()
|
333
|
+
end
|
334
|
+
|
335
|
+
protected
|
336
|
+
|
337
|
+
def finish_handshake(ctx, msg)
|
338
|
+
@handshaker.finishHandshake(ctx.channel(), msg)
|
339
|
+
@handshake_future.setSuccess()
|
340
|
+
rescue WebSocketHandshakeException => e
|
341
|
+
log.warn "Connection failure: #{e.message}"
|
342
|
+
@handshake_future.setFailure(e)
|
343
|
+
end
|
344
|
+
|
345
|
+
def validate_message(msg)
|
346
|
+
case msg
|
347
|
+
when FullHttpResponse
|
348
|
+
raise IllegalStateException, format(
|
349
|
+
UNEXPECTED_FULL_RESPONSE_ERROR,
|
350
|
+
status: msg.status(),
|
351
|
+
content: msg.content().toString(CharsetUtil::UTF_8)
|
352
|
+
)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
# class ClientChannelHandler
|
357
|
+
end
|
358
|
+
# module WebSocket
|
359
|
+
|
360
|
+
# The WebSocket module
|
361
|
+
module WebSocket
|
362
|
+
java_import Java::io.netty.handler.codec.http.HttpClientCodec
|
363
|
+
java_import Java::io.netty.handler.codec.http.HttpObjectAggregator
|
364
|
+
java_import Java::io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler
|
365
|
+
|
366
|
+
# The ClientChannelInitializer class
|
367
|
+
class ClientChannelInitializer < Java::io.netty.channel.ChannelInitializer
|
368
|
+
FrameDecoderBufferSize = 8192 # bytes
|
369
|
+
def initialize(host, port, scheme, options = {})
|
370
|
+
super()
|
371
|
+
@host = host
|
372
|
+
@port = port
|
373
|
+
@options = options
|
374
|
+
@ssl_ctx = ssl_context if /wss/i.match?(scheme)
|
375
|
+
end
|
376
|
+
|
377
|
+
def handshaker
|
378
|
+
@handshaker ||= WebSocketClientHandshakerFactory.newHandshaker(
|
379
|
+
@options[:uri],
|
380
|
+
@options[:websocket_version],
|
381
|
+
@options[:subprotocol],
|
382
|
+
@options[:allow_extensions],
|
383
|
+
@options[:default_headers]
|
384
|
+
)
|
385
|
+
end
|
386
|
+
|
387
|
+
def default_handler
|
388
|
+
@default_handler ||= ::WebSocket::ClientChannelHandler.new(handshaker)
|
389
|
+
end
|
390
|
+
|
391
|
+
def ssl_context
|
392
|
+
context_builder = SslContextBuilder.forClient()
|
393
|
+
trust_manager = context_builder.trustManager(InsecureTrustManagerFactory::INSTANCE)
|
394
|
+
trust_manager.build()
|
395
|
+
end
|
396
|
+
|
397
|
+
def initChannel(channel)
|
398
|
+
pipeline = channel.pipeline()
|
399
|
+
pipeline.addLast(@ssl_ctx.newHandler(channel.alloc(), @host, @port)) unless @ssl_ctx.nil?
|
400
|
+
pipeline.addLast(
|
401
|
+
HttpClientCodec.new,
|
402
|
+
HttpObjectAggregator.new(FrameDecoderBufferSize),
|
403
|
+
WebSocketClientCompressionHandler::INSTANCE
|
404
|
+
)
|
405
|
+
pipeline.addLast(default_handler)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
# class ClientChannelInitializer
|
409
|
+
end
|
410
|
+
# module WebSocket
|
411
|
+
|
412
|
+
# The WebSocket module
|
413
|
+
module WebSocket
|
414
|
+
# The WebSocket::Client class
|
415
|
+
class Client
|
416
|
+
include ::WebSocket::ClientInitializationMethods
|
417
|
+
include ::WebSocket::ClientInstanceMethods
|
418
|
+
include ::WebSocket::Listenable
|
419
|
+
|
420
|
+
def initialize(options = {}, &block)
|
421
|
+
init(::WebSocket.client_config.merge(options))
|
422
|
+
configure_handlers(&block)
|
423
|
+
connect
|
424
|
+
session
|
425
|
+
end
|
426
|
+
|
427
|
+
IdentiferTemplate = '#<%<class>s:0x%<id>s>'.freeze
|
428
|
+
|
429
|
+
def to_s
|
430
|
+
format(IdentiferTemplate, class: self.class.name, id: object_id.to_s(16))
|
431
|
+
end
|
432
|
+
alias inspect to_s
|
433
|
+
end
|
434
|
+
end
|
435
|
+
# module WebSocket
|
436
|
+
|
437
|
+
# The WebSocket module
|
438
|
+
module WebSocket
|
439
|
+
# rubocop: disable Metrics/AbcSize
|
440
|
+
# rubocop: disable Metrics/MethodLength
|
441
|
+
def parse_arguments(options = ::WebSocket.client_config.dup)
|
442
|
+
parser = OptionParser.new
|
443
|
+
parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]"
|
444
|
+
parser.separator ''
|
445
|
+
parser.separator 'Options:'
|
446
|
+
parser.on_head('-u', '--uri=<uri>', 'Fully qualified connection string') do |v|
|
447
|
+
options[:uri] = java.net.URI.new(v)
|
448
|
+
end
|
449
|
+
parser.on_tail('-v', '--verbose', 'Increase verbosity') { options[:log_level] -= 1 }
|
450
|
+
parser.on_tail('-?', '--help', 'Show this message') do
|
451
|
+
puts parser
|
452
|
+
exit
|
453
|
+
end
|
454
|
+
parser.on_tail('--version', 'Show version') do
|
455
|
+
puts "#{$PROGRAM_NAME} version #{::WebSocket.version}"
|
456
|
+
exit
|
457
|
+
end
|
458
|
+
parser.parse!
|
459
|
+
options
|
460
|
+
end
|
461
|
+
# rubocop: enable Metrics/AbcSize
|
462
|
+
# rubocop: enable Metrics/MethodLength
|
463
|
+
|
464
|
+
def main(args = parse_arguments)
|
465
|
+
Logging.log_level = args[:log_level]
|
466
|
+
::WebSocket::Client.new(args)
|
467
|
+
rescue Interrupt => e
|
468
|
+
warn "\n#{e.class}"
|
469
|
+
exit
|
470
|
+
rescue StandardError => e
|
471
|
+
::WebSocket::Client.log.fatal "Unexpected error: #{e.class}: #{e.message}"
|
472
|
+
e.backtrace.each { |t| log.debug t } if Logging.log_level == Logger::DEBUG
|
473
|
+
exit 1
|
474
|
+
end
|
475
|
+
end
|
476
|
+
# module WebSocket
|
477
|
+
|
478
|
+
Object.new.extend(::WebSocket).main if $PROGRAM_NAME == __FILE__
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#! /usr/bin/env jruby
|
2
|
+
|
3
|
+
# encoding: utf-8
|
4
|
+
# frozen_string_literal: false
|
5
|
+
|
6
|
+
# -*- mode: ruby -*-
|
7
|
+
# vi: set ft=ruby :
|
8
|
+
|
9
|
+
# =begin
|
10
|
+
#
|
11
|
+
# Copyright Nels Nelson 2016-2022 but freely usable (see license)
|
12
|
+
#
|
13
|
+
# =end
|
14
|
+
|
15
|
+
require_relative 'log'
|
16
|
+
require_relative 'websocket/arguments_parser'
|
17
|
+
require_relative 'websocket/server'
|
18
|
+
require_relative 'websocket/telnet_proxy'
|
19
|
+
|
20
|
+
# The WebSocket module
|
21
|
+
module WebSocket
|
22
|
+
EchoServerMessageTemplate = "%<message>s\n".freeze
|
23
|
+
InterruptTemplate = "\r%<class>s".freeze
|
24
|
+
|
25
|
+
def echo_server(options)
|
26
|
+
WebSocket::Server.new(options: options) do |_ctx, msg|
|
27
|
+
format(EchoServerMessageTemplate, message: msg.upcase)
|
28
|
+
end.run
|
29
|
+
end
|
30
|
+
|
31
|
+
def telnet_proxy(options)
|
32
|
+
handler = WebSocket::TelnetProxy.new(options[:telnet_proxy_host], options[:telnet_proxy_port])
|
33
|
+
WebSocket::Server.new(handler: handler, options: options).run
|
34
|
+
end
|
35
|
+
|
36
|
+
def main(args = parse_arguments)
|
37
|
+
Logging.log_level = args[:log_level]
|
38
|
+
return telnet_proxy(args) unless args[:telnet_proxy_host].nil?
|
39
|
+
echo_server(args)
|
40
|
+
rescue Interrupt => e
|
41
|
+
warn format(InterruptTemplate, class: e.class)
|
42
|
+
exit
|
43
|
+
rescue StandardError => e
|
44
|
+
WebSocket::Server.log.fatal(e.message)
|
45
|
+
abort
|
46
|
+
end
|
47
|
+
end
|
48
|
+
# module WebSocket
|
49
|
+
|
50
|
+
Object.new.extend(WebSocket).main if $PROGRAM_NAME == __FILE__
|
data/web/client.html
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
3
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
5
|
+
<head>
|
6
|
+
<meta charset="utf-8"></meta>
|
7
|
+
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"></meta>
|
8
|
+
<meta http-equiv="Content-type" content="text/html;charset=UTF-8"></meta>
|
9
|
+
<link id='theme' rel='stylesheet' media="screen, print" href='css/client/console.css' type='text/css'></link>
|
10
|
+
<title>WebSocket Client</title>
|
11
|
+
</head>
|
12
|
+
<body class="preload">
|
13
|
+
<div class="body">
|
14
|
+
|
15
|
+
<div class="nav">
|
16
|
+
<ul class="horizontal">
|
17
|
+
<li class="link">
|
18
|
+
<a id="nav-home" href="/" target="_blank">Home</a></li>
|
19
|
+
<li class="control">
|
20
|
+
<a id="nav-menu">Menu ▼</a></li>
|
21
|
+
</ul>
|
22
|
+
<div class="menu">
|
23
|
+
<ul class="vertical">
|
24
|
+
<li class="menuitem">
|
25
|
+
<a id="nav-help">Help</a></li>
|
26
|
+
<li class="menuitem">
|
27
|
+
<a id="nav-style">◐</a></li>
|
28
|
+
</ul>
|
29
|
+
</div>
|
30
|
+
</div> <!-- nav -->
|
31
|
+
|
32
|
+
<div class="space"></div>
|
33
|
+
<div class="display"><div class="input"><form class="input"><table width="100%"><tr><td><label for="input" id="input" class="input"></label><label for="password" id="password" class="password"></label></td><td class="input" width="100%"><input id="input" class="input" autocapitalize="off" autocorrect="off" x-webkit-speech="x-webkit-speech" data-lpignore="true"></input><input type="password" class="password" id="password" data-lpignore="true"></input></td></tr></table></form></div></div>
|
34
|
+
|
35
|
+
|
36
|
+
</div> <!-- body -->
|
37
|
+
</body>
|
38
|
+
<script type='text/javascript' src='/js/jquery.min.js'></script>
|
39
|
+
<script type='text/javascript' src='/js/client/ansispan.js'></script>
|
40
|
+
<script type='text/javascript' src='/js/client/console.js'></script>
|
41
|
+
<script type='text/javascript' src='/js/client/websocket.js'></script>
|
42
|
+
<script type='text/javascript' src='/js/client/client.js'></script>
|
43
|
+
</html>
|