internal/proxy/response.go

Summary

Maintainability
A
0 mins
Test Coverage
package proxy

import (
    "bytes"
    "encoding/base64"
    "mime"
    "net/http"
    "strings"

    "github.com/apex/up/internal/util"
)

// ResponseWriter implements the http.ResponseWriter interface
// in order to support the API Gateway Lambda HTTP "protocol".
type ResponseWriter struct {
    out         Output
    buf         bytes.Buffer
    header      http.Header
    wroteHeader bool
}

// NewResponse returns a new response writer to capture http output.
func NewResponse() *ResponseWriter {
    return &ResponseWriter{}
}

// Header implementation.
func (w *ResponseWriter) Header() http.Header {
    if w.header == nil {
        w.header = make(http.Header)
    }

    return w.header
}

// Write implementation.
func (w *ResponseWriter) Write(b []byte) (int, error) {
    if !w.wroteHeader {
        w.WriteHeader(http.StatusOK)
    }

    // TODO: HEAD? ignore

    return w.buf.Write(b)
}

// WriteHeader implementation.
func (w *ResponseWriter) WriteHeader(status int) {
    if w.wroteHeader {
        return
    }

    if w.Header().Get("Content-Type") == "" {
        w.Header().Set("Content-Type", "text/plain; charset=utf8")
    }

    w.out.StatusCode = status

    h := make(map[string]string)

    // API Gateway does not support multiple set-cookie fields
    // so we have to stagger the casing in order to support this.
    util.FixMultipleSetCookie(w.Header())

    for k, v := range w.Header() {
        if len(v) > 0 {
            h[k] = v[len(v)-1]
        }
    }

    w.out.Headers = h
    w.wroteHeader = true
}

// End the request.
func (w *ResponseWriter) End() Output {
    w.out.IsBase64Encoded = isBinary(w.header)

    if w.out.IsBase64Encoded {
        w.out.Body = base64.StdEncoding.EncodeToString(w.buf.Bytes())
    } else {
        w.out.Body = w.buf.String()
    }

    return w.out
}

// isBinary returns true if the response reprensents binary.
func isBinary(h http.Header) bool {
    if !isTextMime(h.Get("Content-Type")) {
        return true
    }

    if h.Get("Content-Encoding") == "gzip" {
        return true
    }

    return false
}

// isTextMime returns true if the content type represents textual data.
func isTextMime(kind string) bool {
    mt, _, err := mime.ParseMediaType(kind)
    if err != nil {
        return false
    }

    if strings.HasPrefix(mt, "text/") {
        return true
    }

    switch mt {
    case "image/svg+xml":
        return true
    case "application/json":
        return true
    case "application/xml":
        return true
    default:
        return false
    }
}