websocket-server 1.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +185 -0
  4. data/Rakefile +56 -0
  5. data/exe/websocket +41 -0
  6. data/lib/log.rb +244 -0
  7. data/lib/server/mime_types.rb +38 -0
  8. data/lib/websocket/arguments_parser.rb +142 -0
  9. data/lib/websocket/channel_initializer.rb +73 -0
  10. data/lib/websocket/config.rb +59 -0
  11. data/lib/websocket/encoding.rb +21 -0
  12. data/lib/websocket/file_server_channel_progressive_future_listener.rb +32 -0
  13. data/lib/websocket/frame_handler.rb +71 -0
  14. data/lib/websocket/header_helpers.rb +70 -0
  15. data/lib/websocket/http_static_file_server_handler.rb +50 -0
  16. data/lib/websocket/http_static_file_server_handler_instance_methods.rb +160 -0
  17. data/lib/websocket/idle_handler.rb +41 -0
  18. data/lib/websocket/idle_state_user_event_handler.rb +47 -0
  19. data/lib/websocket/instance_methods.rb +127 -0
  20. data/lib/websocket/listenable.rb +41 -0
  21. data/lib/websocket/message_handler.rb +47 -0
  22. data/lib/websocket/response_helpers.rb +83 -0
  23. data/lib/websocket/server.rb +26 -0
  24. data/lib/websocket/shutdown_hook.rb +36 -0
  25. data/lib/websocket/ssl_cipher_inspector.rb +44 -0
  26. data/lib/websocket/ssl_context_initialization.rb +106 -0
  27. data/lib/websocket/telnet_proxy.rb +22 -0
  28. data/lib/websocket/validation_helpers.rb +51 -0
  29. data/lib/websocket/version.rb +16 -0
  30. data/lib/websocket-server.rb +13 -0
  31. data/lib/websocket_client.rb +478 -0
  32. data/lib/websocket_server.rb +50 -0
  33. data/web/client.html +43 -0
  34. data/web/css/client/console.css +167 -0
  35. data/web/css/client/parchment.css +112 -0
  36. data/web/favicon.ico +0 -0
  37. data/web/fonts/droidsansmono.v4.woff +0 -0
  38. data/web/js/client/ansispan.js +103 -0
  39. data/web/js/client/client.js +144 -0
  40. data/web/js/client/console.js +393 -0
  41. data/web/js/client/websocket.js +76 -0
  42. data/web/js/jquery.min.js +2 -0
  43. metadata +145 -0
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require_relative 'websocket_server'
@@ -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 &#9660;</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">&#9680</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>