mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Switch to dep from glide (#664)
This commit is contained in:
committed by
Reed Allman
parent
0a09d74137
commit
3b9818bc58
3
vendor/github.com/fnproject/fdk-go/CONTRIBUTING.md
generated
vendored
Normal file
3
vendor/github.com/fnproject/fdk-go/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please refer to main contribution doc:
|
||||
|
||||
https://github.com/fnproject/fn/blob/master/CONTRIBUTING.md
|
||||
202
vendor/github.com/fnproject/fdk-go/LICENSE
generated
vendored
Normal file
202
vendor/github.com/fnproject/fdk-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 Oracle Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
83
vendor/github.com/fnproject/fdk-go/README.md
generated
vendored
Normal file
83
vendor/github.com/fnproject/fdk-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
# Go Fn Development Kit (FDK)
|
||||
|
||||
[](https://godoc.org/github.com/fnproject/fdk-go)
|
||||
|
||||
fdk-go provides convenience functions for writing Go fn code
|
||||
|
||||
For getting started with fn, please refer to https://github.com/fnproject/fn/blob/master/README.md
|
||||
|
||||
# Installing fdk-go
|
||||
|
||||
```sh
|
||||
go get github.com/fnproject/fdk-go
|
||||
```
|
||||
|
||||
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
|
||||
will take 2 minutes!
|
||||
|
||||
# Advanced example
|
||||
|
||||
TODO going to move to [examples](examples/) too :)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"encoding/json"
|
||||
|
||||
fdk "github.com/fnproject/fdk-go"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fdk.Handle(fdk.HandlerFunc(myHandler))
|
||||
}
|
||||
|
||||
func myHandler(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
fnctx := fdk.Context(ctx)
|
||||
|
||||
contentType := fnctx.Header.Get("Content-Type")
|
||||
if contentType != "application/json" {
|
||||
fdk.WriteStatus(out, 400)
|
||||
fdk.SetHeader(out, "Content-Type", "application/json")
|
||||
io.WriteString(out, `{"error":"invalid content type"}`)
|
||||
return
|
||||
}
|
||||
|
||||
if fnctx.Config["FN_METHOD"] != "PUT" {
|
||||
fdk.WriteStatus(out, 404)
|
||||
fdk.SetHeader(out, "Content-Type", "application/json")
|
||||
io.WriteString(out, `{"error":"route not found"}`)
|
||||
return
|
||||
}
|
||||
|
||||
var person struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
json.NewDecoder(in).Decode(&person)
|
||||
|
||||
// you can write your own headers & status, if you'd like to
|
||||
fdk.WriteStatus(out, 201)
|
||||
fdk.SetHeader(out, "Content-Type", "application/json")
|
||||
|
||||
all := struct {
|
||||
Name string `json:"name"`
|
||||
Header http.Header `json:"header"`
|
||||
Config map[string]string `json:"config"`
|
||||
}{
|
||||
Name: person.Name,
|
||||
Header: fnctx.Header,
|
||||
Config: fnctx.Config,
|
||||
}
|
||||
|
||||
json.NewEncoder(out).Encode(&all)
|
||||
}
|
||||
```
|
||||
59
vendor/github.com/fnproject/fdk-go/examples/README.md
generated
vendored
Normal file
59
vendor/github.com/fnproject/fdk-go/examples/README.md
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# Function Examples
|
||||
|
||||
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)
|
||||
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.
|
||||
|
||||
### How to run the example
|
||||
|
||||
Install the CLI tool, start a Fn server and run `docker login` to login to
|
||||
DockerHub. See the [front page](https://github.com/fnproject/fn) for
|
||||
instructions.
|
||||
|
||||
Initialize the example with an image name you can access:
|
||||
|
||||
```sh
|
||||
fn init --runtime docker --format http --name <DOCKERHUB_USER/image_name>
|
||||
```
|
||||
|
||||
`--format json` will also work here, or if your functions already use json
|
||||
then adding the fdk will be seamless.
|
||||
|
||||
Build and deploy the function to the Fn server (default localhost:8080)
|
||||
|
||||
```sh
|
||||
fn deploy --app hot-app
|
||||
```
|
||||
|
||||
Now call your function (may take a sec to pull image):
|
||||
|
||||
```sh
|
||||
curl -X POST -d '{"name":"Clarice"}' http://localhost:8080/r/hot-app/hello
|
||||
```
|
||||
|
||||
**Note** that this expects you were in a directory named 'hello' (where this
|
||||
example lives), if this doesn't work, replace 'hello' with your `$PWD` from
|
||||
the `deploy` command.
|
||||
|
||||
Then call it again to see how fast hot functions are!
|
||||
|
||||
### Details
|
||||
|
||||
If you poke around in the Dockerfile you'll see that we're simply adding the
|
||||
file found in this directory, getting the `fdk-go` package to our workspace
|
||||
and then building a binary and building an image with that binary. That then
|
||||
gets deployed to dockerhub and fn.
|
||||
|
||||
For more robust projects, it's recommended to use a tool like `dep` or
|
||||
`glide` to get dependencies such as the `fdk-go` into your functions.
|
||||
|
||||
Scoping out `func.go` you can see that the handler code only deals with input
|
||||
and output, and doesn't have to deal with decoding the formatting from
|
||||
functions (i.e. i/o is presented through `io.Writer` and `io.Reader`). This
|
||||
makes it much easier to write hot functions.
|
||||
|
||||
9
vendor/github.com/fnproject/fdk-go/examples/hello/Dockerfile
generated
vendored
Normal file
9
vendor/github.com/fnproject/fdk-go/examples/hello/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
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"]
|
||||
32
vendor/github.com/fnproject/fdk-go/examples/hello/func.go
generated
vendored
Normal file
32
vendor/github.com/fnproject/fdk-go/examples/hello/func.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
fdk "github.com/fnproject/fdk-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fdk.Handle(fdk.HandlerFunc(myHandler))
|
||||
}
|
||||
|
||||
func myHandler(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
var person struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
json.NewDecoder(in).Decode(&person)
|
||||
if person.Name == "" {
|
||||
person.Name = "world"
|
||||
}
|
||||
|
||||
msg := struct {
|
||||
Msg string `json:"msg"`
|
||||
}{
|
||||
Msg: fmt.Sprintf("Hello %s!\n", person.Name),
|
||||
}
|
||||
|
||||
json.NewEncoder(out).Encode(&msg)
|
||||
}
|
||||
333
vendor/github.com/fnproject/fdk-go/fdk.go
generated
vendored
Normal file
333
vendor/github.com/fnproject/fdk-go/fdk.go
generated
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
package fdk
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
Serve(ctx context.Context, in io.Reader, out io.Writer)
|
||||
}
|
||||
|
||||
type HandlerFunc func(ctx context.Context, in io.Reader, out io.Writer)
|
||||
|
||||
func (f HandlerFunc) Serve(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
f(ctx, in, out)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Handle will run the event loop for a function. Handle should be invoked
|
||||
// through main() in a user's function and can handle communication between the
|
||||
// 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 ""
|
||||
}
|
||||
240
vendor/github.com/fnproject/fdk-go/fdk_test.go
generated
vendored
Normal file
240
vendor/github.com/fnproject/fdk-go/fdk_test.go
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
package fdk
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func echoHTTPHandler(ctx context.Context, in io.Reader, out io.Writer) {
|
||||
io.Copy(out, in)
|
||||
WriteStatus(out, http.StatusTeapot+2)
|
||||
SetHeader(out, "yo", "dawg")
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
inString := "yodawg"
|
||||
var in bytes.Buffer
|
||||
io.WriteString(&in, inString)
|
||||
|
||||
var out bytes.Buffer
|
||||
echoHTTPHandler(buildCtx(), &in, &out)
|
||||
|
||||
if out.String() != inString {
|
||||
t.Fatalf("this was supposed to be easy. strings no matchy: %s got: %s", inString, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
inString := "yodawg"
|
||||
var in bytes.Buffer
|
||||
io.WriteString(&in, inString)
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
doDefault(HandlerFunc(echoHTTPHandler), buildCtx(), &in, &out)
|
||||
|
||||
if out.String() != inString {
|
||||
t.Fatalf("strings no matchy: %s got: %s", inString, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func JSONHandler(_ context.Context, in io.Reader, out io.Writer) {
|
||||
var person struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
json.NewDecoder(in).Decode(&person)
|
||||
|
||||
if person.Name == "" {
|
||||
person.Name = "world"
|
||||
}
|
||||
|
||||
body := fmt.Sprintf("Hello %s!\n", person.Name)
|
||||
err := json.NewEncoder(out).Encode(body)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func JSONWithStatusCode(_ context.Context, in io.Reader, out io.Writer) {
|
||||
SetHeader(out, "Content-Type", "application/json")
|
||||
WriteStatus(out, 201)
|
||||
}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
req := &jsonIn{
|
||||
`{"name":"john"}`,
|
||||
"application/json",
|
||||
"someid",
|
||||
callRequestHTTP{
|
||||
Type: "json",
|
||||
RequestURL: "someURL",
|
||||
Headers: http.Header{},
|
||||
},
|
||||
}
|
||||
|
||||
var in bytes.Buffer
|
||||
err := json.NewEncoder(&in).Encode(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to marshal request")
|
||||
}
|
||||
|
||||
var out, buf bytes.Buffer
|
||||
|
||||
err = doJSONOnce(HandlerFunc(JSONHandler), buildCtx(), &in, &out, &buf, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
|
||||
JSONOut := &jsonOut{}
|
||||
err = json.NewDecoder(&out).Decode(JSONOut)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if !strings.Contains(JSONOut.Body, "Hello john!") {
|
||||
t.Fatalf("Output assertion mismatch. Expected: `Hello john!\n`. Actual: %v", JSONOut.Body)
|
||||
}
|
||||
if JSONOut.Protocol.StatusCode != 200 {
|
||||
t.Fatalf("Response code must equal to 200, but have: %v", JSONOut.Protocol.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailedJSON(t *testing.T) {
|
||||
dummyBody := "should fail with this"
|
||||
in := strings.NewReader(dummyBody)
|
||||
|
||||
var out, buf bytes.Buffer
|
||||
|
||||
JSONOut := &jsonOut{}
|
||||
err := doJSONOnce(HandlerFunc(JSONHandler), buildCtx(), in, &out, &buf, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
|
||||
err = json.NewDecoder(&out).Decode(JSONOut)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if JSONOut.Protocol.StatusCode != 500 {
|
||||
t.Fatalf("Response code must equal to 500, but have: %v", JSONOut.Protocol.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEOF(t *testing.T) {
|
||||
var in, out, buf bytes.Buffer
|
||||
|
||||
err := doJSONOnce(HandlerFunc(JSONHandler), buildCtx(), &in, &out, &buf, make(http.Header))
|
||||
if err != io.EOF {
|
||||
t.Fatal("should return EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONOverwriteStatusCodeAndHeaders(t *testing.T) {
|
||||
var out, buf bytes.Buffer
|
||||
req := &jsonIn{
|
||||
`{"name":"john"}`,
|
||||
"application/json",
|
||||
"someid",
|
||||
callRequestHTTP{
|
||||
Type: "json",
|
||||
RequestURL: "someURL",
|
||||
Headers: http.Header{},
|
||||
},
|
||||
}
|
||||
|
||||
var in bytes.Buffer
|
||||
err := json.NewEncoder(&in).Encode(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to marshal request")
|
||||
}
|
||||
|
||||
err = doJSONOnce(HandlerFunc(JSONWithStatusCode), buildCtx(), &in, &out, &buf, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
|
||||
JSONOut := &jsonOut{}
|
||||
err = json.NewDecoder(&out).Decode(JSONOut)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if JSONOut.Protocol.StatusCode != 201 {
|
||||
t.Fatalf("Response code must equal to 201, but have: %v", JSONOut.Protocol.StatusCode)
|
||||
}
|
||||
cType := JSONOut.Protocol.Headers.Get("Content-Type")
|
||||
if !strings.Contains(cType, "application/json") {
|
||||
t.Fatalf("Response content type should be application/json in this test, but have: %v", cType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP(t *testing.T) {
|
||||
// simulate fn writing us http requests...
|
||||
|
||||
bodyString := "yodawg"
|
||||
in := HTTPreq(t, bodyString)
|
||||
|
||||
var out bytes.Buffer
|
||||
ctx := buildCtx()
|
||||
err := doHTTPOnce(HandlerFunc(echoHTTPHandler), ctx, in, &out, &bytes.Buffer{}, make(http.Header))
|
||||
if err != nil {
|
||||
t.Fatal("should not return error", err)
|
||||
}
|
||||
|
||||
res, err := http.ReadResponse(bufio.NewReader(&out), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusTeapot+2 {
|
||||
t.Fatal("got wrong status code", res.StatusCode)
|
||||
}
|
||||
|
||||
outBody, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Header.Get("yo") != "dawg" {
|
||||
t.Fatal("expected yo dawg header, didn't get it")
|
||||
}
|
||||
|
||||
if string(outBody) != bodyString {
|
||||
t.Fatal("strings no matchy for http", string(outBody), bodyString)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHTTPEOF(t *testing.T) {
|
||||
var in bytes.Buffer
|
||||
var out bytes.Buffer
|
||||
ctx := buildCtx()
|
||||
|
||||
err := doHTTPOnce(HandlerFunc(echoHTTPHandler), ctx, &in, &out, &bytes.Buffer{}, make(http.Header))
|
||||
if err != io.EOF {
|
||||
t.Fatal("should return EOF")
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPreq(t *testing.T, bod string) io.Reader {
|
||||
req, err := http.NewRequest("GET", "http://localhost:8080/r/myapp/yodawg", strings.NewReader(bod))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
byts, err := httputil.DumpRequestOut(req, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return bytes.NewReader(byts)
|
||||
}
|
||||
Reference in New Issue
Block a user