LoginSignup
2
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-13

概要

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

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

ソース

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');

起動は


> node Executer.js 8888

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

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

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

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

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

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

参考:

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1