mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Http stream invoke tests (#1231)
* adds parity level of testing http-stream invoke the other formats had a gamut of tests, now http-stream does too. this makes obvious some of its behaviors. some things changed / can change now that we don't have pipes to worry about, the main one being that when containers blow up now the uds client will get an EOF/ECONNREFUSED instead of the pipe getting wedged up (allowing us to get the container error easily, previously). I made my best 50% effort to make a reasonable error for when this happens (similar to when http/json received garbage errors), open to ideas on verbiage / policy there. should be pretty straightforward. one thing to notice is that http/json/default don't return our fancy new Fn-Http-Status or Fn-Http-H headers... it's relatively easy to go add this to fdk-go just to test this, but for invoke I'm really not sure we care (?) and for the gateway, the output will be identical with the old formats bypassing the header decap. if anybody has any feelings, feel free to express them. * fix oomer up for new error * Adding http header stripping to agent Adding the header stripping into the agent, this should be low enough that all routes to fns get treated the same.
This commit is contained in:
@@ -696,24 +696,28 @@ func (s *hotSlot) exec(ctx context.Context, call *call) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *hotSlot) dispatch(ctx context.Context, call *call) chan error {
|
||||
ctx, span := trace.StartSpan(ctx, "agent_dispatch_httpstream")
|
||||
defer span.End()
|
||||
|
||||
// TODO we can't trust that resp.Write doesn't timeout, even if the http
|
||||
// client should respect the request context (right?) so we still need this (right?)
|
||||
errApp := make(chan error, 1)
|
||||
var removeHeaders = map[string]bool{
|
||||
"connection": true,
|
||||
"keep-alive": true,
|
||||
"trailer": true,
|
||||
"transfer-encoding": true,
|
||||
"te": true,
|
||||
"upgrade": true,
|
||||
"authorization": true,
|
||||
}
|
||||
|
||||
func callToHTTPRequest(ctx context.Context, call *call) (*http.Request, error) {
|
||||
req, err := http.NewRequest("POST", "http://localhost/call", call.req.Body)
|
||||
if err != nil {
|
||||
errApp <- err
|
||||
return errApp
|
||||
return req, err
|
||||
}
|
||||
|
||||
req.Header = make(http.Header)
|
||||
for k, vs := range call.req.Header {
|
||||
for _, v := range vs {
|
||||
req.Header.Add(k, v)
|
||||
if !removeHeaders[strings.ToLower(k)] {
|
||||
for _, v := range vs {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,14 +730,31 @@ func (s *hotSlot) dispatch(ctx context.Context, call *call) chan error {
|
||||
deadlineStr := deadline.Format(time.RFC3339)
|
||||
req.Header.Set("Fn-Deadline", deadlineStr)
|
||||
req.Header.Set("FN_DEADLINE", deadlineStr)
|
||||
}
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
func (s *hotSlot) dispatch(ctx context.Context, call *call) chan error {
|
||||
ctx, span := trace.StartSpan(ctx, "agent_dispatch_httpstream")
|
||||
defer span.End()
|
||||
|
||||
// TODO we can't trust that resp.Write doesn't timeout, even if the http
|
||||
// client should respect the request context (right?) so we still need this (right?)
|
||||
errApp := make(chan error, 1)
|
||||
|
||||
req, err := callToHTTPRequest(ctx, call)
|
||||
|
||||
if err != nil {
|
||||
errApp <- err
|
||||
return errApp
|
||||
}
|
||||
|
||||
go func() {
|
||||
resp, err := s.udsClient.Do(req)
|
||||
if err != nil {
|
||||
common.Logger(ctx).WithError(err).Debug("Got error from UDS socket")
|
||||
errApp <- err
|
||||
common.Logger(ctx).WithError(err).Error("Got error from UDS socket")
|
||||
errApp <- models.NewAPIError(http.StatusBadGateway, errors.New("error receiving function response"))
|
||||
return
|
||||
}
|
||||
common.Logger(ctx).WithField("status", resp.StatusCode).Debug("Got resp from UDS socket")
|
||||
@@ -741,7 +762,7 @@ func (s *hotSlot) dispatch(ctx context.Context, call *call) chan error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
select {
|
||||
case errApp <- writeResp(resp, call.w):
|
||||
case errApp <- writeResp(s.cfg.MaxResponseSize, resp, call.w):
|
||||
case <-ctx.Done():
|
||||
errApp <- ctx.Err()
|
||||
}
|
||||
@@ -750,12 +771,15 @@ func (s *hotSlot) dispatch(ctx context.Context, call *call) chan error {
|
||||
}
|
||||
|
||||
// XXX(reed): dupe code in http proto (which will die...)
|
||||
func writeResp(resp *http.Response, w io.Writer) error {
|
||||
func writeResp(max uint64, resp *http.Response, w io.Writer) error {
|
||||
rw, ok := w.(http.ResponseWriter)
|
||||
if !ok {
|
||||
w = common.NewClampWriter(rw, max, models.ErrFunctionResponseTooBig)
|
||||
return resp.Write(w)
|
||||
}
|
||||
|
||||
rw = newSizerRespWriter(max, rw)
|
||||
|
||||
// if we're writing directly to the response writer, we need to set headers
|
||||
// and status code, and only copy the body. resp.Write would copy a full
|
||||
// http request into the response body (not what we want).
|
||||
@@ -772,6 +796,24 @@ func writeResp(resp *http.Response, w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// XXX(reed): this is a remnant of old io.pipe plumbing, we need to get rid of
|
||||
// the buffers from the front-end in actuality, but only after removing other formats... so here, eat this
|
||||
type sizerRespWriter struct {
|
||||
http.ResponseWriter
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = new(sizerRespWriter)
|
||||
|
||||
func newSizerRespWriter(max uint64, rw http.ResponseWriter) http.ResponseWriter {
|
||||
return &sizerRespWriter{
|
||||
ResponseWriter: rw,
|
||||
w: common.NewClampWriter(rw, max, models.ErrFunctionResponseTooBig),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sizerRespWriter) Write(b []byte) (int, error) { return s.w.Write(b) }
|
||||
|
||||
// TODO remove
|
||||
func (s *hotSlot) dispatchOldFormats(ctx context.Context, call *call) chan error {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user