LoginSignup
2
0

More than 5 years have passed since last update.

Go言語でJavaScriptを使ってオリジナルなコマンドシェルを作る(Webインターフェースを追加)

Last updated at Posted at 2019-05-31

概要

今まではコンソールから入力するだけでしたが、いろいろ機能を追加してテストするときJavaScriptの入力が大変になってきます。load関数はありますが。そこで、今回はWebインターフェースを追加して楽に開発しましょう。Webポートを開いてサーバー側のJavaScriptが実行できるのでセキュリティに十分配慮してください。

の知識を前提としています。

実装

Webインターフェースを実装する前の準備

今まで作成したプログラムの一部をWebインターフェースに合わせて変更します。

jsRuntimeout io.Writerを追加します。これはjsprintの出力先がコンソールかブラウザかを決めるためです。

type jsRuntime struct {
    runtime   *goja.Runtime
    stringify goja.Callable
    program   *goja.Program
    out       io.Writer  //<==追加
}

func initialSettingの一部を変更します。

    //変更前
    js := &jsRuntime{runtime: rt} 

    //変更後
    js := &jsRuntime{runtime: rt, out: os.Stdout} 

さらに、jsprint関数内で fmt.Fprintfに変更します。//webがついているところが変更箇所です。

func (js *jsRuntime) jsprint(vals ...goja.Value) {
    rt := js.runtime
    format := "%v"
    for _, val := range vals {
        str, ok := val.Export().(string)
        if ok {
            fmt.Fprintf(js.out, format, str) //web
            format = " %v"
            continue
        }
        v, err := js.stringify(goja.Undefined(), val)
        if err != nil {
            rt.Interrupt(rt.NewGoError(err))
            return
        }
        fmt.Fprintf(js.out, format, v) //web
        format = " %v"
    }
    fmt.Fprintln(js.out) //web
}

Webインターフェースの実装

まず,func initialSettingでJavaScriptにGo言語のWebインターフェースの関数をセットします。

    rt.Set("web", js.execWebServer)

続いてexecWebServerや関連する関数です.

import (
    "bytes"
    "fmt"
    "net/http"
    "os"
)

var isExecWeb = false

func (js *jsRuntime) execWebServer(port int) {
    if isExecWeb {
        js.runtime.Interrupt("already executed")
        return
    }
    isExecWeb = true
    go js.webServer(port)
}

func (js *jsRuntime) webServer(port int) {
    http.Handle("/html/", http.StripPrefix("/html/", http.FileServer(http.Dir("html/"))))
    http.HandleFunc("/script", js.routeScript)
    http.ListenAndServe(fmt.Sprintf("localhost:%d", port), nil)
}

func (js *jsRuntime) routeScript(w http.ResponseWriter, r *http.Request) {
    js.out = w
    defer func() { js.out = os.Stdout }()
    bufferBody := new(bytes.Buffer)
    bufferBody.ReadFrom(r.Body)
    reqBody := bufferBody.String()
    val, err := js.runtime.RunScript("web", reqBody)
    if err == nil {
        js.jsprint(val)
    } else {
        js.jsprint(js.runtime.ToValue(fmt.Sprintf("%v\n", err)))
    }
}

JavaScriptからの引数はポート番号です。 カレントディレクトリの下にhtmlというディレクトリを作ってそこに必要なHTMLを置きます。JavaScriptを実行させるURLは/scriptとしました。
js.out = wでJavaScriptの表示がブラウザになります。処理終了時にはos.Stdoutに戻します。これでコンソールからもWebからも入力できます。ただし、JavaScriptのRuntimeは共有していますので、同時には実行しないでください。Webインターフェース同士の同時処理もしないように注意が必要です。

続いてHTMLです。ファイル名はscript.htmlとしてhtmlディレクトリに置きます。また、jQueryを使用しています。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>JavaScript Shell</title>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
</head>

<body>
    <form>
        <textarea cols="60" rows="5" id="query" spellcheck="false" wrap="off"></textarea>
    </form>

    <button id="ajax" onclick="postAjax()">run</button>
    <div id="output"></div>

    <script>
        function postAjax() {
            sendData = $('#query').val()
            $.ajax({
                url: '/script',
                type: 'POST',
                data: sendData
            }).done((data) => {
                ajaxSuccess(data);
            }).fail((data) => {
                ajaxFail(data);
            });
        }

        function ajaxSuccess(result) {
            document.getElementById("output").innerHTML = `<pre>${result}</pre>`
        }

        function ajaxFail(result) {
            document.getElementById("output").innerHTML = result
        }
    </script>
</body>
</html>

実行

go run main.go
> web(8080)
undefined
> 

ブラウザで実行した結果です

image.png

次のJavaScriptでHTMLを生成してそれをブラウザで表示させます。残念ながらHTMLをエンコードする関数は用意していないのでそのまま出力します。

script.html
document.getElementById("output").innerHTML = `<pre>${result}</pre>`

を次のように変更してHTMLを有効にします。

script.html
document.getElementById("output").innerHTML = result

下記のJavaScriptを実行しています。最後の""undefinedがでないように戻り値を空文字にしています。一度作成したScriptはファイルにしてブラウザからはloadで実行することもできます。

print ("<table border=1><th>A</th><th>B</th><th>C</th></tr>")
var asum = 0
var bsum = 0
excel("Book1.xlsx", "Sheet1").ForEachArray(function(ix, row) {
   print("<tr>")
   asum += row[0]
   bsum += row[1]
   row.forEach(function(cell){
     print("<td>", cell, "</td>")
   })
   print("</tr>")
})
print("<tr><td>",asum, "</tb><td>", bsum ,"</td><td>合計</td></tr>")
""

実行結果です。
image.png

2次元データの文字列にしてデータだけブラウザに返しブラウザ側でevalを実行、それからJavaScriptのデータとしてブラウザ側で処理することも可能でしょう。

まとめ

これで開発も楽になります。また、大量の出力データもコンソールよりはブラウザの方が見やすいです。
Web機能は実行できましたがWeb機能だけ止める方法はよくわかりませんでした。それらしいパッケージはありましたが試していません。なのでWeb機能を停止したいときは一度プログラムを終了してから再起動してください。これで十分だと思っています。

2
0
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
0