mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
opentracing -> opencensus (#802)
* update vendor directory, add go.opencensus.io * update imports * oops * s/opentracing/opencensus/ & remove prometheus / zipkin stuff & remove old stats * the dep train rides again * fix gin build * deps from last guy * start in on the agent metrics * she builds * remove tags for now, cardinality error is fussing. subscribe instead of register * update to patched version of opencensus to proceed for now TODO switch to a release * meh fix imports * println debug the bad boys * lace it with the tags * update deps again * fix all inconsistent cardinality errors * add our own logger * fix init * fix oom measure * remove bugged removal code * fix s3 measures * fix prom handler nil
This commit is contained in:
2
vendor/github.com/fnproject/fdk-go/README.md
generated
vendored
2
vendor/github.com/fnproject/fdk-go/README.md
generated
vendored
@@ -17,7 +17,7 @@ or your favorite vendoring solution :)
|
||||
# Examples
|
||||
|
||||
For a simple getting started, see the [examples](/examples/hello) and follow
|
||||
the [README](/examples/hello/README.md). If you already have `fn` set up it
|
||||
the [README](/examples/README.md). If you already have `fn` set up it
|
||||
will take 2 minutes!
|
||||
|
||||
# Advanced example
|
||||
|
||||
2
vendor/github.com/fnproject/fdk-go/examples/README.md
generated
vendored
2
vendor/github.com/fnproject/fdk-go/examples/README.md
generated
vendored
@@ -4,7 +4,7 @@ The goal of the `fdk`'s are to make it just as easy to write a hot function as
|
||||
it is a cold one. The best way to showcase this is with an example.
|
||||
|
||||
This is an example of a hot function using the fdk-go bindings. The [hot function
|
||||
documentation](https://github.com/fnproject/fn/blob/master/docs/hot-functions.md)
|
||||
documentation](https://github.com/fnproject/fn/blob/master/docs/developers/hot-functions.md)
|
||||
contains an analysis of how this example works under the hood. With any of the
|
||||
examples provided here, you may use any format to configure your functions in
|
||||
`fn` itself. Here we add instructions to set up functions with a 'hot' format.
|
||||
|
||||
9
vendor/github.com/fnproject/fdk-go/examples/hello/Dockerfile
generated
vendored
9
vendor/github.com/fnproject/fdk-go/examples/hello/Dockerfile
generated
vendored
@@ -1,9 +0,0 @@
|
||||
FROM fnproject/go:dev as build-stage
|
||||
WORKDIR /function
|
||||
ADD . /src
|
||||
RUN go get github.com/fnproject/fdk-go
|
||||
RUN cd /src && go build -o func
|
||||
FROM fnproject/go
|
||||
WORKDIR /function
|
||||
COPY --from=build-stage /src/func /function/
|
||||
ENTRYPOINT ["./func"]
|
||||
18
vendor/github.com/fnproject/fdk-go/examples/hello/Gopkg.lock
generated
vendored
Normal file
18
vendor/github.com/fnproject/fdk-go/examples/hello/Gopkg.lock
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/fnproject/fdk-go"
|
||||
packages = [
|
||||
".",
|
||||
"utils"
|
||||
]
|
||||
revision = "5d768b2006f11737b6a69a758ddd6d2fac04923e"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "c55f0d3da5ec2e9e5c9a7c563702e4cf28513fa1aaea1c18664ca2cb7d726f89"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
25
vendor/github.com/fnproject/fdk-go/examples/hello/Gopkg.toml
generated
vendored
Normal file
25
vendor/github.com/fnproject/fdk-go/examples/hello/Gopkg.toml
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/fnproject/fdk-go"
|
||||
6
vendor/github.com/fnproject/fdk-go/examples/hello/func.go
generated
vendored
6
vendor/github.com/fnproject/fdk-go/examples/hello/func.go
generated
vendored
@@ -19,13 +19,13 @@ func myHandler(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
}
|
||||
json.NewDecoder(in).Decode(&person)
|
||||
if person.Name == "" {
|
||||
person.Name = "world"
|
||||
person.Name = "World"
|
||||
}
|
||||
|
||||
msg := struct {
|
||||
Msg string `json:"msg"`
|
||||
Msg string `json:"message"`
|
||||
}{
|
||||
Msg: fmt.Sprintf("Hello %s!", person.Name),
|
||||
Msg: fmt.Sprintf("Hello %s", person.Name),
|
||||
}
|
||||
|
||||
json.NewEncoder(out).Encode(&msg)
|
||||
|
||||
295
vendor/github.com/fnproject/fdk-go/fdk.go
generated
vendored
295
vendor/github.com/fnproject/fdk-go/fdk.go
generated
vendored
@@ -1,17 +1,12 @@
|
||||
package fdk
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fnproject/fdk-go/utils"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
@@ -27,11 +22,19 @@ func (f HandlerFunc) Serve(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
// Context will return an *fn.Ctx that can be used to read configuration and
|
||||
// request information from an incoming request.
|
||||
func Context(ctx context.Context) *Ctx {
|
||||
return ctx.Value(ctxKey).(*Ctx)
|
||||
utilsCtx := utils.Context(ctx)
|
||||
return &Ctx{
|
||||
Header: utilsCtx.Header,
|
||||
Config: utilsCtx.Config,
|
||||
}
|
||||
}
|
||||
|
||||
func WithContext(ctx context.Context, fnctx *Ctx) context.Context {
|
||||
return context.WithValue(ctx, ctxKey, fnctx)
|
||||
utilsCtx := &utils.Ctx{
|
||||
Header: fnctx.Header,
|
||||
Config: fnctx.Config,
|
||||
}
|
||||
return utils.WithContext(ctx, utilsCtx)
|
||||
}
|
||||
|
||||
// Ctx provides access to Config and Headers from fn.
|
||||
@@ -40,31 +43,27 @@ type Ctx struct {
|
||||
Config map[string]string
|
||||
}
|
||||
|
||||
type key struct{}
|
||||
|
||||
var ctxKey = new(key)
|
||||
|
||||
// AddHeader will add a header on the function response, for hot function
|
||||
// formats.
|
||||
func AddHeader(out io.Writer, key, value string) {
|
||||
if resp, ok := out.(*response); ok {
|
||||
resp.header.Add(key, value)
|
||||
if resp, ok := out.(*utils.Response); ok {
|
||||
resp.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// SetHeader will set a header on the function response, for hot function
|
||||
// formats.
|
||||
func SetHeader(out io.Writer, key, value string) {
|
||||
if resp, ok := out.(*response); ok {
|
||||
resp.header.Set(key, value)
|
||||
if resp, ok := out.(*utils.Response); ok {
|
||||
resp.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteStatus will set the status code to return in the function response, for
|
||||
// hot function formats.
|
||||
func WriteStatus(out io.Writer, status int) {
|
||||
if resp, ok := out.(*response); ok {
|
||||
resp.status = status
|
||||
if resp, ok := out.(*utils.Response); ok {
|
||||
resp.Status = status
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,261 +72,5 @@ func WriteStatus(out io.Writer, status int) {
|
||||
// function and fn server via any of the supported formats.
|
||||
func Handle(handler Handler) {
|
||||
format, _ := os.LookupEnv("FN_FORMAT")
|
||||
do(handler, format, os.Stdin, os.Stdout)
|
||||
}
|
||||
|
||||
func do(handler Handler, format string, in io.Reader, out io.Writer) {
|
||||
ctx := buildCtx()
|
||||
switch format {
|
||||
case "http":
|
||||
doHTTP(handler, ctx, in, out)
|
||||
case "json":
|
||||
doJSON(handler, ctx, in, out)
|
||||
case "default":
|
||||
doDefault(handler, ctx, in, out)
|
||||
default:
|
||||
panic("unknown format (fdk-go): " + format)
|
||||
}
|
||||
}
|
||||
|
||||
// doDefault only runs once, since it is a 'cold' function
|
||||
func doDefault(handler Handler, ctx context.Context, in io.Reader, out io.Writer) {
|
||||
setHeaders(ctx, buildHeadersFromEnv())
|
||||
|
||||
ctx, cancel := ctxWithDeadline(ctx)
|
||||
defer cancel()
|
||||
|
||||
handler.Serve(ctx, in, out)
|
||||
}
|
||||
|
||||
// doHTTP runs a loop, reading http requests from in and writing
|
||||
// http responses to out
|
||||
func doHTTP(handler Handler, ctx context.Context, in io.Reader, out io.Writer) {
|
||||
var buf bytes.Buffer
|
||||
// maps don't get down-sized, so we can reuse this as it's likely that the
|
||||
// user sends in the same amount of headers over and over (but still clear
|
||||
// b/w runs) -- buf uses same principle
|
||||
hdr := make(http.Header)
|
||||
|
||||
for {
|
||||
err := doHTTPOnce(handler, ctx, in, out, &buf, hdr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doJSON(handler Handler, ctx context.Context, in io.Reader, out io.Writer) {
|
||||
var buf bytes.Buffer
|
||||
hdr := make(http.Header)
|
||||
|
||||
for {
|
||||
err := doJSONOnce(handler, ctx, in, out, &buf, hdr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type callRequestHTTP struct {
|
||||
Type string `json:"type"`
|
||||
RequestURL string `json:"request_url"`
|
||||
Headers http.Header `json:"headers"`
|
||||
}
|
||||
|
||||
type jsonIn struct {
|
||||
Body string `json:"body"`
|
||||
ContentType string `json:"content_type"`
|
||||
CallID string `json:"call_id"`
|
||||
Protocol callRequestHTTP `json:"protocol"`
|
||||
}
|
||||
|
||||
type callResponseHTTP struct {
|
||||
StatusCode int `json:"status_code,omitempty"`
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
}
|
||||
|
||||
type jsonOut struct {
|
||||
Body string `json:"body"`
|
||||
ContentType string `json:"content_type"`
|
||||
Protocol callResponseHTTP `json:"protocol,omitempty"`
|
||||
}
|
||||
|
||||
func doJSONOnce(handler Handler, ctx context.Context, in io.Reader, out io.Writer, buf *bytes.Buffer, hdr http.Header) error {
|
||||
buf.Reset()
|
||||
resetHeaders(hdr)
|
||||
|
||||
var jsonResponse jsonOut
|
||||
var jsonRequest jsonIn
|
||||
|
||||
resp := response{
|
||||
Writer: buf,
|
||||
status: 200,
|
||||
header: hdr,
|
||||
}
|
||||
|
||||
err := json.NewDecoder(in).Decode(&jsonRequest)
|
||||
if err != nil {
|
||||
// stdin now closed
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
jsonResponse.Protocol.StatusCode = 500
|
||||
jsonResponse.Body = fmt.Sprintf(`{"error": %v}`, err.Error())
|
||||
} else {
|
||||
setHeaders(ctx, jsonRequest.Protocol.Headers)
|
||||
ctx, cancel := ctxWithDeadline(ctx)
|
||||
defer cancel()
|
||||
handler.Serve(ctx, strings.NewReader(jsonRequest.Body), &resp)
|
||||
jsonResponse.Protocol.StatusCode = resp.status
|
||||
jsonResponse.Body = buf.String()
|
||||
jsonResponse.Protocol.Headers = resp.header
|
||||
}
|
||||
|
||||
json.NewEncoder(out).Encode(jsonResponse)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ctxWithDeadline(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
fdkCtx := Context(ctx)
|
||||
fnDeadline := fdkCtx.Header.Get("FN_DEADLINE") // this is always in headers
|
||||
|
||||
t, err := time.Parse(time.RFC3339, fnDeadline)
|
||||
if err == nil {
|
||||
return context.WithDeadline(ctx, t)
|
||||
}
|
||||
return context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
func doHTTPOnce(handler Handler, ctx context.Context, in io.Reader, out io.Writer, buf *bytes.Buffer, hdr http.Header) error {
|
||||
buf.Reset()
|
||||
resetHeaders(hdr)
|
||||
resp := response{
|
||||
Writer: buf,
|
||||
status: 200,
|
||||
header: hdr,
|
||||
}
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(in))
|
||||
if err != nil {
|
||||
// stdin now closed
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
// TODO it would be nice if we could let the user format this response to their preferred style..
|
||||
resp.status = http.StatusInternalServerError
|
||||
io.WriteString(resp, err.Error())
|
||||
} else {
|
||||
ctx, cancel := ctxWithDeadline(ctx)
|
||||
defer cancel()
|
||||
setHeaders(ctx, req.Header)
|
||||
handler.Serve(ctx, req.Body, &resp)
|
||||
}
|
||||
|
||||
hResp := http.Response{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
StatusCode: resp.status,
|
||||
Request: req,
|
||||
Body: ioutil.NopCloser(buf),
|
||||
ContentLength: int64(buf.Len()),
|
||||
Header: resp.header,
|
||||
}
|
||||
hResp.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetHeaders(m http.Header) {
|
||||
for k := range m { // compiler optimizes this to 1 instruction now
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// response is a general purpose response struct any format can use to record
|
||||
// user's code responses before formatting them appropriately.
|
||||
type response struct {
|
||||
status int
|
||||
header http.Header
|
||||
|
||||
io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
base = map[string]struct{}{
|
||||
`FN_APP_NAME`: struct{}{},
|
||||
`FN_PATH`: struct{}{},
|
||||
`FN_METHOD`: struct{}{},
|
||||
`FN_FORMAT`: struct{}{},
|
||||
`FN_MEMORY`: struct{}{},
|
||||
`FN_TYPE`: struct{}{},
|
||||
}
|
||||
|
||||
headerPre = `FN_HEADER_`
|
||||
|
||||
exact = map[string]struct{}{
|
||||
`FN_CALL_ID`: struct{}{},
|
||||
`FN_METHOD`: struct{}{},
|
||||
`FN_REQUEST_URL`: struct{}{},
|
||||
}
|
||||
)
|
||||
|
||||
func setHeaders(ctx context.Context, hdr http.Header) {
|
||||
fctx := ctx.Value(ctxKey).(*Ctx)
|
||||
fctx.Header = hdr
|
||||
}
|
||||
|
||||
func buildCtx() context.Context {
|
||||
ctx := &Ctx{
|
||||
Config: buildConfig(),
|
||||
// allow caller to build headers separately (to avoid map alloc)
|
||||
}
|
||||
|
||||
return WithContext(context.Background(), ctx)
|
||||
}
|
||||
|
||||
func buildConfig() map[string]string {
|
||||
cfg := make(map[string]string, len(base))
|
||||
|
||||
for _, e := range os.Environ() {
|
||||
vs := strings.SplitN(e, "=", 2)
|
||||
if len(vs) < 2 {
|
||||
vs = append(vs, "")
|
||||
}
|
||||
cfg[vs[0]] = vs[1]
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func buildHeadersFromEnv() http.Header {
|
||||
env := os.Environ()
|
||||
hdr := make(http.Header, len(env)-len(base))
|
||||
|
||||
for _, e := range env {
|
||||
vs := strings.SplitN(e, "=", 2)
|
||||
hdrKey := headerKey(vs[0])
|
||||
if hdrKey == "" {
|
||||
continue
|
||||
}
|
||||
if len(vs) < 2 {
|
||||
vs = append(vs, "")
|
||||
}
|
||||
// rebuild these as 'http' headers
|
||||
vs = strings.Split(vs[1], ", ")
|
||||
for _, v := range vs {
|
||||
hdr.Add(hdrKey, v)
|
||||
}
|
||||
}
|
||||
return hdr
|
||||
}
|
||||
|
||||
// for getting headers out of env
|
||||
func headerKey(key string) string {
|
||||
if strings.HasPrefix(key, headerPre) {
|
||||
return strings.TrimPrefix(key, headerPre)
|
||||
}
|
||||
_, ok := exact[key]
|
||||
if ok {
|
||||
return key
|
||||
}
|
||||
return ""
|
||||
utils.Do(handler, format, os.Stdin, os.Stdout)
|
||||
}
|
||||
|
||||
42
vendor/github.com/fnproject/fdk-go/fdk_test.go
generated
vendored
42
vendor/github.com/fnproject/fdk-go/fdk_test.go
generated
vendored
@@ -13,6 +13,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fnproject/fdk-go/utils"
|
||||
)
|
||||
|
||||
func echoHTTPHandler(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
@@ -27,7 +29,7 @@ func TestHandler(t *testing.T) {
|
||||
io.WriteString(&in, inString)
|
||||
|
||||
var out bytes.Buffer
|
||||
echoHTTPHandler(buildCtx(), &in, &out)
|
||||
echoHTTPHandler(utils.BuildCtx(), &in, &out)
|
||||
|
||||
if out.String() != inString {
|
||||
t.Fatalf("this was supposed to be easy. strings no matchy: %s got: %s", inString, out.String())
|
||||
@@ -41,7 +43,7 @@ func TestDefault(t *testing.T) {
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
doDefault(HandlerFunc(echoHTTPHandler), buildCtx(), &in, &out)
|
||||
utils.DoDefault(HandlerFunc(echoHTTPHandler), utils.BuildCtx(), &in, &out)
|
||||
|
||||
if out.String() != inString {
|
||||
t.Fatalf("strings no matchy: %s got: %s", inString, out.String())
|
||||
@@ -71,12 +73,14 @@ func JSONWithStatusCode(_ context.Context, in io.Reader, out io.Writer) {
|
||||
}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
req := &jsonIn{
|
||||
req := &utils.JsonIn{
|
||||
`{"name":"john"}`,
|
||||
"application/json",
|
||||
"someid",
|
||||
callRequestHTTP{
|
||||
Type: "json",
|
||||
"2018-01-30T16:52:39.786Z",
|
||||
"sync",
|
||||
utils.CallRequestHTTP{
|
||||
Type: "http",
|
||||
RequestURL: "someURL",
|
||||
Headers: http.Header{},
|
||||
},
|
||||
@@ -90,12 +94,12 @@ func TestJSON(t *testing.T) {
|
||||
|
||||
var out, buf bytes.Buffer
|
||||
|
||||
err = doJSONOnce(HandlerFunc(JSONHandler), buildCtx(), &in, &out, &buf, make(http.Header))
|
||||
err = utils.DoJSONOnce(HandlerFunc(JSONHandler), utils.BuildCtx(), &in, &out, &buf, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
|
||||
JSONOut := &jsonOut{}
|
||||
JSONOut := &utils.JsonOut{}
|
||||
err = json.NewDecoder(&out).Decode(JSONOut)
|
||||
|
||||
if err != nil {
|
||||
@@ -115,8 +119,8 @@ func TestFailedJSON(t *testing.T) {
|
||||
|
||||
var out, buf bytes.Buffer
|
||||
|
||||
JSONOut := &jsonOut{}
|
||||
err := doJSONOnce(HandlerFunc(JSONHandler), buildCtx(), in, &out, &buf, make(http.Header))
|
||||
JSONOut := &utils.JsonOut{}
|
||||
err := utils.DoJSONOnce(HandlerFunc(JSONHandler), utils.BuildCtx(), in, &out, &buf, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
@@ -133,7 +137,7 @@ func TestFailedJSON(t *testing.T) {
|
||||
func TestJSONEOF(t *testing.T) {
|
||||
var in, out, buf bytes.Buffer
|
||||
|
||||
err := doJSONOnce(HandlerFunc(JSONHandler), buildCtx(), &in, &out, &buf, make(http.Header))
|
||||
err := utils.DoJSONOnce(HandlerFunc(JSONHandler), utils.BuildCtx(), &in, &out, &buf, make(http.Header))
|
||||
if err != io.EOF {
|
||||
t.Fatal("should return EOF")
|
||||
}
|
||||
@@ -141,11 +145,13 @@ func TestJSONEOF(t *testing.T) {
|
||||
|
||||
func TestJSONOverwriteStatusCodeAndHeaders(t *testing.T) {
|
||||
var out, buf bytes.Buffer
|
||||
req := &jsonIn{
|
||||
req := &utils.JsonIn{
|
||||
`{"name":"john"}`,
|
||||
"application/json",
|
||||
"someid",
|
||||
callRequestHTTP{
|
||||
"2018-01-30T16:52:39.786Z",
|
||||
"sync",
|
||||
utils.CallRequestHTTP{
|
||||
Type: "json",
|
||||
RequestURL: "someURL",
|
||||
Headers: http.Header{},
|
||||
@@ -158,12 +164,12 @@ func TestJSONOverwriteStatusCodeAndHeaders(t *testing.T) {
|
||||
t.Fatal("Unable to marshal request")
|
||||
}
|
||||
|
||||
err = doJSONOnce(HandlerFunc(JSONWithStatusCode), buildCtx(), &in, &out, &buf, make(http.Header))
|
||||
err = utils.DoJSONOnce(HandlerFunc(JSONWithStatusCode), utils.BuildCtx(), &in, &out, &buf, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
|
||||
JSONOut := &jsonOut{}
|
||||
JSONOut := &utils.JsonOut{}
|
||||
err = json.NewDecoder(&out).Decode(JSONOut)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
@@ -185,8 +191,8 @@ func TestHTTP(t *testing.T) {
|
||||
in := HTTPreq(t, bodyString)
|
||||
|
||||
var out bytes.Buffer
|
||||
ctx := buildCtx()
|
||||
err := doHTTPOnce(HandlerFunc(echoHTTPHandler), ctx, in, &out, &bytes.Buffer{}, make(http.Header))
|
||||
ctx := utils.BuildCtx()
|
||||
err := utils.DoHTTPOnce(HandlerFunc(echoHTTPHandler), ctx, in, &out, &bytes.Buffer{}, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
@@ -218,9 +224,9 @@ func TestHTTP(t *testing.T) {
|
||||
func TestHTTPEOF(t *testing.T) {
|
||||
var in bytes.Buffer
|
||||
var out bytes.Buffer
|
||||
ctx := buildCtx()
|
||||
ctx := utils.BuildCtx()
|
||||
|
||||
err := doHTTPOnce(HandlerFunc(echoHTTPHandler), ctx, &in, &out, &bytes.Buffer{}, make(http.Header))
|
||||
err := utils.DoHTTPOnce(HandlerFunc(echoHTTPHandler), ctx, &in, &out, &bytes.Buffer{}, make(http.Header))
|
||||
if err != io.EOF {
|
||||
t.Fatal("should return EOF")
|
||||
}
|
||||
|
||||
316
vendor/github.com/fnproject/fdk-go/utils/utils.go
generated
vendored
Normal file
316
vendor/github.com/fnproject/fdk-go/utils/utils.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
Serve(ctx context.Context, in io.Reader, out io.Writer)
|
||||
}
|
||||
|
||||
// Context will return an *fn.Ctx that can be used to read configuration and
|
||||
// request information from an incoming request.
|
||||
func Context(ctx context.Context) *Ctx {
|
||||
return ctx.Value(ctxKey).(*Ctx)
|
||||
}
|
||||
|
||||
func WithContext(ctx context.Context, fnctx *Ctx) context.Context {
|
||||
return context.WithValue(ctx, ctxKey, fnctx)
|
||||
}
|
||||
|
||||
// Ctx provides access to Config and Headers from fn.
|
||||
type Ctx struct {
|
||||
Header http.Header
|
||||
Config map[string]string
|
||||
}
|
||||
|
||||
type key struct{}
|
||||
|
||||
var ctxKey = new(key)
|
||||
|
||||
func Do(handler Handler, format string, in io.Reader, out io.Writer) {
|
||||
ctx := BuildCtx()
|
||||
switch format {
|
||||
case "http":
|
||||
DoHTTP(handler, ctx, in, out)
|
||||
case "json":
|
||||
DoJSON(handler, ctx, in, out)
|
||||
case "default":
|
||||
DoDefault(handler, ctx, in, out)
|
||||
default:
|
||||
panic("unknown format (fdk-go): " + format)
|
||||
}
|
||||
}
|
||||
|
||||
// doDefault only runs once, since it is a 'cold' function
|
||||
func DoDefault(handler Handler, ctx context.Context, in io.Reader, out io.Writer) {
|
||||
SetHeaders(ctx, BuildHeadersFromEnv())
|
||||
fnDeadline, _ := os.LookupEnv("FN_DEADLINE")
|
||||
|
||||
ctx, cancel := CtxWithDeadline(ctx, fnDeadline)
|
||||
defer cancel()
|
||||
|
||||
handler.Serve(ctx, in, out)
|
||||
}
|
||||
|
||||
// doHTTP runs a loop, reading http requests from in and writing
|
||||
// http responses to out
|
||||
func DoHTTP(handler Handler, ctx context.Context, in io.Reader, out io.Writer) {
|
||||
var buf bytes.Buffer
|
||||
// maps don't get down-sized, so we can reuse this as it's likely that the
|
||||
// user sends in the same amount of headers over and over (but still clear
|
||||
// b/w runs) -- buf uses same principle
|
||||
hdr := make(http.Header)
|
||||
|
||||
for {
|
||||
err := DoHTTPOnce(handler, ctx, in, out, &buf, hdr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DoJSON(handler Handler, ctx context.Context, in io.Reader, out io.Writer) {
|
||||
var buf bytes.Buffer
|
||||
hdr := make(http.Header)
|
||||
|
||||
for {
|
||||
err := DoJSONOnce(handler, ctx, in, out, &buf, hdr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CallRequestHTTP struct {
|
||||
Type string `json:"type"`
|
||||
RequestURL string `json:"request_url"`
|
||||
Method string `json:"method"`
|
||||
Headers http.Header `json:"headers"`
|
||||
}
|
||||
|
||||
type JsonIn struct {
|
||||
Body string `json:"body"`
|
||||
ContentType string `json:"content_type"`
|
||||
CallID string `json:"call_id"`
|
||||
Deadline string `json:"deadline"`
|
||||
Type string `json:"type"`
|
||||
Protocol CallRequestHTTP `json:"protocol"`
|
||||
}
|
||||
|
||||
type CallResponseHTTP struct {
|
||||
StatusCode int `json:"status_code,omitempty"`
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
}
|
||||
|
||||
type JsonOut struct {
|
||||
Body string `json:"body"`
|
||||
ContentType string `json:"content_type"`
|
||||
Protocol CallResponseHTTP `json:"protocol,omitempty"`
|
||||
}
|
||||
|
||||
func GetJSONResp(buf *bytes.Buffer, fnResp *Response, req *JsonIn) *JsonOut {
|
||||
|
||||
hResp := &JsonOut{
|
||||
Body: buf.String(),
|
||||
ContentType: "",
|
||||
Protocol: CallResponseHTTP{
|
||||
StatusCode: fnResp.Status,
|
||||
Headers: fnResp.Header,
|
||||
},
|
||||
}
|
||||
|
||||
return hResp
|
||||
}
|
||||
|
||||
func DoJSONOnce(handler Handler, ctx context.Context, in io.Reader, out io.Writer, buf *bytes.Buffer, hdr http.Header) error {
|
||||
buf.Reset()
|
||||
ResetHeaders(hdr)
|
||||
resp := Response{
|
||||
Writer: buf,
|
||||
Status: 200,
|
||||
Header: hdr,
|
||||
}
|
||||
|
||||
var jsonRequest JsonIn
|
||||
err := json.NewDecoder(in).Decode(&jsonRequest)
|
||||
if err != nil {
|
||||
// stdin now closed
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
resp.Status = http.StatusInternalServerError
|
||||
io.WriteString(resp, fmt.Sprintf(`{"error": %v}`, err.Error()))
|
||||
} else {
|
||||
SetHeaders(ctx, jsonRequest.Protocol.Headers)
|
||||
ctx, cancel := CtxWithDeadline(ctx, jsonRequest.Deadline)
|
||||
defer cancel()
|
||||
handler.Serve(ctx, strings.NewReader(jsonRequest.Body), &resp)
|
||||
}
|
||||
|
||||
jsonResponse := GetJSONResp(buf, &resp, &jsonRequest)
|
||||
json.NewEncoder(out).Encode(jsonResponse)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CtxWithDeadline(ctx context.Context, fnDeadline string) (context.Context, context.CancelFunc) {
|
||||
t, err := time.Parse(time.RFC3339, fnDeadline)
|
||||
if err == nil {
|
||||
return context.WithDeadline(ctx, t)
|
||||
}
|
||||
return context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
func GetHTTPResp(buf *bytes.Buffer, fnResp *Response, req *http.Request) http.Response {
|
||||
|
||||
fnResp.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
|
||||
|
||||
hResp := http.Response{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
StatusCode: fnResp.Status,
|
||||
Request: req,
|
||||
Body: ioutil.NopCloser(buf),
|
||||
ContentLength: int64(buf.Len()),
|
||||
Header: fnResp.Header,
|
||||
}
|
||||
|
||||
return hResp
|
||||
}
|
||||
|
||||
func DoHTTPOnce(handler Handler, ctx context.Context, in io.Reader, out io.Writer, buf *bytes.Buffer, hdr http.Header) error {
|
||||
buf.Reset()
|
||||
ResetHeaders(hdr)
|
||||
resp := Response{
|
||||
Writer: buf,
|
||||
Status: 200,
|
||||
Header: hdr,
|
||||
}
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(in))
|
||||
if err != nil {
|
||||
// stdin now closed
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
// TODO it would be nice if we could let the user format this response to their preferred style..
|
||||
resp.Status = http.StatusInternalServerError
|
||||
io.WriteString(resp, err.Error())
|
||||
} else {
|
||||
fnDeadline := Context(ctx).Header.Get("FN_DEADLINE")
|
||||
ctx, cancel := CtxWithDeadline(ctx, fnDeadline)
|
||||
defer cancel()
|
||||
SetHeaders(ctx, req.Header)
|
||||
handler.Serve(ctx, req.Body, &resp)
|
||||
}
|
||||
|
||||
hResp := GetHTTPResp(buf, &resp, req)
|
||||
hResp.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ResetHeaders(m http.Header) {
|
||||
for k := range m { // compiler optimizes this to 1 instruction now
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// response is a general purpose response struct any format can use to record
|
||||
// user's code responses before formatting them appropriately.
|
||||
type Response struct {
|
||||
Status int
|
||||
Header http.Header
|
||||
|
||||
io.Writer
|
||||
}
|
||||
|
||||
var (
|
||||
BaseHeaders = map[string]struct{}{
|
||||
`FN_APP_NAME`: struct{}{},
|
||||
`FN_PATH`: struct{}{},
|
||||
`FN_METHOD`: struct{}{},
|
||||
`FN_FORMAT`: struct{}{},
|
||||
`FN_MEMORY`: struct{}{},
|
||||
`FN_TYPE`: struct{}{},
|
||||
}
|
||||
|
||||
HeaderPrefix = `FN_HEADER_`
|
||||
|
||||
ExactHeaders = map[string]struct{}{
|
||||
`FN_CALL_ID`: struct{}{},
|
||||
`FN_METHOD`: struct{}{},
|
||||
`FN_REQUEST_URL`: struct{}{},
|
||||
}
|
||||
)
|
||||
|
||||
func SetHeaders(ctx context.Context, hdr http.Header) {
|
||||
fctx := ctx.Value(ctxKey).(*Ctx)
|
||||
fctx.Header = hdr
|
||||
}
|
||||
|
||||
func BuildCtx() context.Context {
|
||||
ctx := &Ctx{
|
||||
Config: BuildConfig(),
|
||||
// allow caller to build headers separately (to avoid map alloc)
|
||||
}
|
||||
|
||||
return WithContext(context.Background(), ctx)
|
||||
}
|
||||
|
||||
func BuildConfig() map[string]string {
|
||||
cfg := make(map[string]string, len(BaseHeaders))
|
||||
|
||||
for _, e := range os.Environ() {
|
||||
vs := strings.SplitN(e, "=", 2)
|
||||
if len(vs) < 2 {
|
||||
vs = append(vs, "")
|
||||
}
|
||||
cfg[vs[0]] = vs[1]
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func BuildHeadersFromEnv() http.Header {
|
||||
env := os.Environ()
|
||||
hdr := make(http.Header, len(env)-len(BaseHeaders))
|
||||
|
||||
for _, e := range env {
|
||||
vs := strings.SplitN(e, "=", 2)
|
||||
hdrKey := IsHeaderKey(vs[0])
|
||||
if hdrKey == "" {
|
||||
continue
|
||||
}
|
||||
if len(vs) < 2 {
|
||||
vs = append(vs, "")
|
||||
}
|
||||
// rebuild these as 'http' headers
|
||||
vs = strings.Split(vs[1], ", ")
|
||||
for _, v := range vs {
|
||||
hdr.Add(hdrKey, v)
|
||||
}
|
||||
}
|
||||
return hdr
|
||||
}
|
||||
|
||||
// for getting headers out of env
|
||||
func IsHeaderKey(key string) string {
|
||||
if strings.HasPrefix(key, HeaderPrefix) {
|
||||
return strings.TrimPrefix(key, HeaderPrefix)
|
||||
}
|
||||
_, ok := ExactHeaders[key]
|
||||
if ok {
|
||||
return key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user