предисловие
Всех с наступающим Новым годом~ Из-за моей занятости я некоторое время не обновлялся (на самом деле я ленивый).веб-терминалЯ наступил на много ям и, наконец, понял все. Я хотел написать статью, чтобы отблагодарить сообщество, поэтому я просто сделал то, что сказал, и поехали~
Предварительное изучение 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вверх,порталвот, пройди мимоотличный👍Это самая большая поддержка для меня, значит, увидимся в следующий раз~