предисловие
Всех с наступающим Новым годом~ Из-за моей занятости я некоторое время не обновлялся (на самом деле я ленивый).веб-терминалЯ наступил на много ям и, наконец, понял все. Я хотел написать статью, чтобы отблагодарить сообщество, поэтому я просто сделал то, что сказал, и поехали~
Предварительное изучение xterm.js
знать, что нужно сделатьweb-terminal, первое, что нужно сделать, это изучить конкретные технологии, необходимые в Интернете, и, наконец, найтиxterm.jsдля большинстваweb-terminalраствор, знаменитыйvscodeОн все еще используется, и кажется, что надежность все еще гарантирована. Итак, я с радостью постучалОфициальный сайтНаходитьdemo, на первый взгляд хороший парень выглядит довольно просто, просто установите его и инициализируйте экземпляр следующим образом:
npm install xterm
<!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>
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>
</body>
</html>
Поскольку наш проект основан наReact, поэтому я собираюсь использоватьcreate-react-appзаписыватьdemoПопробуйте, после еды скопируйте пример с официального сайтаdemo, а затем запустите его, чтобы увидеть эффект.
Затем страница выходит из терминального стиля следующим образом:
Я собираюсь написать персонажей, чтобы увидеть эффект, молодец. . Я не могу в него войти, я когда-то подозревал, что это яdemoКопия неправильная После тщательного сравнения выясняется, что она действительно правильная? ?
Затем я поискал документацию и обнаружил, что вход все равно нужно вызыватьapiЯ впервые вижу пример эмоционального официального сайта, который нельзя запустить напрямую.
скорректировал код
term.open(document.getElementById("terminal"));
term.write("Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ");
+ term.onData((val) => {
+ term.write(val);
+ });
Ввод, наконец, в порядке, но снова возникает новая проблема, и сообщается об ошибке, как только я ее удаляю.
И как только вводишь, курсор возвращается к началу, этому. . Я не мог не погрузиться в глубокие размышления, вернулся к документации для повторного поиска и обнаружил, что документацияonDataописание
contains real string data with any valid Unicode codepoint, thus the payload should be treated as UTF-16/UCS-2. For OS interaction this data should be converted to UTF-8 bytes (automatically done by
node-pty). If you need legacy encoding support, see below.
оказалосьonDataвозвращает всеUTF-16/UCS-2Кодирование, пусть система распознает вывод какUTF-8Кодирование, неудивительно, что у меня проблемы с прямым вводом, и мне приходится самому крутить кодировку... Это сложно для меня, мне что, столько ключей разбирать?
К счастью, чиновник предложил решение, которое заключается в использованииnode-ptyВыполнить автоматический разбор. Способ использования тоже очень простой, на официальном сайте есть следующий код
pty.onData(recv => terminal.write(recv));
terminal.onData(send => pty.write(send));
дело в том, чтобы позволитьonDataвернутьUTF-16/UCS-2для строкиnode-ptyРазобрать в системно читаемыйUTF-8Закодированные строки для завершения ввода, очевидно, нам нужно создать связь между ними, положитьxterm.jsКогда интерфейс рендеринга графики браузера,node-ptyКогда сервер прослушивает инструмент ввода и транскодирования, он может передатьwebsocketЧтобы связать отношения между двумя сторонами, кажется возможным!
Разбирать сигналы ввода с клавиатуры с помощью node-pty
Поскольку вы знаетеnode-ptyЕго можно разобрать, нам его сначала нужно установить, по описанию на официальном сайте устанавливаются разные системыnode-ptyТребуются разные препараты, что понятно, поскольку разные системы будут иметь разные отличия.
Linux/Ubuntu
sudo apt install -y make python build-essential
Node.JS 10+
macOS
Xcode is needed to compile the sources, this can be installed from the App Store.
Windows
npm install --global --production windows-build-tools
Windows SDK - only the "Desktop C++ Apps" components are needed to be installed
Node.JS 10+
После установки создадим наши серверные файлыserver.js, для сокаexpress express-wsстроитьnodeобслуживать и включатьwebsocketСлужить.
const express = require("express");
const expressWs = require("express-ws");
const app = express();
expressWs(app);
app.listen(4000, "127.0.0.1");
тогда присоединяйтесьnode-ptyисходный код.
const express = require("express");
const expressWs = require("express-ws");
const app = express();
expressWs(app);
const pty = require("node-pty");
const os = require("os");
const shell = os.platform() === "win32" ? "powershell.exe" : "bash";
const term = pty.spawn(shell, ["--login"], {
name: "xterm-color",
cols: 80,
rows: 24,
cwd: process.env.HOME,
env: process.env,
});
app.ws("/socket", (ws, req) => {
term.on("data", function (data) {
ws.send(data);
});
ws.on("message", (data) => {
term.write(data);
});
ws.on("close", function () {
term.kill();
});
});
В реальном сценарии также будут сценарии, когда несколько терминалов работают вместе, поэтому нас явно не устраивает инициализация сразу после запуска сервера.Что нам делать?
После некоторых размышлений.. вот оно! Идея сока в том, что когда клиент инициализирует экземпляр терминала, он инициализирует и серверptyНапример, разные терминалы инициализируют разныеptyнапример, черезpidЧтобы различать, чтобы, если есть сценарий расширения нескольких терминалов, он также мог быть удовлетворен.
Сторона клиента отправляет запрос на инициализацию на сторону сервера, и сторона сервера инициализируетсяptyэкземпляр, возвращает текущий экземплярpid, а затем клиент и сервер каждый разwebsocketБерите с собой при общенииpid, сервер проходитpidчтобы получить соответствующийptyНапример, вернуть проанализированное значение клиенту, таким образом реализуя мультитерминальный сценарий!
Измените наш предыдущий код
...
const termMap = new Map(); //存储 pty 实例,通过 pid 映射
function nodeEnvBind() {
//绑定当前系统 node 环境
const term = pty.spawn(shell, ["--login"], {
name: "xterm-color",
cols: 80,
rows: 24,
cwd: process.env.HOME,
env: process.env,
});
termMap.set(term.pid, term);
return term;
}
//服务端初始化
app.post("/terminal", (req, res) => {
const term = nodeEnvBind(req);
res.send(term.pid.toString());
res.end();
});
app.ws("/socket/:pid", (ws, req) => {
const pid = parseInt(req.params.pid);
const term = termMap.get(pid);
term.on("data", function (data) {
ws.send(data);
});
ws.on("message", (data) => {
term.write(data);
});
ws.on("close", function () {
term.kill();
termMap.delete(pid);
});
});
Веб-сокет для подключения клиента
Сервер готов! Далее мы начинаем писать клиентский код, клиент должен создатьwebsocketсоединять.
const socketURL = "ws://127.0.0.1:4000/socket/";
const ws = new WebSocket(socketURL);
Этого недостаточно, нам также нужно получить серверptyпримерpid, как уникальный идентификатор соединения, это просто, достаточно получить его прямо через интерфейс.
import axios from "axios";
...
//初始化当前系统环境,返回终端的 pid,标识当前终端的唯一性
const initSysEnv = async (term: Terminal) =>
await axios
.post("http://127.0.0.1:4000/terminal")
.then((res) => res.data)
.catch((err) => {
throw new Error(err);
});
const pid = await initSysEnv(term),
ws = new WebSocket(socketURL + pid);
xterm.jsПредоставляет возможность расширения самого пакета, здесь мы используем один из его пакетов расширенияxterm-addon-attach, это может помочь нам автоматически иwebsocketВзаимодействуйте, спасите нас, написав это сами.
Примечание. Для xterm-addon-attach требуется xterm.js v4+.
import { AttachAddon } from "xterm-addon-attach";
...
attachAddon = new AttachAddon(ws);
term.loadAddon(attachAddon);
Это завершает клиентский код~
Давайте начнем и посмотрим.
Фактически междоменный. . Что ж, тогда давайте добавим анти-междоменный код на сервер.
// //解决跨域问题
app.all("*", function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Content-Type");
res.header("Access-Control-Allow-Methods", "*");
next();
});
Перезапустите сервер, чтобы увидеть эффект~
Видно, что мы успешно побежали, и сок прошел
web-terminalУспешно авторизовались дома удаленномалиновый пирог, похоже, хороший опыт
Суммировать
Как видно из количества кода, реализующегоweb-terminalЭто не особо сложно, главное идея. Для тех, кто хочет исходный код, я передал кодgithubвверх,порталвот, пройди мимоотличный👍Это самая большая поддержка для меня, значит, увидимся в следующий раз~