mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
except just copying content from request body to STDIN we need to write encoded data, so we're using STDIN JSON stream encoder.
134 lines
2.9 KiB
Go
134 lines
2.9 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
// This is sent into the function
|
|
// All HTTP request headers should be set in env
|
|
type JSONIO struct {
|
|
Headers http.Header `json:"headers,omitempty"`
|
|
Body string `json:"body"`
|
|
StatusCode int `json:"status_code,omitempty"`
|
|
}
|
|
|
|
// JSONProtocol converts stdin/stdout streams from HTTP into JSON format.
|
|
type JSONProtocol struct {
|
|
in io.Writer
|
|
out io.Reader
|
|
}
|
|
|
|
func (p *JSONProtocol) IsStreamable() bool {
|
|
return true
|
|
}
|
|
|
|
type RequestEncoder struct {
|
|
*json.Encoder
|
|
}
|
|
|
|
func (h *JSONProtocol) DumpJSON(w io.Writer, req *http.Request) error {
|
|
stdin := json.NewEncoder(h.in)
|
|
_, err := io.WriteString(h.in, `{`)
|
|
if err != nil {
|
|
// this shouldn't happen
|
|
return err
|
|
}
|
|
if req.Body != nil {
|
|
_, err := io.WriteString(h.in, `"body":"`)
|
|
if err != nil {
|
|
// this shouldn't happen
|
|
return err
|
|
}
|
|
bb := new(bytes.Buffer)
|
|
_, err = bb.ReadFrom(req.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = stdin.Encode(bb.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.WriteString(h.in, `",`)
|
|
if err != nil {
|
|
// this shouldn't happen
|
|
return err
|
|
}
|
|
defer bb.Reset()
|
|
defer req.Body.Close()
|
|
}
|
|
_, err = io.WriteString(h.in, `"headers:"`)
|
|
if err != nil {
|
|
// this shouldn't happen
|
|
return err
|
|
}
|
|
err = stdin.Encode(req.Header)
|
|
if err != nil {
|
|
// this shouldn't happen
|
|
return err
|
|
}
|
|
_, err = io.WriteString(h.in, `"}`)
|
|
if err != nil {
|
|
// this shouldn't happen
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *JSONProtocol) Dispatch(w io.Writer, req *http.Request) error {
|
|
err := h.DumpJSON(w, req)
|
|
if err != nil {
|
|
return respondWithError(
|
|
w, fmt.Errorf("unable to write JSON into STDIN: %s", err.Error()))
|
|
}
|
|
jout := new(JSONIO)
|
|
dec := json.NewDecoder(h.out)
|
|
if err := dec.Decode(jout); err != nil {
|
|
return respondWithError(
|
|
w, fmt.Errorf("unable to decode JSON response object: %s", err.Error()))
|
|
}
|
|
if rw, ok := w.(http.ResponseWriter); ok {
|
|
// this has to be done for pulling out:
|
|
// - status code
|
|
// - body
|
|
// - headers
|
|
for k, vs := range jout.Headers {
|
|
for _, v := range vs {
|
|
rw.Header().Add(k, v) // on top of any specified on the route
|
|
}
|
|
}
|
|
rw.WriteHeader(jout.StatusCode)
|
|
_, err = rw.Write([]byte(jout.Body)) // TODO timeout
|
|
if err != nil {
|
|
return respondWithError(
|
|
w, fmt.Errorf("unable to write JSON response object: %s", err.Error()))
|
|
}
|
|
} else {
|
|
// logs can just copy the full thing in there, headers and all.
|
|
err = json.NewEncoder(w).Encode(jout)
|
|
if err != nil {
|
|
return respondWithError(
|
|
w, fmt.Errorf("error writing function response: %s", err.Error()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func respondWithError(w io.Writer, err error) error {
|
|
errMsg := []byte(err.Error())
|
|
statusCode := http.StatusInternalServerError
|
|
if rw, ok := w.(http.ResponseWriter); ok {
|
|
rw.WriteHeader(statusCode)
|
|
rw.Write(errMsg)
|
|
} else {
|
|
// logs can just copy the full thing in there, headers and all.
|
|
w.Write(errMsg)
|
|
}
|
|
|
|
return err
|
|
}
|