ブラウザから外部プログラムを起動するサーバー

## 概要

ブラウザから外部プログラムを起動することはブラウザ単体では(拡張機能を使っても)出来ないので、node.jsで外部プログラムを起動するサーバーを作ってみた。

クエリ文字列でコマンドを渡すのでブックマーク機能を使えば簡単なランチャーにもなります。ただし、アイコンはデフォルトのままなので、変えたいなら[拡張機能を使う必要があります。](https://outkura.com/916.html/)

## ソース

```Executer.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');
```

起動は

```bat

> node Executer.js 8888
```

起動した後ブラウザから``` http://localhost:8888 ```に接続すればしょぼいコマンド入力フォームが出てきますのでコマンドを入力します。

アドレスバーに``` http://localhost:8888?command=[コマンド文字列] ```と入力すれば直接実行できます。この形式でブックマークすることもできます。

起動に成功すれば自動的に前のページに戻ります。spawnでエラーが出たり、エラー出力があった場合は内容が赤字で表示されて前のページに戻りません。

サーバーの終了はCtrl-Cで。

## 外部からサーバーを(強制)終了させる

ポート番号がわかっていればnetstatコマンドでポートを確保しているプロセス番号がわかるので、それを元にkillすることで正常終了させる仕組みがないサーバーでも外部からサーバーを強制終了させることができるらしい。
下記はWindowsが対象だが、たぶんLinuxでも同じようなことは出来ると思う。

### 参考:

* [ポートを握っているプロセスを見たい時](https://qiita.com/ionis_h/items/21cd094136c45ad98b44)
* [Windowsで、特定のポート番号でLISTENINGしているプロセスを強制終了させる方法](https://qiita.com/opengl-8080/items/38f42dbdc1aea7c2f229)
* [Node.jsでport 3000のプロセスを探してkillするDOS バッチファイル(Windows10)](https://qiita.com/torisan_/items/7971982e80564d8bd3d7)


```portkill.bat
@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できる。

## ブラウザと連動する

いろいろやり方があるような気がしないでもないですが、一番手っ取り早いのは

* 先にサーバーを別コンソールで起動しておいて
* コンソールでブラウザを起動すると終了するまでコンソールに制御が戻らないことを利用して終了を待ち
* ブラウザが終了してコンソールに制御が戻ったらサーバーも終了させる

という動作をするバッチファイルを作ることでしょうか…

```start.bat
@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

```