Skip to content

Commit

Permalink
Initial commit for function proxy (#1)
Browse files Browse the repository at this point in the history
Defines a go server that acts as a proxy for function invocation to language specific servers. Handles timeouts, capturing logs, parallel function calls to separate language servers.
  • Loading branch information
tenczar authored Jul 23, 2018
1 parent 450a556 commit 7387c28
Show file tree
Hide file tree
Showing 68 changed files with 12,256 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* text=auto

vendor/** binary
**/gen/** -diff

# for Github
**/gen/** linguist-generated
vendor/** linguist-vendored
48 changes: 48 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true


[[constraint]]
branch = "master"
name = "golang.org/x/sync"

[prune]
go-tests = true
unused-packages = true
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Funky - Function Proxy Server

A simple proxy server written in Go used to forward function invocations to language specific servers. Funky handles capturing stdout and stderr logs, function invocation timeouts and a limited amount of parallel function invocations.

Funky requires two environment variables:
* SERVERS - a number of language specific servers to initalize to handle function invocations
* SERVER_CMD - the command to run to start a function server e.g. `python3 main.py hello.handle`

Any request to the function server will try to invoke the function on any free server. The request will block if no server if idle and able to process the request.
109 changes: 109 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////
// Copyright (c) 2018 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
///////////////////////////////////////////////////////////////////////
package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strconv"

"github.com/dispatchframework/funky/pkg/funky"
)

const (
serversEnvVar = "SERVERS"
serverCmdEnvVar = "SERVER_CMD"
)

type funkyHandler struct {
router funky.Router
}

func (f funkyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var body funky.Message
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
resp := funky.Message{
Context: &funky.Context{
Error: &funky.Error{
ErrorType: funky.InputError,
Message: fmt.Sprintf("Invalid Input: %s", err),
},
},
}
out, _ := json.Marshal(resp)
fmt.Fprintf(w, string(out))
return
}

resp, _ := f.router.Delegate(&body)

json.NewEncoder(w).Encode(resp)
}

func healthy(c <-chan struct{}) bool {
select {
case <-c:
return false
default:
return true
}
}

func main() {
numServers, err := strconv.Atoi(os.Getenv(serversEnvVar))
if err != nil {
log.Fatalf("Unable to parse %s environment variable", serversEnvVar)
}
if numServers < 1 {
numServers = 1
}

serverCmd := os.Getenv(serverCmdEnvVar)
serverFactory, err := funky.NewDefaultServerFactory(serverCmd)
if err != nil {
log.Fatal("Too few arguments to server command.")
}

router, err := funky.NewRouter(numServers, serverFactory)
if err != nil {
log.Fatalf("Failed creating new router: %+v", err)
}

handler := funkyHandler{
router: router,
}

servMux := http.NewServeMux()
servMux.Handle("/", handler)
servMux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
if !healthy(funky.Healthy) {
w.WriteHeader(500)
}

w.Write([]byte("{}"))
})

server := &http.Server{
Addr: ":8080",
Handler: servMux,
}

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
server.Shutdown(context.TODO())
router.Shutdown()
os.Exit(0)
}()

server.ListenAndServe()
}
67 changes: 67 additions & 0 deletions pkg/funky/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
///////////////////////////////////////////////////////////////////////
// Copyright (c) 2018 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
///////////////////////////////////////////////////////////////////////
package funky

import (
"fmt"
)

// IllegalArgumentError An error indicating that an argument to a method is illegal or invalid.
type IllegalArgumentError string

func (e IllegalArgumentError) Error() string {
return fmt.Sprintf("The argument is illegal or inappropriate: %s", string(e))
}

// TimeoutError An error indicating that a timeout has been exceeded
type TimeoutError string

func (e TimeoutError) Error() string {
return fmt.Sprintf("The invocation exceeded the timeout: %s", string(e))
}

// FunctionServerError a generic error indicating that the function server experienced an error
type FunctionServerError struct {
APIError Error
}

func (e FunctionServerError) Error() string {
return "The function server encountered an error. See APIError field for more detail"
}

// ConnectionRefusedError An error indicating that the http connection to the function server was refused
type ConnectionRefusedError string

func (e ConnectionRefusedError) Error() string {
return fmt.Sprintf("The local function server at %s refused the connection", string(e))
}

// BadRequestError An error indicating that the body of an http request was invalid
type BadRequestError string

func (e BadRequestError) Error() string {
return fmt.Sprintf("The request body is invalid: %s", string(e))
}

// InvocationError An error indicating that a general error occurred while invoking a function
type InvocationError string

func (e InvocationError) Error() string {
return fmt.Sprintf("Invocation of the function failed: %s", string(e))
}

// InvalidResponsePayloadError error indicating that function return could not be serialized
type InvalidResponsePayloadError string

func (e InvalidResponsePayloadError) Error() string {
return fmt.Sprintf("Unable to serialize response payload: %s", string(e))
}

// UnknownSystemError error for when something unknown happens during function invocation
type UnknownSystemError string

func (e UnknownSystemError) Error() string {
return fmt.Sprintf("Unknown system error: %s", string(e))
}
Loading

0 comments on commit 7387c28

Please sign in to comment.