概要
このエントリは、Goを使い、外部コマンドを呼び出すこと、それをHTTPリクエスト越しに実行してみることをやってみたので、メモとして残すものです。コンテナにもできます。
表題のことをやってみようかなと思った方への参考の一つとして紹介します。
前提
想定読者
- Go言語で、"A tour of Go"を少しやったことがある人
利用するもの
以下を利用します
- HTTPサーバとして、gin
- 動作環境は*nix系(コマンド実行)
ソース
リリースv0.1.0がGitHubにあります。
https://github.com/hrkt/cmd-exec-server/releases/tag/0.1.0
ベースとなるもの
以前書いたQiitaのエントリ「Go-lang+GinでAPIサーバを作る一歩目~EHW2018「アプリ②」」で作ってあったものをforkしました。
今できること
- app-settings.jsonを編集して
see: "app-settings.json" paragraph in this README.
- サーバを実行して
$ make run
- サーバ側でコマンドを実行するため、ボディに標準入力を渡し、HTTP POST リクエストを飛ばすと、標準出力がJSONファイルの中の文字列として返ってきます。
(バッファサイズは2048byteに絞ってあります)
$ curl -X POST localhost:8080/api/exec -d "some input"
{"result":"SOME INPUT"}
(で、README.mdにあるように"make build-container"すると、コンテナになります)
書いたもの
Router
"/api/exec"でコマンドを実行できるように指定しています。
POSTリクエストを受け取り、リクエストのボディ部をstringとして格納しています。
(ここのバッファは今は固定長。2048byte。)
func setupRouter() *gin.Engine {
router := gin.Default()
// Global middleware
router.Use(gin.Logger())
// Routing
router.StaticFile("/", "./index.html")
router.POST("/api/exec", func(ctx *gin.Context) {
buf := make([]byte, RequestBodyBufferSize)
n, _ := ctx.Request.Body.Read(buf)
body := string(buf[0:n])
res := executeIt(body)
ctx.JSON(200, gin.H{
"result": res,
})
})
return router
}
外部コマンドの実行
app-config.jsonで指定したコマンドを実行します。
Go-langのos/execを使います。
(今の実装では出力を区切っていないため、出力が多いコマンドだとサーバが止まるでしょう)
func executeIt(requestBody string) string {
cmd := exec.Command(appConfig.Command, appConfig.Arguments[:]...)
cmd.Stdin = strings.NewReader(requestBody)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
return out.String()
}
おわりに
このエントリでは、Goで外部コマンドを呼び出すのをHTTPリクエスト越しに実行してみることを紹介しました。何かのご参考になれば幸いです。
補足:エントリを書くきっかけ
Pythonで、"$ python -m CGIHTTPServer"などでお手軽サーバを作るような感じをGoでやったらどうなるかなと思って書いてみました。