@@ -381,142 +381,247 @@ public class TelnetShell {
381381
382382## HTTP/WebSocket Connectivity
383383
384- WebSocket terminals enable browser-based terminal access, perfect for web applications.
384+ WebSocket terminals enable browser-based terminal access, perfect for web applications. The terminal-http module uses Netty for high-performance WebSocket handling and includes a default web page using [ xterm.js ] ( https://github.com/xtermjs/xterm.js ) .
385385
386386### Maven Dependency
387387
388388``` xml
389389<dependency >
390390 <groupId >org.aesh</groupId >
391- <artifactId >aesh- terminal-http</artifactId >
392- <version >2.6 </version >
391+ <artifactId >terminal-http</artifactId >
392+ <version >3.0 </version >
393393</dependency >
394394```
395395
396396### Basic WebSocket Server
397397
398398``` java
399- import org.aesh.terminal.http.HttpTerminal ;
399+ import org.aesh.terminal.http.netty.NettyWebsocketTtyBootstrap ;
400400import org.aesh.readline.Readline ;
401401import org.aesh.readline.ReadlineBuilder ;
402402import org.aesh.terminal.Connection ;
403403
404+ import java.util.concurrent.TimeUnit ;
405+
404406public class WebTerminal {
405-
406- public static void main (String [] args ) throws Exception {
407- HttpTerminal http = HttpTerminal . builder()
408- .host(" 0.0.0.0" )
409- .port(8080 )
410- .webSocketPath(" /terminal" )
411- .connectionHandler(WebTerminal :: handleConnection)
412- .build();
413-
414- System . out. println(" WebSocket terminal started" );
415- System . out. println(" Open browser to: http://localhost:8080/terminal" );
416-
417- http. start();
418- Thread . currentThread(). join();
407+
408+ public static synchronized void main (String [] args ) throws Exception {
409+ NettyWebsocketTtyBootstrap bootstrap = new NettyWebsocketTtyBootstrap ()
410+ .setHost(" localhost" )
411+ .setPort(8080 );
412+
413+ bootstrap. start(WebTerminal :: handleConnection). get(10 , TimeUnit . SECONDS );
414+
415+ System . out. println(" WebSocket terminal started on http://localhost:8080" );
416+ WebTerminal . class. wait();
419417 }
420-
418+
421419 private static void handleConnection (Connection connection ) {
422420 Readline readline = ReadlineBuilder . builder(). build();
423-
421+
424422 connection. write(" Welcome to Web Terminal!\r\n " );
425423 read(connection, readline);
426424 }
427-
425+
428426 private static void read (Connection connection , Readline readline ) {
429427 readline. readline(connection, " [web]$ " , input - > {
430428 if (input == null || input. equals(" exit" )) {
431429 connection. write(" Goodbye!\r\n " );
432430 connection. close();
433431 return ;
434432 }
435-
433+
436434 connection. write(" You entered: " + input + " \r\n " );
437435 read(connection, readline);
438436 });
439437 }
440438}
441439```
442440
443- ### WebSocket Builder Options
441+ ### WebSocket Bootstrap Options
444442
445443| Method | Type | Description |
446444| --------| ------| -------------|
447- | ` host(String) ` | ` String ` | Bind address (default: "0.0.0.0") |
448- | ` port(int) ` | ` int ` | HTTP port (default: 8080) |
449- | ` webSocketPath(String) ` | ` String ` | WebSocket endpoint path |
450- | ` connectionHandler(Consumer<Connection>) ` | ` Consumer ` | Handler for new connections |
451- | ` staticFilesPath(String) ` | ` String ` | Path to serve static files |
452- | ` idleTimeout(long) ` | ` long ` | Connection idle timeout (ms) |
445+ | ` setHost(String) ` | ` String ` | Bind address (default: "localhost") |
446+ | ` setPort(int) ` | ` int ` | HTTP port (default: 8080) |
447+ | ` setResourcePath(String) ` | ` String ` | Classpath path for static files (default: "/org/aesh/terminal/http") |
448+ | ` setServeStaticFiles(boolean) ` | ` boolean ` | Enable/disable static file serving (default: true) |
449+
450+ The WebSocket endpoint is always available at ` /ws ` .
451+
452+ ### Custom Web Page
453+
454+ By default, terminal-http serves a built-in HTML page with xterm.js. To use your own web page:
455+
456+ ``` java
457+ // Use custom resources from your classpath
458+ NettyWebsocketTtyBootstrap bootstrap = new NettyWebsocketTtyBootstrap ()
459+ .setHost(" localhost" )
460+ .setPort(8080 )
461+ .setResourcePath(" /com/myapp/web" ); // Your classpath location
462+ ```
463+
464+ Place your ` index.html ` at ` src/main/resources/com/myapp/web/index.html ` .
465+
466+ ### WebSocket-Only Mode
467+
468+ For applications that serve HTML from a separate web server:
469+
470+ ``` java
471+ // Disable static file serving - only handle WebSocket at /ws
472+ NettyWebsocketTtyBootstrap bootstrap = new NettyWebsocketTtyBootstrap ()
473+ .setHost(" localhost" )
474+ .setPort(8080 )
475+ .setServeStaticFiles(false );
476+ ```
453477
454478### Browser Client with xterm.js
455479
456- Create an HTML client using xterm.js:
480+ Create an HTML client using xterm.js from CDN :
457481
458482``` html
459- <!DOCTYPE html>
460- <html >
483+ <!doctype html>
484+ <html lang = " en " >
461485<head >
486+ <meta charset =" UTF-8" >
487+ <meta name =" viewport" content =" width=device-width, initial-scale=1.0" >
462488 <title >Web Terminal</title >
463- <link rel =" stylesheet" href =" https://unpkg.com/xterm/css/xterm.css" />
464- <script src =" https://unpkg.com/xterm/lib/xterm.js" ></script >
465- <script src =" https://unpkg.com/xterm-addon-attach/lib/xterm-addon-attach.js" ></script >
466- <script src =" https://unpkg.com/xterm-addon-fit/lib/xterm-addon-fit.js" ></script >
489+ <link rel =" stylesheet" href =" https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css" />
467490 <style >
468- body { margin : 0 ; padding : 20px ; background : #1e1e1e ; }
469- #terminal { width : 100% ; height : calc (100vh - 40px ); }
491+ html , body {
492+ margin : 0 ;
493+ padding : 0 ;
494+ background : #1e1e1e ;
495+ font-family : -apple-system , BlinkMacSystemFont, " Segoe UI" , Roboto, sans-serif ;
496+ min-height : 100vh ;
497+ }
498+ .wrapper {
499+ display : flex ;
500+ flex-direction : column ;
501+ align-items : center ;
502+ padding : 20px ;
503+ min-height : 100vh ;
504+ box-sizing : border-box ;
505+ }
506+ h1 {
507+ margin : 0 0 20px 0 ;
508+ font-size : 24px ;
509+ color : #f0f0f0 ;
510+ }
511+ #terminal-container {
512+ border : 2px solid #444 ;
513+ border-radius : 8px ;
514+ overflow : hidden ;
515+ box-shadow : 0 4px 20px rgba (0 , 0 , 0 , 0.5 );
516+ padding : 10px ;
517+ background : #000 ;
518+ }
519+ .connection-status {
520+ margin-bottom : 15px ;
521+ padding : 8px 16px ;
522+ border-radius : 4px ;
523+ font-size : 14px ;
524+ }
525+ .status-connected { background : #1a472a ; color : #4ade80 ; }
526+ .status-disconnected { background : #4a1a1a ; color : #f87171 ; }
527+ .status-connecting { background : #4a3a1a ; color : #fbbf24 ; }
470528 </style >
471529</head >
472530<body >
473- <div id =" terminal" ></div >
531+ <div class =" wrapper" >
532+ <h1 >Web Terminal</h1 >
533+ <div id =" status" class =" connection-status status-connecting" >Connecting...</div >
534+ <div id =" terminal-container" ></div >
535+ </div >
536+
537+ <script src =" https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.js" ></script >
474538 <script >
475- const term = new Terminal ({
476- cursorBlink: true ,
477- fontSize: 14 ,
478- fontFamily: ' Menlo, Monaco, "Courier New", monospace'
479- });
480-
481- const fitAddon = new FitAddon.FitAddon ();
482- term .loadAddon (fitAddon);
483-
484- term .open (document .getElementById (' terminal' ));
485- fitAddon .fit ();
486-
487- // Connect to WebSocket
488- const socket = new WebSocket (' ws://localhost:8080/terminal' );
489- const attachAddon = new AttachAddon.AttachAddon (socket);
490- term .loadAddon (attachAddon);
491-
492- // Handle window resize
493- window .addEventListener (' resize' , () => fitAddon .fit ());
494-
495- // Handle disconnection
496- socket .onclose = () => {
497- term .write (' \r\n\x1b [31mConnection closed\x1b [0m\r\n ' );
498- };
499-
500- socket .onerror = (error ) => {
501- term .write (' \r\n\x1b [31mConnection error\x1b [0m\r\n ' );
502- };
539+ (function () {
540+ ' use strict' ;
541+
542+ var statusEl = document .getElementById (' status' );
543+
544+ function setStatus (status , message ) {
545+ statusEl .className = ' connection-status status-' + status;
546+ statusEl .textContent = message;
547+ }
548+
549+ function connect () {
550+ var wsProtocol = window .location .protocol === ' https:' ? ' wss:' : ' ws:' ;
551+ var wsHost = window .location .host || ' localhost:8080' ;
552+ var wsUrl = wsProtocol + ' //' + wsHost + ' /ws' ;
553+
554+ setStatus (' connecting' , ' Connecting to ' + wsUrl + ' ...' );
555+
556+ var socket = new WebSocket (wsUrl);
557+
558+ socket .onopen = function () {
559+ setStatus (' connected' , ' Connected' );
560+
561+ var term = new Terminal ({
562+ cols: 120 ,
563+ rows: 40 ,
564+ cursorBlink: true ,
565+ cursorStyle: ' block' ,
566+ fontFamily: ' "DejaVu Sans Mono", "Liberation Mono", "Courier New", monospace' ,
567+ fontSize: 14 ,
568+ theme: {
569+ background: ' #000000' ,
570+ foreground: ' #f0f0f0' ,
571+ cursor: ' #f0f0f0'
572+ }
573+ });
574+
575+ term .open (document .getElementById (' terminal-container' ));
576+
577+ socket .onmessage = function (event ) {
578+ if (event .type === ' message' ) {
579+ term .write (event .data );
580+ }
581+ };
582+
583+ term .onData (function (data ) {
584+ socket .send (JSON .stringify ({action: ' read' , data: data}));
585+ });
586+
587+ socket .onclose = function (event ) {
588+ setStatus (' disconnected' , ' Disconnected (code: ' + event .code + ' )' );
589+ term .write (' \r\n\x1b [31mConnection closed.\x1b [0m\r\n ' );
590+ };
591+
592+ socket .onerror = function (error ) {
593+ setStatus (' disconnected' , ' Connection error' );
594+ console .error (' WebSocket error:' , error);
595+ };
596+
597+ term .focus ();
598+ };
599+
600+ socket .onerror = function (error ) {
601+ setStatus (' disconnected' , ' Failed to connect' );
602+ console .error (' WebSocket connection error:' , error);
603+ };
604+ }
605+
606+ window .addEventListener (' load' , connect);
607+ })();
503608 </script >
504609</body >
505610</html >
506611```
507612
508- ### Serving Static Files
613+ ### Message Protocol
509614
510- ``` java
511- HttpTerminal http = HttpTerminal . builder()
512- .host(" 0.0.0.0" )
513- .port(8080 )
514- .webSocketPath(" /terminal" )
515- .staticFilesPath(" src/main/resources/static" ) // Serve HTML/JS/CSS
516- .connectionHandler(this :: handleConnection)
517- .build();
615+ The WebSocket uses JSON messages for communication:
616+
617+ ** Client to Server (input):**
618+ ``` json
619+ {"action" : " read" , "data" : " user input here" }
518620```
519621
622+ ** Server to Client (output):**
623+ Plain text with ANSI escape sequences for terminal rendering.
624+
520625## Connection Management
521626
522627### Connection Interface
@@ -611,21 +716,18 @@ public class MultiProtocolServer {
611716 .build();
612717
613718 // WebSocket on port 8080
614- HttpTerminal http = HttpTerminal . builder()
615- .port(8080 )
616- .webSocketPath(" /terminal" )
617- .connectionHandler(MultiProtocolServer :: handleConnection)
618- .build();
619-
719+ NettyWebsocketTtyBootstrap http = new NettyWebsocketTtyBootstrap ()
720+ .setPort(8080 );
721+
620722 // Start all servers
621723 ssh. start();
622724 telnet. start();
623- http. start();
624-
725+ http. start(MultiProtocolServer :: handleConnection );
726+
625727 System . out. println(" Servers started:" );
626728 System . out. println(" SSH: ssh -p 2222 user@localhost" );
627729 System . out. println(" Telnet: telnet localhost 2323" );
628- System . out. println(" WebSocket: http://localhost:8080/terminal " );
730+ System . out. println(" WebSocket: http://localhost:8080" );
629731
630732 Thread . currentThread(). join();
631733 }
@@ -656,15 +758,10 @@ public class MultiProtocolServer {
656758### Starting and Stopping
657759
658760``` java
659- // Start server
761+ // Start servers
660762ssh. start();
661763telnet. start();
662- http. start();
663-
664- // Check if running
665- if (ssh. isRunning()) {
666- System . out. println(" SSH server is running" );
667- }
764+ http. start(connectionHandler); // WebSocket requires handler
668765
669766// Stop server (closes all connections)
670767ssh. stop();
@@ -677,11 +774,15 @@ http.stop();
677774``` java
678775Runtime . getRuntime(). addShutdownHook(new Thread (() - > {
679776 System . out. println(" Shutting down servers..." );
680-
777+
681778 ssh. stop();
682779 telnet. stop();
683- http. stop();
684-
780+ try {
781+ http. stop(). get(5 , TimeUnit . SECONDS );
782+ } catch (Exception e) {
783+ // Handle timeout
784+ }
785+
685786 System . out. println(" Servers stopped" );
686787}));
687788```
0 commit comments