Skip to content

Commit

Permalink
Fix response transform when client specify "Accept-Encoding"
Browse files Browse the repository at this point in the history
If upstream returns compressed response, in order to transform the body
we need to decompress it first, and after transformation compress
again.
  • Loading branch information
buger committed Mar 9, 2018
1 parent b67766a commit 775f627
Showing 1 changed file with 52 additions and 4 deletions.
56 changes: 52 additions & 4 deletions res_handler_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package main

import (
"bytes"
"compress/flate"
"compress/gzip"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strconv"
Expand All @@ -23,6 +26,43 @@ func (h *ResponseTransformMiddleware) Init(c interface{}, spec *APISpec) error {
return nil
}

func respBodyReader(req *http.Request, resp *http.Response) io.ReadCloser {
if req.Header.Get("Accept-Encoding") == "" {
return resp.Body
}

switch resp.Header.Get("Content-Encoding") {
case "gzip":
reader, err := gzip.NewReader(resp.Body)
if err != nil {
log.Error("Body decompression error:", err)
return ioutil.NopCloser(bytes.NewReader(nil))
}
return reader
case "deflate":
return flate.NewReader(resp.Body)
}

return resp.Body
}

func compressBuffer(in bytes.Buffer, encoding string) (out bytes.Buffer) {
switch encoding {
case "gzip":
zw := gzip.NewWriter(&out)
zw.Write(in.Bytes())
zw.Close()
case "deflate":
zw, _ := flate.NewWriter(&out, 1)
zw.Write(in.Bytes())
zw.Close()
default:
out = in
}

return out
}

func (h *ResponseTransformMiddleware) HandleResponse(rw http.ResponseWriter, res *http.Response, req *http.Request, ses *user.SessionState) error {
_, versionPaths, _, _ := h.Spec.Version(req)
found, meta := h.Spec.CheckSpecMatchesStatus(req, versionPaths, TransformedResponse)
Expand All @@ -32,16 +72,20 @@ func (h *ResponseTransformMiddleware) HandleResponse(rw http.ResponseWriter, res
}
tmeta := meta.(*TransformSpec)

// Read the body:
defer res.Body.Close()
respBody := respBodyReader(req, res)
defer respBody.Close()

// Put into an interface:
var bodyData map[string]interface{}
switch tmeta.TemplateData.Input {
case apidef.RequestXML:
mxj.XmlCharsetReader = WrappedCharsetReader
var err error
bodyData, err = mxj.NewMapXmlReader(res.Body) // unmarshal

bodyBuf, _ := ioutil.ReadAll(respBody)
log.Warning("BODY:", string(bodyBuf))

bodyData, err = mxj.NewMapXml(bodyBuf) // unmarshal
if err != nil {
log.WithFields(logrus.Fields{
"prefix": "outbound-transform",
Expand All @@ -51,7 +95,7 @@ func (h *ResponseTransformMiddleware) HandleResponse(rw http.ResponseWriter, res
}).Error("Error unmarshalling XML: ", err)
}
default: // apidef.RequestJSON
if err := json.NewDecoder(res.Body).Decode(&bodyData); err != nil {
if err := json.NewDecoder(respBody).Decode(&bodyData); err != nil {
log.WithFields(logrus.Fields{
"prefix": "outbound-transform",
"server_name": h.Spec.Proxy.TargetURL,
Expand All @@ -72,6 +116,10 @@ func (h *ResponseTransformMiddleware) HandleResponse(rw http.ResponseWriter, res
}).Error("Failed to apply template to request: ", err)
}

// Re-compress if original upstream response was compressed
encoding := res.Header.Get("Content-Encoding")
bodyBuffer = compressBuffer(bodyBuffer, encoding)

res.ContentLength = int64(bodyBuffer.Len())
res.Header.Set("Content-Length", strconv.Itoa(bodyBuffer.Len()))
res.Body = ioutil.NopCloser(&bodyBuffer)
Expand Down

0 comments on commit 775f627

Please sign in to comment.