The 3rd party package version used by the go server may be different from the one in the user's source code and will cause plugin version mismatched. Hence, we should never import any external packages except the Fission or built-in packages. This PR replaces all 3rd-party packages with built-in packages.
204 lines
5.3 KiB
Go
204 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"plugin"
|
|
|
|
// DO NOT IMPORT THIRD PARTY PACKAGES
|
|
// The 3rd party package version used by go server may be
|
|
// different from the one in user's source code and will
|
|
// cause plugin version mismatched. Hence, we should never
|
|
// import any external packages except the Fission or built-in
|
|
// packages.
|
|
|
|
"github.com/fission/fission/environments/go/context"
|
|
)
|
|
|
|
const (
|
|
CODE_PATH = "/userfunc/user"
|
|
)
|
|
|
|
type (
|
|
FunctionLoadRequest struct {
|
|
// FilePath is an absolute filesystem path to the
|
|
// function. What exactly is stored here is
|
|
// env-specific. Optional.
|
|
FilePath string `json:"filepath"`
|
|
|
|
// FunctionName has an environment-specific meaning;
|
|
// usually, it defines a function within a module
|
|
// containing multiple functions. Optional; default is
|
|
// environment-specific.
|
|
FunctionName string `json:"functionName"`
|
|
|
|
// URL to expose this function at. Optional; defaults
|
|
// to "/".
|
|
URL string `json:"url"`
|
|
}
|
|
)
|
|
|
|
var userFunc http.HandlerFunc
|
|
|
|
func loadPlugin(codePath, entrypoint string) (http.HandlerFunc, error) {
|
|
|
|
// if codepath's a directory, load the file inside it
|
|
info, err := os.Stat(codePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error checking plugin path: %v", err)
|
|
}
|
|
if info.IsDir() {
|
|
files, err := ioutil.ReadDir(codePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading directory: %v", err)
|
|
}
|
|
if len(files) == 0 {
|
|
return nil, fmt.Errorf("no files to load: %v", codePath)
|
|
}
|
|
fi := files[0]
|
|
codePath = filepath.Join(codePath, fi.Name())
|
|
}
|
|
|
|
log.Printf("loading plugin from %v", codePath)
|
|
p, err := plugin.Open(codePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error loading plugin: %v", err)
|
|
}
|
|
sym, err := p.Lookup(entrypoint)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("entry point not found: %v", err)
|
|
}
|
|
|
|
switch h := sym.(type) {
|
|
case *http.Handler:
|
|
return (*h).ServeHTTP, nil
|
|
case *http.HandlerFunc:
|
|
return *h, nil
|
|
case func(http.ResponseWriter, *http.Request):
|
|
return h, nil
|
|
case func(context.Context, http.ResponseWriter, *http.Request):
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
c := context.New()
|
|
h(c, w, r)
|
|
}, nil
|
|
default:
|
|
panic("Entry point not found: bad type")
|
|
}
|
|
}
|
|
|
|
func specializeHandler() func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if userFunc != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte("Not a generic container"))
|
|
return
|
|
}
|
|
|
|
_, err := os.Stat(CODE_PATH)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
log.Printf("code path (%v) does not exist: %v", CODE_PATH, err)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte(CODE_PATH + ": not found"))
|
|
return
|
|
} else {
|
|
log.Printf("unknown error looking for code path(%v): %v", CODE_PATH, err)
|
|
err = fmt.Errorf("unknown error: %v", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
}
|
|
|
|
log.Println("specializing ...")
|
|
userFunc, err = loadPlugin(CODE_PATH, "Handler")
|
|
if err != nil {
|
|
err = fmt.Errorf("error specializing function: %v", err)
|
|
log.Println(err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
log.Println("done")
|
|
}
|
|
}
|
|
|
|
func specializeHandlerV2() func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if userFunc != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte("Not a generic container"))
|
|
return
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
log.Printf("error reading request body: %v", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
var loadreq FunctionLoadRequest
|
|
err = json.Unmarshal(body, &loadreq)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
_, err = os.Stat(loadreq.FilePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
log.Printf("code path (%v) does not exist: %v", loadreq.FilePath, err)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte(loadreq.FilePath + ": not found"))
|
|
return
|
|
} else {
|
|
log.Printf("unknown error looking for code path(%v): %v", loadreq.FilePath, err)
|
|
err = fmt.Errorf("unknown error: %v", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
}
|
|
|
|
log.Println("specializing ...")
|
|
userFunc, err = loadPlugin(loadreq.FilePath, loadreq.FunctionName)
|
|
if err != nil {
|
|
err = fmt.Errorf("error specializing function: %v", err)
|
|
log.Println(err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
log.Println("done")
|
|
}
|
|
}
|
|
|
|
func readinessProbeHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func main() {
|
|
|
|
http.HandleFunc("/healthz", readinessProbeHandler)
|
|
http.HandleFunc("/specialize", specializeHandler())
|
|
http.HandleFunc("/v2/specialize", specializeHandlerV2())
|
|
|
|
// Generic route -- all http requests go to the user function.
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if userFunc == nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("Generic container: no requests supported"))
|
|
return
|
|
}
|
|
userFunc(w, r)
|
|
})
|
|
|
|
log.Println("listening on 8888 ...")
|
|
http.ListenAndServe(":8888", nil)
|
|
}
|