使用socket.io打造簡易web-shell(node)

需求

在寫程序的時候,常常會打不少log,以監測程序或業務的運行狀況。如今有這樣一個需求,須要將業務執行狀況即一部分log輸出或所有輸出展現到前臺的可視化界面上。用戶在頁面上進行不一樣的任務操做,能夠直接看到相應的執行狀況。javascript

實現構想

基本思路

對當前進程進行監聽,每次有輸出事件時,則反饋到前臺html

實現工具

當前項目是用koa寫的,那就koa+socket.iojava

可參見socket.io的官方文檔,express等也是相似的用法。node

基本代碼

Client端

html部分web

...
<div id="result">
  運行結果
  <pre id="console"></pre>
</div>
...
<script src="/javascript/cmd.js"></script>
...

cmd.jsshell

/* global io,$,window */
/* eslint prefer-arrow-callback: 0 */
const elConsole = document.getElementById('console')
// 與Server鏈接
const socket = io.connect(location.host)
// 替換特殊字符
const escapeHtml = function(html) {
  return html.replace(/&/g, '&amp;')
    .replace(/>/g, '&gt;')
    .replace(/</g, '&lt;')
}

const output = function(data) {
  $('#console').append(`${escapeHtml(data)}\n`)
  elConsole.scrollTop = 9e5
}

// PubSub模型
// 監聽Server端觸發的output事件
socket.on('output', output)

socket.on('connect', function() {
  output('鏈接成功')
})

socket.on('disconnect', function() {
  output('鏈接已斷開\n')
})
// 可自定義任意事件,emit觸發便可
....

Server端

以前查找node的process相關資料時,發現了這樣一段話

那就監聽process.on('data',...)就好啦express

因而,參考網上一些代碼,貼出關鍵部分npm

// app.js
const app = require('koa')()

// 使用./public下的靜態文件
app.use(serve('./public'));
...
// 使用路由
app.use(router(app));
.....

// 這一行代碼必定要在最後一個app.use後面使用
var server = require('http').createServer(app.callback()),
    io = require('socket.io')(server);

....
app.get('/', function *(next) {
  yield this.render('index', { my: 'data' });
});
....

// npm install --save socket.io
const server = require('http').createServer(app.callback())
const io = require('socket.io')(server)
// 關鍵代碼 Socket.io的標準用法
io.on('connection', function(socket){
  const output = function(msg) {
    socket.emit('output', msg.toString())
  }
  process.stdout.on('data', output)
});

// 開啓服務器
server.listen(1337);
console.info('Now running on localhost:1337');

解釋一下bash

app.callback()服務器

返回一個可被http.createServer()接受的程序實例,也能夠將這個返回函數掛載在一個 Connect/Express 應用中

哈哈,這下大功告成了有沒有,來趕忙起一下server見證奇蹟!

發現問題

界面上除了「鏈接成功」四個大字,啥也沒有!看一下終端,嘩嘩——的滿屏輸出啊!

why??

這才發現儘管終端滿屏輸出, 但process並無監聽到任何data事件!爲何呢?

Console.prototype.log = function() {
  this._stdout.write(util.format.apply(this, arguments) + '\n');
};

可見,console.log底層調用了process.stdout.write,而這句調用並不會觸發process監聽的data事件!

那就簡單啦,每次log的時候,加一句process.stdout.emit('data', msg)就ok啦!

很好,咱們來寫一個本身的console.log吧(最簡單的喲)

// console.js

function log(msg){
  console.log(msg)
  process.stdout.emit('data', msg)
}
module.exports.log = log

function error(msg){
  console.error(msg)
  // error 可設置不向前臺輸出
  // process.stdout.emit('data', msg)
}
module.exports.error = error

function info(msg){
  console.info(msg)
  process.stdout.emit('data', msg)
}
module.exports.info = info

而後在app.js加上

global.myconsole = require('./console')
...
// 調用
myconsole.log('test')

ps:用法不少,隨意隨意啦!
來看一下結果

web-shell

需求解決啦,可是做爲一個web-shell, 怎麼能只有輸出,沒有輸入呢!在原有的基礎上加一些擴展就好啦!

Server端

對於每一個鏈接的客戶端,咱們能夠爲其單獨開一個shell,以供交互

// app.js
...
const spawn = require('child_process').spawn

io.on('connection', (socket) => {
  const shell = spawn('bash')
  const output = function(msg) {
    socket.emit('output', msg.toString())
  }
  shell.stdout.on('data', output)
  shell.stderr.on('data', output)
  shell.on('close', () => {
    output('Exit')
    socket.disconnect(true)
  })
  // 監聽輸入事件
  socket.on('input', (data) => {
    shell.stdin.write(data)
  })
  socket.on('disconnect', () => {
    shell.kill('SIGKILL')
  })
  myconsole.debug('控制檯鏈接成功')
})

Client端

html

<input type='text' id='cmd' placeholder="輸入命令"/>

cmd.js

...
window.unload = function() {
  // 離開頁面則關閉該進程
  socket.emit('disconnect')
}

elCmd.addEventListener('keypress', function(e) {
  if (e.keyCode === 13) {
    const data = `${elCmd.value}`
    output(`$ ${data}`)
    socket.emit('input', `${data}\n`)
    elCmd.value = ''
  }
})

效果以下圖

其餘

以上例子中,使用
shell.stdout.pipe(process.stdout)能夠傳遞shell的輸出到父進程!
反着則不行,好像不是同一個類型的對象!

也是東拼西湊啦,還有不少完善的空間!

譬如,界面輸入命令對ctrl+c等快捷鍵的支持,子進程父進程之間的通訊,界面結果顯示顏色高亮,console.js拓展等!

另外,是否是也有辦法能直接監聽到process.stdout.write呢,思考.......

相關文章
相關標籤/搜索