概要
ブラウザから外部プログラムを起動することはブラウザ単体では(拡張機能を使っても)出来ないので、node.jsで外部プログラムを起動するサーバーを作ってみた。
クエリ文字列でコマンドを渡すのでブックマーク機能を使えば簡単なランチャーにもなります。ただし、アイコンはデフォルトのままなので、変えたいなら拡張機能を使う必要があります。
ソース
var http = require('http');
var url = require('url');
var proc = require('child_process');
//コマンドラインからポート番号の設定、デフォルトは8888
var port = process.argv[2] || 8888;
//ヘッダHTMLの出力
var header = function(res, title) {
res.write(`
<html>
<head>
<title>${title}</title>
</head>
<body>
`);
};
//フォームHTMLの出力
var form = function(res) {
res.write(`
<form action="http://localhost:${port}" method="GET">
Command:<input type="text" name="command" value="" />
<input type="submit" value="Execute!">
</form>
`);
};
//フッターHTMLの出力
var footer = function(res) {
res.write(`
</body>
</html>
`);
res.end();
};
//サーバー起動
http.createServer(function (req, res) {
if(req.method === 'GET'){
//キャッシュしない設定でHTTPヘッダを出力
res.writeHead(200, {'Content-Type': 'text/html', 'Cache-Control': 'private, no-cache, no-store, must-revalidate'});
var query = url.parse(req.url, true).query;
var out = '';
var iserr = false
//HTTP Queryからコマンド文字列を取得
if(typeof query.command !== 'undefined' && query.command.length > 0) {
//クエリにコマンド文字列が設定されていればspawnで起動
header(res, query.command);
res.write('<pre id="output" style="color:red">');
var spwn = proc.spawn(query.command, [], { shell: true, env: process.env });
//spawnのエラーイベント・エラー出力があればそのままレスポンスとして出力
spwn.on('error', (err) => {
iserr = true;
res.write(data);
console.log(data);
});
spwn.stdout.on('data', (data) => {
//標準出力は出力しない
//res.write(data);
});
spwn.stderr.on('data', (data) => {
iserr = true;
res.write(data);
});
//2秒後にフッタを出力して切断(起動したアプリはそのまま)
setTimeout(() => {
res.write('</pre>');
//エラーがなければそのまま前のページに戻る
if(!iserr) res.write('<script>history.back();</script>');
footer(res);
}, 2000);
} else {
//コマンド文字列が設定されていなければフォームを出力して切断
header(res, 'Node Executer');
form(res);
footer(res);
}
} else {
//GETメソッド以外は404エラーを返す
res.writeHead(404, {'Content-Type': 'text/plain'});
res.write('Not Found');
res.end();
}
}).listen(port, '127.0.0.1');
起動は
> node Executer.js 8888
起動した後ブラウザからhttp://localhost:8888
に接続すればしょぼいコマンド入力フォームが出てきますのでコマンドを入力します。
アドレスバーにhttp://localhost:8888?command=[コマンド文字列]
と入力すれば直接実行できます。この形式でブックマークすることもできます。
起動に成功すれば自動的に前のページに戻ります。spawnでエラーが出たり、エラー出力があった場合は内容が赤字で表示されて前のページに戻りません。
サーバーの終了はCtrl-Cで。
外部からサーバーを(強制)終了させる
ポート番号がわかっていればnetstatコマンドでポートを確保しているプロセス番号がわかるので、それを元にkillすることで正常終了させる仕組みがないサーバーでも外部からサーバーを強制終了させることができるらしい。
下記はWindowsが対象だが、たぶんLinuxでも同じようなことは出来ると思う。
参考:
- ポートを握っているプロセスを見たい時
- Windowsで、特定のポート番号でLISTENINGしているプロセスを強制終了させる方法
- Node.jsでport 3000のプロセスを探してkillするDOS バッチファイル(Windows10)
@echo off
set __KILLPID=
set __TEMPPID=
if "%1"=="" (
echo 使い方: portkill.bat [ポート番号]
goto end
)
for /F "delims=" %%i in ('netstat -aon ^| findstr /r 0\.[01]:%1[^^^^0-9]') do set __TEMPPID=%%i
for %%a in (%__TEMPPID%) do (
set __KILLPID=%%a
)
if not defined __KILLPID (
echo %1番ポートを待ち受けているプログラムが見つかりませんでした。
goto end
)
echo %__KILLPID%
taskkill /pid %__KILLPID% /F
:end
set __KILLPID=
set __TEMPPID=
こんな感じのバッチファイルを作っておけば外部から任意のポートを待ち受けているプログラムをkillできる。
ブラウザと連動する
いろいろやり方があるような気がしないでもないですが、一番手っ取り早いのは
- 先にサーバーを別コンソールで起動しておいて
- コンソールでブラウザを起動すると終了するまでコンソールに制御が戻らないことを利用して終了を待ち
- ブラウザが終了してコンソールに制御が戻ったらサーバーも終了させる
という動作をするバッチファイルを作ることでしょうか…
@if not "%~0"=="%~dp0.\%~nx0" start /min cmd /c,"%~dp0.\%~nx0" %* & goto :eof
@echo off
start /min cmd /c node Executer.js 8888
C:\Program Files\Google\Chrome\Application\Chrome.exe
call portkill.bat 8888