LoginSignup
5
2

More than 5 years have passed since last update.

gopher-lua で簡易 AWS Lambda っぽいものを作ってみる

Posted at

net/http で起動しているウェブサーバーに Lua で記述された関数を登録し、URL経由で登録した関数を呼び出せるようにしてみます。

登録するLuaの関数には、実装の都合上、以下の制約を設けます
1. 関数名は lambda とする
2. 関数は返り値を1つ返す

実装

Lua の関数を動的に登録するので goroutine を使い、その中でLua関数を呼び出します。
関数の呼び出しは channel で行います。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "strconv"

    "github.com/yuin/gopher-lua"
)

type httpRequest struct {
    W http.ResponseWriter
    R *http.Request
}

var (
    count      = 1 // 登録されたプロセス数
    processMap = make(map[string]chan httpRequest)
)

const (
    retNum   = 1        // 登録されるLuaスクリプトの返り値の数
    funcName = "lambda" // Luaスクリプトに登録される関数名
)

func execLuaProcess(w http.ResponseWriter, r *http.Request) {
    processNum := r.URL.Path[len("/lua/exec/"):]
    ch, ok := processMap[processNum]
    if !ok {
        fmt.Fprintf(w, "不明なプロセス番号です: %s", processNum)
        return
    }

    ch <- httpRequest{w, r}
    <-ch // Luaプロセスの完了を待つ
}

func registerLuaProcess(w http.ResponseWriter, r *http.Request) {
    L := lua.NewState()
    // defer L.Close() // TODO: どこで Close するのが適切か調べる

    luaScript, err := ioutil.ReadAll(r.Body)
    defer r.Body.Close()
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "%v", err)
        return
    }

    if err := L.DoString(string(luaScript)); err != nil {
        w.WriteHeader(http.StatusBadRequest)
        fmt.Fprintf(w, "%v", err)
        return
    }

    fn := L.GetGlobal(funcName)
    ch := make(chan httpRequest)

    go func() {
        for {
            httpReq := <-ch

            if err := L.CallByParam(lua.P{
                Fn:      fn,
                NRet:    retNum,
                Protect: true,
            }); err != nil {
                httpReq.W.WriteHeader(http.StatusInternalServerError)
                fmt.Fprintf(httpReq.W, "%v", err)
                ch <- httpReq
                continue
            }

            // 関数の返り値を取得する
            ret := L.Get(-1)
            L.Pop(1)

            fmt.Fprintf(httpReq.W, "Luaプロセスが %v を返しました", ret)
            ch <- httpReq
        }
    }()

    processMap[strconv.Itoa(count)] = ch
    fmt.Fprintf(w, "luaプロセスを %d 番に登録しました", count)

    count++
}

func main() {
    http.HandleFunc("/lua/register/", registerLuaProcess)
    http.HandleFunc("/lua/exec/", execLuaProcess)

    http.ListenAndServe(":8080", nil)
}

実行結果

  • Lua 関数を登録する
$ curl -X POST localhost:8080/lua/register/ -d 'lambda = function() return "hoge" end'
luaプロセスを 1 番に登録しました

$ curl -X POST localhost:8080/lua/register/ -d 'lambda = function() return math.random(1, 100) end'
luaプロセスを 2 番に登録しました
  • 登録した関数を呼び出してみる
$ curl localhost:8080/lua/exec/1                                                                  
Luaプロセスが hoge を返しました

$ curl localhost:8080/lua/exec/2
Luaプロセスが 82 を返しました

URL ごとに異なる関数が呼ばれていることが確認できました。

その他

実装の都合上、関数名と返り値を固定しましたが gopher-lua では ast が触れるみたいなので、関数名や返り値の数などは動的に取得できるかもしれません(未確認)

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