Привет, Хабр. Console chat отличная вещь, но для фронтендеров, а что если вы хотите такой же, но для бэкэнда. Если да, то эта статья для вас. Но какой инструмент часто используют в бэкенде? Правильно ssh, так что представляю sshchat
Как это будет выглядеть
Где-то на сервере крутится программа на ноде.
Как только кто-то хочет, подключится к чату он вводит:
ssh server -p 8022
После этого система спрашивает пароль и сверяет его с паролем в специальном файле. Если пароль совпал, то подключаем к чату(юзер получает 100 предыдущих сообщений и все остальные видят что он подключился)
Дальше он принимает сообщения других, и может написать своё.
Вот с сообщениями поинтереснее:
@box{@color(red){Red text in box}}
Отправит красный текст в коробке.
Приступим
Для работы с ssh мы будем использовать https://www.npmjs.com/package/ssh2
Для форматирования используем chalk и boxen
Так что установим их
npm i ssh2 chalk boxen
Теперь сам код одна из самых важных частей это парсер сообщений:
https://github.com/maximmasterr/ssh-server/blob/master/parserExec.js
// Подключаем chalk и boxen const chalk = require('chalk'); const boxen = require('boxen'); // Здесь прописаны методы которые мы сможем использовать через @ // Функции принимают 2 аргумента то что в скобках и текс в фигурных скобках let methods = { color: function(args, text) { return chalk.keyword(args)(text); }, bold: function(args, text) { return chalk.bold(text); }, underline: function(args, text) { return chalk.underline(text); }, hex: function(args, text) { return chalk.hex(args)(text); }, box: function(args, text) { return boxen(text, { borderStyle: 'round', padding: 1, borderColor: 'blueBright' }); } }; // Сам парсер function parseAndExecute(str) { let pos = 0; let stage = 0; let nS = ''; let bufs = ['', '', '', '']; let level = 0; while (pos < str.length) { let symbol = str[pos]; pos++; if (symbol == '\\' && '(){}@'.indexOf(str[pos]) !== -1) { bufs[stage] += str[pos]; pos++; continue; } if (stage == 0 && symbol == '@') { stage++; nS += bufs[0]; bufs[0] = ''; continue; } else if (stage >= 1) { if (symbol == '(') if (stage < 2) { stage = 2; } else { level++; } if (symbol == ')' && stage >= 2 && level > 0) level--; if (symbol == '{') if (stage != 3) { stage = 3; } else { level++; } if (symbol == '}') { if (level == 0) { bufs[3] += '}'; nS += methods[bufs[1]](bufs[2].slice(1, -1), parseAndExecute(bufs[3].slice(1, -1))); bufs = ['', '', '', '']; stage = 0; continue; } else { level--; } } } bufs[stage] += symbol; } return nS + bufs[0]; } module.exports.parseAndExecute = parseAndExecute;
Форматирование:
https://github.com/maximmasterr/ssh-server/blob/master/format.js
const chalk = require('chalk'); const { parseAndExecute } = require('./parserExec') // Стилизуем ник(Генерируем цвет и делаем жирным) function getNick(nick) { let hash = 0; for (var i = 0; i < nick.length; i++) hash += nick.charCodeAt(i) - 32; return chalk.hsv((hash + 160) % 360, 90, 90)(chalk.bold(nick)); } module.exports.format = function(nick, message) { const nickSpace = '\r ' + ' '.repeat(nick.length); nick = getNick(nick) + ': '; message = message.replace(/\\n/gm, '\n'); // Заменяем \n новыми строками message = parseAndExecute(message) // Парсим // Добавлям к каждой новой строке отступ message = message .split('\n') .map((e, i) => '' + (i !== 0 ? nickSpace : '') + e) .join('\n'); return nick + message; };
Методы для отправки сообщения всем пользователям и сохранения 100 сообщений:
https://github.com/maximmasterr/ssh-server/blob/master/broadcaster.js
let listeners = []; // Все пользователи let cache = new Array(100).fill('') // Кэш // Добавления и удаление подписчиков module.exports.addListener = write => listeners.push(write) - 1; module.exports.delListener = id => listeners.splice(id, 1); // Отправляем сообщение module.exports.broadcast = msg => { cache.shift() cache.push(msg) process.stdout.write(msg) listeners.forEach(wr => wr(msg)); } // Получаем кэш module.exports.getCache = ()=>cache.join('\r\033[1K')
Лобби, создание сервера и авторизация
https://github.com/maximmasterr/ssh-server/blob/master/lobby.js
const { Server } = require('ssh2'); const { readFileSync } = require('fs'); const hostKey = readFileSync('./ssh'); // Читаем ключ const users = JSON.parse(readFileSync('./users.json')); // Юзеры let connectionCallback = () => {}; module.exports.createServer = function createServer({ lobby }) { // Создаём сервер const server = new Server( { banner: lobby, // Баннер встречает до ввода пароля hostKeys: [hostKey] }, function(client) { nick = ''; client .on('authentication', ctx => { // Авторизация if (ctx.method !== 'password') return ctx.reject(); if (ctx.password !== users[ctx.username]) ctx.reject(); nick = ctx.username; ctx.accept(); }) .on('ready', function() { connectionCallback(client, nick); }); } ); return server }; module.exports.setConnectCallback = callback => { // Устанавливает колбэк при подключении connectionCallback = callback; };
Различные методы:
https://github.com/maximmasterr/ssh-server/blob/master/utils.js
const { createInterface } = require('readline'); module.exports.getStream = function(client, onStream, onEnd){ client // Получает стрим и клиента .on('session', function(accept, reject) { accept() .on('pty', accept => accept & accept()) .on('shell', accept => onStream(accept())); }) .on('end', () => onEnd()); } // Создаём коммуникатор module.exports.getCommunicator = function(stream, onMessage, onEnd){ let readline = createInterface({ // Интерфейс для считывания строк input: stream, output: stream, prompt: '> ', historySize: 0, terminal: true }) readline.prompt() readline.on('close', ()=>{ radline = null; onEnd() stream.end() }) readline.on('line', (msg)=>{ stream.write('\033[s\033[1A\033[1K\r') onMessage(msg) readline.prompt() }) // Метод для записи сообщения return msg=>{ stream.write('\033[1K\r' + msg) readline.prompt() } }
А теперь объединим
https://github.com/maximmasterr/ssh-server/blob/master/index.js
const { createServer, setConnectCallback } = require('./lobby'); const { getStream, getCommunicator } = require('./utils'); const { addListener, delListener, broadcast, getCache } = require('./broadcaster'); const { format, getNick } = require('./format'); // Функция создания сервера module.exports = function({ lobby = 'Hi' } = {}) { const server = createServer({ lobby }); setConnectCallback((client, nick) => { // Ожидание соединения console.log('Client authenticated!'); let id = null; getStream( // Получаем стрим client, stream => { const write = getCommunicator( // И интерфейс stream, msg => { if (msg == '') return; try { broadcast(format(nick, msg) + '\n'); // Как только получим сообщение, отправим его всем } catch (e) {} }, () => {} ); id = addListener(write); // Слушаем сообщения write('\033c' + getCache()); // Отправляем кэш broadcast(getNick(nick) + ' connected\n'); // Сообщаем о подключении }, () => { delListener(id); broadcast(getNick(nick) + ' disconnected\n') // Сообщаем об отключении } ); }); server.listen(8022); };
И финальный этап пример сервера:
const chat = require('.') chat({})
Так же в файле users.json описаны юзеры и их пароли
Выводы
Вот так можно написать не самый простой чат в ssh
Для такого чата не нужно писать клиент, он обладает возможностями оформления, и его может развернуть любой желающий
Что ещё можно сделать:
- Добавить возможность создания своих функций оформления
- Добавить поддержку markdown
- Добавить поддержку ботов
-
Отправка файлов по scp
Финальный репозиторий: https://github.com/maximmasterr/ssh-server
ссылка на оригинал статьи https://habr.com/ru/post/466633/
Добавить комментарий