diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 9128a4f..b1ab588 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -31,8 +31,15 @@ jobs: go-version: ${{ env.GoVersion }} - name: Download dependency - run: make dep + run: go mod download - name: Build run: | - make build + # spread the work across 2 processes + build1=$! + go build -tags aliyun + build2=$! + go build -tags tencent + + wait $build1 + wait $build2 diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 8a8aab9..d7b5937 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -44,11 +44,18 @@ jobs: go-version: ${{ env.GoVersion }} - name: Download dependency - run: make dep + run: go mod download - name: Build run: | - make all + # spread the work across 2 processes + build1=$! + go run _script/build.go aliyun + build2=$! + go run _script/build.go tencent + + wait $build1 + wait $build2 - name: Create Release if: github.event_name != 'release' diff --git a/Makefile b/Makefile deleted file mode 100644 index 76cb2d2..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# flags -targets?=aliyun tencent # default to all -LDFLAGS=-ldflags "-s -w -buildid=" - -# make -all: dep build - -dep: - @echo Downloading dependencies... - @go mod download - -build: - @echo Building... - @for target in ${targets}; do \ - GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -tags $${target} -trimpath -o bootstrap; \ - zip -rq $${target}-serverless.zip bootstrap; \ - done - @rm -f bootstrap diff --git a/_script/build.go b/_script/build.go new file mode 100644 index 0000000..fc3279e --- /dev/null +++ b/_script/build.go @@ -0,0 +1,85 @@ +package main + +import ( + "archive/zip" + "fmt" + "io" + "os" + "os/exec" +) + +func main() { + var targets []string + if len(os.Args) == 1 { + targets = []string{"aliyun", "tencent"} + } else { + targets = os.Args[1:] + } + os.Setenv("GOOS", "linux") + os.Setenv("GOARCH", "amd64") + for _, target := range targets { + var out string + switch target { + case "aliyun": + out = "main" + case "tencent": + out = "bootstrap" + default: + panic("not supported platform") + } + cmd := exec.Command("go", + "build", + "-trimpath", + "-ldflags", "-s -w -buildid=", + "-tags", target, + "-o", out, + ) + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Printf("%s\n", string(output)) + panic(err) + } + err = zipFile(target+"-serverless.zip", out) + if err != nil { + fmt.Printf("%s\n", string(output)) + panic(err) + } + os.Remove(out) + } +} + +func zipFile(output, file string) (err error) { + var out *os.File + out, err = os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return + } + defer func() { + out.Close() + if err != nil { + os.Remove(output) + } + }() + w := zip.NewWriter(out) + var writer io.Writer + header := &zip.FileHeader{ + Name: file, + Method: zip.Deflate, + } + header.SetMode(0755) // set executable permission + writer, err = w.CreateHeader(header) + if err != nil { + return + } + var in *os.File + in, err = os.Open(file) + if err != nil { + return + } + defer in.Close() + if _, err = io.Copy(writer, in); err != nil { + return + } + err = w.Close() + return +} diff --git a/aliyun.go b/aliyun.go index 6950cba..6474054 100644 --- a/aliyun.go +++ b/aliyun.go @@ -3,10 +3,11 @@ package main import ( - "encoding/json" "fmt" + "io" "net/http" "os" + "strings" ) type timerTrigger struct { @@ -16,45 +17,54 @@ type timerTrigger struct { } const ( - fcStatus = "X-Fc-Status" + fcStatus = "X-Fc-Status" + fcRequestId = "X-Fc-Request-Id" + apiVersion = "2020-11-11" + contentType = "text/plain" ) func regist() (handler, error) { - port := os.Getenv("FC_SERVER_PORT") - if port == "" { - port = "9000" - } + address := os.Getenv("FC_RUNTIME_API") + endpoint := fmt.Sprintf("http://%s/%s/runtime/invocation/", address, apiVersion) return &aliyun{ - port: port, + endpoint: endpoint, + client: http.Client{}, }, nil } type aliyun struct { - port string + endpoint string + ua string + client http.Client +} + +func (a aliyun) Next() (body io.ReadCloser, reqID string, err error) { + var resp *http.Response + resp, err = http.Get(a.endpoint + "next") + if err == nil { + body = resp.Body + reqID = resp.Header.Get(fcRequestId) + } + return } -func (a aliyun) ListenAndServe(punch func(payload string) error) error { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - requestId := r.Header.Get("X-Fc-Request-Id") - fmt.Printf("FC Invoke Start RequestId: %s\n", requestId) - defer fmt.Printf("FC Invoke End RequestId: %s\n", requestId) - - t := &timerTrigger{} - err := json.NewDecoder(r.Body).Decode(t) - - if err != nil { - Fatal.Log("error payload format\n") - w.Header().Set(fcStatus, "404") - w.Write([]byte("error payload format")) - return - } - err = punch(t.Payload) - if err != nil { - w.Header().Set(fcStatus, "404") - fmt.Fprintf(w, "Punch Failed: %s", err.Error()) - } else { - w.Write([]byte("success")) - } - }) - return http.ListenAndServe(":"+a.port, nil) +func (a aliyun) ReportSuccess(id string) { + res, err := http.DefaultClient.Post(a.endpoint+id+"/response", contentType, http.NoBody) + if err == nil { + res.Body.Close() + } else { + Error.Log(err.Error() + "\n") + } +} + +func (a aliyun) ReportError(msg string, id string) { + res, err := http.Post(a.endpoint+id+"/error", + contentType, + strings.NewReader(msg), + ) + if err == nil { + res.Body.Close() + } else { + Error.Log(err.Error() + "\n") + } } diff --git a/main.go b/main.go index 12064cc..0ed13b2 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "encoding/json" "errors" + "io" "os" "strings" "time" @@ -11,7 +13,9 @@ import ( ) type handler interface { - ListenAndServe(punch func(payload string) error) error + Next() (body io.ReadCloser, reqID string, err error) + ReportError(msg string, id string) + ReportSuccess(id string) } func main() { @@ -19,9 +23,7 @@ func main() { if err != nil { os.Exit(1) } - if err = h.ListenAndServe(punch); err != nil { - os.Exit(2) - } + startServe(h) } func init() { @@ -30,6 +32,31 @@ func init() { } } +func startServe(handler handler) { + for { + body, id, err := handler.Next() + if err != nil { + Error.Log("get trigger payload failed, err: %s\n", err.Error()) + continue + } + t := &timerTrigger{} + err = json.NewDecoder(body).Decode(t) + body.Close() // close body + if err != nil { + msg := "parse request body failed, err: " + err.Error() + Error.Log(msg + "\n") + handler.ReportError(msg, id) + continue + } + err = punch(t.Payload) + if err != nil { + handler.ReportError(err.Error(), id) + } else { + handler.ReportSuccess(id) + } + } +} + func punch(payload string) error { account := strings.Fields(payload) if len(account) < 2 { diff --git a/tencentCloud.go b/tencentCloud.go index 65f9da2..505ea88 100644 --- a/tencentCloud.go +++ b/tencentCloud.go @@ -3,7 +3,8 @@ package main import ( - "encoding/json" + "fmt" + "io" "net/http" "os" "strings" @@ -14,19 +15,19 @@ const contentType = "text/plain" func regist() (handler, error) { host := os.Getenv("SCF_RUNTIME_API") port := os.Getenv("SCF_RUNTIME_API_PORT") - reportAPI := "http://" + host + ":" + port - res, err := http.Post(reportAPI+"/runtime/init/ready", contentType, http.NoBody) + endpoint := fmt.Sprintf("http://%s:%s/", host, port) + res, err := http.Post(endpoint+"runtime/init/ready", contentType, http.NoBody) if err != nil { return nil, err } res.Body.Close() return &tencent{ - reportAPI: reportAPI, + endpoint: endpoint, }, err } type tencent struct { - reportAPI string + endpoint string } type timerTrigger struct { @@ -35,39 +36,27 @@ type timerTrigger struct { Payload string `json:"Message"` } -func (h tencent) ListenAndServe(punch func(payload string) error) error { - for { - res, err := http.Get(h.reportAPI + "/runtime/invocation/next") - if err != nil { - Error.Log("get trigger payload failed, err: %s\n", err.Error()) - h.reportError("get payload failed, err: " + err.Error() + "\n") - } - requestId := res.Header.Get("Request_id") - dec := json.NewDecoder(res.Body) - t := &timerTrigger{} - err = dec.Decode(t) - res.Body.Close() // close body - if err != nil { - msg := "parse request body failed, err: " + err.Error() - Error.Log(msg + "\n") - h.reportError(msg) - } - err = punch(t.Payload) - if err != nil { - h.reportError(err.Error() + "\n") - } else { - res, err := http.DefaultClient.Post(h.reportAPI+"/runtime/invocation/response", contentType, strings.NewReader(requestId)) - if err == nil { - res.Body.Close() - } else { - Fatal.Log(err.Error() + "\n") - } - } +func (h tencent) Next() (body io.ReadCloser, id string, err error) { + var resp *http.Response + resp, err = http.Get(h.endpoint + "runtime/invocation/next") + if err == nil { + body = resp.Body + id = resp.Header.Get("Request_id") + } + return +} + +func (h tencent) ReportSuccess(id string) { + res, err := http.DefaultClient.Post(h.endpoint+"runtime/invocation/response", contentType, strings.NewReader(id)) + if err == nil { + res.Body.Close() + } else { + Fatal.Log(err.Error() + "\n") } } -func (h tencent) reportError(msg string) { - res, err := http.DefaultClient.Post(h.reportAPI+"/runtime/invocation/error", +func (h tencent) ReportError(msg string, _ string) { + res, err := http.DefaultClient.Post(h.endpoint+"runtime/invocation/error", contentType, strings.NewReader(msg), )