WebTerminal function implementation and code demonstration (based on Golang and Xterm.js)

Article directory

  • Purpose
  • Program Description
  • Implementation process and code demonstration
    • Front-end page (Xterm.js)
    • Backend service (Golang)
  • compile and test
  • Summarize

Purpose

WebTerminal is a more interesting feature that allows us to interact with Linux devices in the browser without specialized software. This article will give a simple description and demonstration of this function.

The routine address is as follows:
https://github.com/NaisuXu/web-terminal-demo-with-golang-and-xterm

Description of the plan

The name of WebTerminal in the market is still quite confusing. In most cases, it is also mixed with WebSSH. There are some differences between the two in essence. Here are some common solutions that mix the two together:


The above situation is commonly used in operation and maintenance management, etc., and multiple devices or servers can be managed in the browser.


The above situation is also commonly used in operation and maintenance management.


The above situation is mainly used for the operation of the device itself through the Web UI, and for more advanced operations, it can be performed directly through the terminal.

This article implements the last method. In addition, because I mainly use it in embedded Linux devices, the use of Golang in Web Server is estimated to be the best in terms of adaptability and development efficiency.

Implementation process and code demonstration

Front page (Xterm.js)

The front-end implements the Terminal function mainly using the Xterm.js library. The official page is as follows:
https://xtermjs.org/

The terminal window in VS Code uses this library, and the backend uses node-pty.

When Node.js is installed, create a new project directory and enter it, then initialize the project and download the xterm library:

npm init -y
npm install xterm

Create a new index.html file with the following content:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
    <script src="node_modules/xterm/lib/xterm.js"></script>
</head>
<body>
    <div id="terminal"></div>
    <script>
        const term = new Terminal();
        term.open(document.querySelector('#terminal'));
    </script>
</body>
</html>

At this time, you can test the function of Xterm.js, you can use the write method of the Terminal object to output content to the terminal window, and use the onData method Keyboard operations in the terminal window can be received:

The above is the core thing of the front-end page, and then only need to deal with the data interaction with the back-end service. The WebSocket method is used here, and the completed code is as follows:

<!DOCTYPE html>
<html>

<head>
    <title>WebTerminal</title>
    <link rel="stylesheet" href="./node_modules/xterm/css/xterm.css" />
    <script src="./node_modules/xterm/lib/xterm.js"></script>
</head>

<body>
    <div style="width: 736px; height: 408px;">
        <!-- The current version of Xterm 5.1.0 default serial port size is 24x80 -->
        <div id="terminal"></div>
    </div>
    <script>
        const term = new Terminal();

        term.open(document.querySelector('#terminal')); // mount

        const socket = new WebSocket(`ws://${<!-- -->window.location.host}/webterminal`); // create a WebSocket connection

        term.onData((data) => {<!-- --> // There is input data in the webpage xterm window
            // console.log('term.onData:', data);
            socket.send(data); // send to server via WebSocket
        });
        
        socket.onmessage = (event) => {<!-- --> // received a WebSocket message from the server
            // console.log('socket.onmessage:', event.data);
            term.write(event.data); // write data to xterm object
        };
    </script>
</body>

</html>

Backend service (Golang)

In the environment where Go is installed, in the same directory as before, use the following command to initialize the project:

go mod init webterminal

Create a new main.go file with the following contents:

package main

import (
"embed"
"net/http"
"os/exec"

"github.com/creack/pty"
"github.com/olahol/melody"
)

//go:embed index.html node_modules/xterm/css/xterm.css node_modules/xterm/lib/xterm.js
var content embed.FS

func main() {<!-- -->
c := exec.Command("sh") // system default shell interactive program
f, err := pty.Start(c) // pty is used to call the virtual terminal that comes with the system
if err != nil {<!-- -->
panic(err)
}

m := melody.New() // melody is used to realize the WebSocket function

go func() {<!-- --> // process messages from the virtual terminal
for {<!-- -->
buf := make([]byte, 1024)
read, err := f.Read(buf)
if err != nil {<!-- -->
return
}
// fmt.Println("f.Read: ", string(buf[:read]))
m.Broadcast(buf[:read]) // Send data to the web page
}
}()

m.HandleMessage(func(s *melody.Session, msg []byte) {<!-- --> // Handle messages from WebSocket
// fmt.Println("m.HandleMessage: ", string(msg))
f.Write(msg) // write the message to the virtual terminal
})

http.HandleFunc("/webterminal", func(w http.ResponseWriter, r *http.Request) {<!-- -->
m.HandleRequest(w, r) // When visiting /webterminal, it will be handed over to melody for processing
})

fs := http.FileServer(http.FS(content))
http.Handle("/", http.StripPrefix("/", fs)) // Set static file service

http.ListenAndServe("0.0.0.0:22333", nil) // Start the server, visit http://local (server) IP address: 22333/ for testing
}

The code is relatively simple, and the core function is to call the virtual terminal in the system, and then conduct two-way communication with the webpage through WebSocket.

The above code is only used for functional testing. The problem with this code is that it does not process each client separately, so when multiple web pages are opened, the operations will respond synchronously.

Use the following command to install related dependencies:

go mod tidy

Compile and test

Create a build.sh file in the project directory, the file content is as follows:

#!/bin/sh
GOOS=linux GOARCH=amd64 go build -o webterminal_linux_x86-64
GOOS=linux GOARCH=arm GOARM=7 go build -o webterminal_linux_armv7
GOOS=linux GOARCH=arm GOARM=5 go build -o webterminal_linux_armv5

The following three lines of commands are used to compile and generate programs for the three platforms, and you can also write them according to your needs.

Use the following command to compile (note that you need to use git-bash and other operations in the window):

# chmod +x ./build.sh
./build.sh

Copy the compiled program to the corresponding platform to test.

linux_x86-64 (Ununtu 22.04 AMD Ryzen 5 PRO 4650U):

linux_armv5 (NUC980 Linux buildroot 5.10.103 + armv5tejl GNU/Linux):

The linux_armv7 platform is not tested here. If possible, you can find a Raspberry Pi (4B) for testing.

Summary

The realization of WebTerminal functions based on Golang and Xterm.js is relatively simple in principle, but in actual application, it is more necessary to optimize the functions and usage according to the requirements.