diff --git a/core/requestALB.go b/core/requestALB.go
new file mode 100644
index 0000000..69eb023
--- /dev/null
+++ b/core/requestALB.go
@@ -0,0 +1,210 @@
+// Package core provides utility methods that help convert ALB events
+// into an http.Request and http.ResponseWriter
+package core
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/aws/aws-lambda-go/events"
+ "github.com/aws/aws-lambda-go/lambdacontext"
+)
+
+const (
+ // ALBContextHeader is the custom header key used to store the
+ // ALB ELB context. To access the Context properties use the
+ // GetALBContext method of the RequestAccessorALB object.
+ ALBContextHeader = "X-GoLambdaProxy-ALB-Context"
+)
+
+// RequestAccessorALB objects give access to custom ALB Target Group properties
+// in the request.
+type RequestAccessorALB struct {
+ stripBasePath string
+}
+
+// GetALBContext extracts the ALB context object from a request's custom header.
+// Returns a populated events.ALBTargetGroupRequestContext object from the request.
+func (r *RequestAccessorALB) GetContextALB(req *http.Request) (events.ALBTargetGroupRequestContext, error) {
+ if req.Header.Get(ALBContextHeader) == "" {
+ return events.ALBTargetGroupRequestContext{}, errors.New("no context header in request")
+ }
+ context := events.ALBTargetGroupRequestContext{}
+ err := json.Unmarshal([]byte(req.Header.Get(ALBContextHeader)), &context)
+ if err != nil {
+ log.Println("Error while unmarshalling context")
+ log.Println(err)
+ return events.ALBTargetGroupRequestContext{}, err
+ }
+ return context, nil
+}
+
+// StripBasePath instructs the RequestAccessor object that the given base
+// path should be removed from the request path before sending it to the
+// framework for routing. This is used when API Gateway is configured with
+// base path mappings in custom domain names.
+func (r *RequestAccessorALB) StripBasePath(basePath string) string {
+ if strings.Trim(basePath, " ") == "" {
+ r.stripBasePath = ""
+ return ""
+ }
+
+ newBasePath := basePath
+ if !strings.HasPrefix(newBasePath, "/") {
+ newBasePath = "/" + newBasePath
+ }
+
+ if strings.HasSuffix(newBasePath, "/") {
+ newBasePath = newBasePath[:len(newBasePath)-1]
+ }
+
+ r.stripBasePath = newBasePath
+
+ return newBasePath
+}
+
+// ProxyEventToHTTPRequest converts an ALB Target Group Request event into a http.Request object.
+// Returns the populated http request with additional custom header for the ALB context.
+// To access these properties use the GetALBContext method of the RequestAccessorALB object.
+func (r *RequestAccessorALB) ProxyEventToHTTPRequest(req events.ALBTargetGroupRequest) (*http.Request, error) {
+ httpRequest, err := r.EventToRequest(req)
+ if err != nil {
+ log.Println(err)
+ return nil, err
+ }
+ return addToHeaderALB(httpRequest, req)
+}
+
+// EventToRequestWithContext converts an ALB Target Group Request event and context into an http.Request object.
+// Returns the populated http request with lambda context, ALB TargetGroup RequestContext as part of its context.
+func (r *RequestAccessorALB) EventToRequestWithContext(ctx context.Context, req events.ALBTargetGroupRequest) (*http.Request, error) {
+ httpRequest, err := r.EventToRequest(req)
+ if err != nil {
+ log.Println(err)
+ return nil, err
+ }
+ return addToContextALB(ctx, httpRequest, req), nil
+}
+
+// EventToRequest converts an ALB TargetGroup event into an http.Request object.
+// Returns the populated request maintaining headers
+func (r *RequestAccessorALB) EventToRequest(req events.ALBTargetGroupRequest) (*http.Request, error) {
+ decodedBody := []byte(req.Body)
+ if req.IsBase64Encoded {
+ base64Body, err := base64.StdEncoding.DecodeString(req.Body)
+ if err != nil {
+ return nil, err
+ }
+ decodedBody = base64Body
+ }
+
+ path := req.Path
+ if r.stripBasePath != "" && len(r.stripBasePath) > 1 {
+ if strings.HasPrefix(path, r.stripBasePath) {
+ path = strings.Replace(path, r.stripBasePath, "", 1)
+ }
+ }
+ if !strings.HasPrefix(path, "/") {
+ path = "/" + path
+ }
+ serverAddress := "https://" + req.Headers["host"]
+ // if customAddress, ok := os.LookupEnv(CustomHostVariable); ok {
+ // serverAddress = customAddress
+ // }
+ path = serverAddress + path
+
+ if len(req.MultiValueQueryStringParameters) > 0 {
+ queryString := ""
+ for q, l := range req.MultiValueQueryStringParameters {
+ for _, v := range l {
+ if queryString != "" {
+ queryString += "&"
+ }
+ queryString += url.QueryEscape(q) + "=" + url.QueryEscape(v)
+ }
+ }
+ path += "?" + queryString
+ } else if len(req.QueryStringParameters) > 0 {
+ // Support `QueryStringParameters` for backward compatibility.
+ // https://github.com/awslabs/aws-lambda-go-api-proxy/issues/37
+ queryString := ""
+ for q := range req.QueryStringParameters {
+ if queryString != "" {
+ queryString += "&"
+ }
+ queryString += url.QueryEscape(q) + "=" + url.QueryEscape(req.QueryStringParameters[q])
+ }
+ path += "?" + queryString
+ }
+
+ httpRequest, err := http.NewRequest(
+ strings.ToUpper(req.HTTPMethod),
+ path,
+ bytes.NewReader(decodedBody),
+ )
+
+ if err != nil {
+ fmt.Printf("Could not convert request %s:%s to http.Request\n", req.HTTPMethod, req.Path)
+ log.Println(err)
+ return nil, err
+ }
+
+ if req.MultiValueHeaders != nil {
+ for k, values := range req.MultiValueHeaders {
+ for _, value := range values {
+ httpRequest.Header.Add(k, value)
+ }
+ }
+ } else {
+ for h := range req.Headers {
+ httpRequest.Header.Add(h, req.Headers[h])
+ }
+ }
+
+ httpRequest.RequestURI = httpRequest.URL.RequestURI()
+
+ return httpRequest, nil
+}
+
+func addToHeaderALB(req *http.Request, albRequest events.ALBTargetGroupRequest) (*http.Request, error) {
+ albContext, err := json.Marshal(albRequest.RequestContext)
+ if err != nil {
+ log.Println("Could not Marshal ALB context for custom header")
+ return req, err
+ }
+ req.Header.Set(ALBContextHeader, string(albContext))
+ return req, nil
+}
+
+// adds context data to http request so we can pass
+func addToContextALB(ctx context.Context, req *http.Request, albRequest events.ALBTargetGroupRequest) *http.Request {
+ lc, _ := lambdacontext.FromContext(ctx)
+ rc := requestContextALB{lambdaContext: lc, albContext: albRequest.RequestContext}
+ ctx = context.WithValue(ctx, ctxKey{}, rc)
+ return req.WithContext(ctx)
+}
+
+// GetALBTargetGroupRequestFromContext retrieve ALBTargetGroupt from context.Context
+func GetTargetGroupRequetFromContextALB(ctx context.Context) (events.ALBTargetGroupRequestContext, bool) {
+ v, ok := ctx.Value(ctxKey{}).(requestContextALB)
+ return v.albContext, ok
+}
+
+// GetRuntimeContextFromContext retrieve Lambda Runtime Context from context.Context
+func GetRuntimeContextFromContextALB(ctx context.Context) (*lambdacontext.LambdaContext, bool) {
+ v, ok := ctx.Value(ctxKey{}).(requestContextALB)
+ return v.lambdaContext, ok
+}
+
+type requestContextALB struct {
+ lambdaContext *lambdacontext.LambdaContext
+ albContext events.ALBTargetGroupRequestContext
+}
diff --git a/core/requestALB_test.go b/core/requestALB_test.go
new file mode 100644
index 0000000..876ea19
--- /dev/null
+++ b/core/requestALB_test.go
@@ -0,0 +1,283 @@
+package core_test
+
+import (
+ "context"
+ "encoding/base64"
+ "math/rand"
+ "strings"
+
+ "github.com/awslabs/aws-lambda-go-api-proxy/core"
+
+ "github.com/aws/aws-lambda-go/events"
+ "github.com/aws/aws-lambda-go/lambdacontext"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("RequestAccessorALB tests", func() {
+ Context("ALB event conversion", func() {
+ accessor := core.RequestAccessorALB{}
+ qs := make(map[string]string)
+ mvh := make(map[string][]string)
+ mvqs := make(map[string][]string)
+ hdr := make(map[string]string)
+ qs["UniqueId"] = "12345"
+ mvh["accept"] = []string{"test", "one"}
+ mvh["connection"] = []string{"keep-alive"}
+ mvh["host"] = []string{"lambda-test-alb-1234567.us-east-1.elb.amazonaws.com"}
+ hdr["header1"] = "Testhdr1"
+ hdr["header2"] = "Testhdr2"
+ //multivalue querystrings
+ mvqs["k1"] = []string{"t1"}
+ mvqs["k2"] = []string{"t2"}
+ bdy := "Test BODY"
+ basicRequest := getALBProxyRequest("/hello", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, nil)
+
+ It("Correctly converts a basic event", func() {
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), basicRequest)
+ Expect(err).To(BeNil())
+ Expect("/hello").To(Equal(httpReq.URL.Path))
+ Expect("/hello?UniqueId=12345").To(Equal(httpReq.RequestURI))
+ Expect("GET").To(Equal(httpReq.Method))
+ headers := basicRequest.Headers
+ Expect(2).To(Equal(len(headers)))
+ mvhs := basicRequest.MultiValueHeaders
+ Expect(3).To(Equal(len(mvhs)))
+ mvqs := basicRequest.MultiValueQueryStringParameters
+ Expect(0).To(Equal(len(mvqs)))
+
+ })
+
+ binaryBody := make([]byte, 256)
+ _, err := rand.Read(binaryBody)
+ if err != nil {
+ Fail("Could not generate random binary body")
+ }
+
+ encodedBody := base64.StdEncoding.EncodeToString(binaryBody)
+
+ binaryRequest := getALBProxyRequest("/hello", "POST", getALBRequestContext(), true, hdr, bdy, qs, mvh, nil)
+ binaryRequest.Body = encodedBody
+ binaryRequest.IsBase64Encoded = true
+
+ It("Decodes a base64 encoded body", func() {
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), binaryRequest)
+ Expect(err).To(BeNil())
+ Expect("/hello").To(Equal(httpReq.URL.Path))
+ Expect("/hello?UniqueId=12345").To(Equal(httpReq.RequestURI))
+ Expect("POST").To(Equal(httpReq.Method))
+
+ Expect(err).To(BeNil())
+
+ })
+
+ mqsRequest := getALBProxyRequest("/hello", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, nil)
+ mqsRequest.QueryStringParameters = map[string]string{
+ "hello": "1",
+ "world": "2",
+ }
+ It("Populates multiple value query string correctly", func() {
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), mqsRequest)
+ Expect(err).To(BeNil())
+ Expect("/hello").To(Equal(httpReq.URL.Path))
+ Expect(httpReq.RequestURI).To(ContainSubstring("hello=1"))
+ Expect(httpReq.RequestURI).To(ContainSubstring("world=2"))
+ Expect("GET").To(Equal(httpReq.Method))
+
+ query := httpReq.URL.Query()
+ Expect(2).To(Equal(len(query)))
+ Expect(query["hello"]).ToNot(BeNil())
+ Expect(query["world"]).ToNot(BeNil())
+ Expect(1).To(Equal(len(query["hello"])))
+ Expect("1").To(Equal(query["hello"][0]))
+ Expect("2").To(Equal(query["world"][0]))
+
+ })
+
+ qsRequest := getALBProxyRequest("/hello", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, nil)
+ qsRequest.QueryStringParameters = map[string]string{
+ "hello": "1",
+ "world": "2",
+ }
+ It("Populates query string correctly", func() {
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), qsRequest)
+ Expect(err).To(BeNil())
+ Expect("/hello").To(Equal(httpReq.URL.Path))
+ Expect(httpReq.RequestURI).To(ContainSubstring("hello=1"))
+ Expect(httpReq.RequestURI).To(ContainSubstring("world=2"))
+ Expect("GET").To(Equal(httpReq.Method))
+
+ query := httpReq.URL.Query()
+ Expect(2).To(Equal(len(query)))
+ Expect(query["hello"]).ToNot(BeNil())
+ Expect(query["world"]).ToNot(BeNil())
+ Expect(1).To(Equal(len(query["hello"])))
+ Expect(1).To(Equal(len(query["world"])))
+ Expect("1").To(Equal(query["hello"][0]))
+ Expect("2").To(Equal(query["world"][0]))
+ })
+
+ // If multivaluehaders are set then it only passes the multivalue headers to the http.Request
+ mvhRequest := getALBProxyRequest("/hello", "GET", getALBRequestContext(), false, hdr, bdy, qs, nil, mvqs)
+ mvhRequest.MultiValueHeaders = map[string][]string{
+ "accept": {"test", "one"},
+ "connection": {"keep-alive"},
+ "host": {"lambda-test-alb-1234567.us-east-1.elb.amazonaws.com"},
+ }
+ It("Populates multiple value headers correctly", func() {
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), mvhRequest)
+ Expect(err).To(BeNil())
+ Expect("/hello").To(Equal(httpReq.URL.Path))
+ Expect("GET").To(Equal(httpReq.Method))
+
+ headers := httpReq.Header
+ Expect(3).To(Equal(len(headers)))
+
+ for k, value := range headers {
+ Expect(value).To(Equal(mvhRequest.MultiValueHeaders[strings.ToLower(k)]))
+ }
+
+ })
+ // If multivaluehaders are set then it only passes the multivalue headers to the http.Request
+ svhRequest := getALBProxyRequest("/hello", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, mvqs)
+ svhRequest.Headers = map[string]string{
+ "header1": "Testhdr1",
+ "header2": "Testhdr2"}
+
+ It("Populates single value headers correctly", func() {
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), svhRequest)
+ Expect(err).To(BeNil())
+ Expect("/hello").To(Equal(httpReq.URL.Path))
+ Expect("GET").To(Equal(httpReq.Method))
+
+ headers := httpReq.Header
+ Expect(3).To(Equal(len(headers)))
+
+ for k, value := range headers {
+ Expect(value).To(Equal(mvhRequest.MultiValueHeaders[strings.ToLower(k)]))
+ }
+
+ })
+
+ basePathRequest := getALBProxyRequest("/app1/orders", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, nil)
+
+ It("Stips the base path correct", func() {
+ accessor.StripBasePath("app1")
+ httpReq, err := accessor.EventToRequestWithContext(context.Background(), basePathRequest)
+
+ Expect(err).To(BeNil())
+ Expect("/orders").To(Equal(httpReq.URL.Path))
+ Expect("/orders?UniqueId=12345").To(Equal(httpReq.RequestURI))
+ })
+
+ contextRequest := getALBProxyRequest("orders", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, mvqs)
+ contextRequest.RequestContext = getALBRequestContext()
+
+ It("Populates context header correctly", func() {
+ // calling old method to verify reverse compatibility
+ httpReq, err := accessor.ProxyEventToHTTPRequest(contextRequest)
+ Expect(err).To(BeNil())
+ Expect(4).To(Equal(len(httpReq.Header)))
+ Expect(httpReq.Header.Get(core.ALBContextHeader)).ToNot(BeNil())
+ })
+ })
+
+ Context("StripBasePath tests", func() {
+ accessor := core.RequestAccessorALB{}
+ It("Adds prefix slash", func() {
+ basePath := accessor.StripBasePath("app1")
+ Expect("/app1").To(Equal(basePath))
+ })
+
+ It("Removes trailing slash", func() {
+ basePath := accessor.StripBasePath("/app1/")
+ Expect("/app1").To(Equal(basePath))
+ })
+
+ It("Ignores blank strings", func() {
+ basePath := accessor.StripBasePath(" ")
+ Expect("").To(Equal(basePath))
+ })
+ })
+
+ Context("Retrieves ALB Target Group Request context", func() {
+ It("Returns a correctly unmarshalled object", func() {
+ qs := make(map[string]string)
+ mvh := make(map[string][]string)
+ hdr := make(map[string]string)
+ mvqs := make(map[string][]string)
+ qs["UniqueId"] = "12345"
+ mvh["accept"] = []string{"*/*", "/"}
+ mvh["connection"] = []string{"keep-alive"}
+ mvh["host"] = []string{"lambda-test-alb-1234567.us-east-1.elb.amazonaws.com"}
+ mvqs["key1"] = []string{"Test1"}
+ mvqs["key2"] = []string{"test2"}
+ hdr["header1"] = "Testhdr1"
+ bdy := "Test BODY2"
+
+ contextRequest := getALBProxyRequest("/orders", "GET", getALBRequestContext(), false, hdr, bdy, qs, mvh, mvqs)
+ contextRequest.RequestContext = getALBRequestContext()
+
+ accessor := core.RequestAccessorALB{}
+ // calling old method to verify reverse compatibility
+ httpReq, err := accessor.ProxyEventToHTTPRequest(contextRequest)
+ Expect(err).To(BeNil())
+
+ headerContext, err := accessor.GetContextALB(httpReq)
+ Expect(err).To(BeNil())
+ Expect(headerContext).ToNot(BeNil())
+ Expect("arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh").To(Equal(headerContext.ELB.TargetGroupArn))
+ proxyContext, ok := core.GetTargetGroupRequetFromContextALB(httpReq.Context())
+ // should fail because using header proxy method
+ Expect(ok).To(BeFalse())
+
+ httpReq, err = accessor.EventToRequestWithContext(context.Background(), contextRequest)
+ Expect(err).To(BeNil())
+ proxyContext, ok = core.GetTargetGroupRequetFromContextALB(httpReq.Context())
+ Expect(ok).To(BeTrue())
+ Expect("arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh").To(Equal(proxyContext.ELB.TargetGroupArn))
+ runtimeContext, ok := core.GetRuntimeContextFromContextALB(httpReq.Context())
+ Expect(ok).To(BeTrue())
+ Expect(runtimeContext).To(BeNil())
+
+ lambdaContext := lambdacontext.NewContext(context.Background(), &lambdacontext.LambdaContext{AwsRequestID: "abc123"})
+ httpReq, err = accessor.EventToRequestWithContext(lambdaContext, contextRequest)
+ Expect(err).To(BeNil())
+
+ headerContext, err = accessor.GetContextALB(httpReq)
+ // should fail as new context method doesn't populate headers
+ Expect(err).ToNot(BeNil())
+ proxyContext, ok = core.GetTargetGroupRequetFromContextALB(httpReq.Context())
+ Expect(ok).To(BeTrue())
+ Expect("arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh").To(Equal(proxyContext.ELB.TargetGroupArn))
+ runtimeContext, ok = core.GetRuntimeContextFromContextALB(httpReq.Context())
+ Expect(ok).To(BeTrue())
+ Expect(runtimeContext).ToNot(BeNil())
+
+ })
+ })
+})
+
+func getALBProxyRequest(path string, method string, requestCtx events.ALBTargetGroupRequestContext,
+ is64 bool, header map[string]string, body string, qs map[string]string, mvh map[string][]string, mvqsp map[string][]string) events.ALBTargetGroupRequest {
+ return events.ALBTargetGroupRequest{
+ HTTPMethod: method,
+ Path: path,
+ QueryStringParameters: qs,
+ MultiValueQueryStringParameters: mvqsp,
+ Headers: header,
+ MultiValueHeaders: mvh,
+ RequestContext: requestCtx,
+ IsBase64Encoded: is64,
+ Body: body,
+ }
+}
+
+func getALBRequestContext() events.ALBTargetGroupRequestContext {
+ return events.ALBTargetGroupRequestContext{
+ ELB: events.ELBContext{
+ TargetGroupArn: "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh",
+ },
+ }
+}
diff --git a/core/responseALB.go b/core/responseALB.go
new file mode 100644
index 0000000..b5869d4
--- /dev/null
+++ b/core/responseALB.go
@@ -0,0 +1,112 @@
+// Package core provides utility methods that help convert proxy events
+// into an http.Request and http.ResponseWriter
+package core
+
+import (
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "net/http"
+ "unicode/utf8"
+
+ "github.com/aws/aws-lambda-go/events"
+)
+
+// ProxyResponseWriter implements http.ResponseWriter and adds the method
+// necessary to return an events.ALBTargetGroupResponse object
+type ProxyResponseWriterALB struct {
+ headers http.Header
+ body bytes.Buffer
+ status int
+ statusText string
+ observers []chan<- bool
+}
+
+// NewProxyResponseWriter returns a new ProxyResponseWriter object.
+// The object is initialized with an empty map of headers and a
+// status code of -1
+func NewProxyResponseWriterALB() *ProxyResponseWriterALB {
+ return &ProxyResponseWriterALB{
+ headers: make(http.Header),
+ status: defaultStatusCode,
+ statusText: http.StatusText(defaultStatusCode),
+ observers: make([]chan<- bool, 0),
+ }
+
+}
+
+func (r *ProxyResponseWriterALB) CloseNotify() <-chan bool {
+ ch := make(chan bool, 1)
+
+ r.observers = append(r.observers, ch)
+
+ return ch
+}
+
+func (r *ProxyResponseWriterALB) notifyClosed() {
+ for _, v := range r.observers {
+ v <- true
+ }
+}
+
+// Header implementation from the http.ResponseWriter interface.
+func (r *ProxyResponseWriterALB) Header() http.Header {
+ return r.headers
+}
+
+// Write sets the response body in the object. If no status code
+// was set before with the WriteHeader method it sets the status
+// for the response to 200 OK.
+func (r *ProxyResponseWriterALB) Write(body []byte) (int, error) {
+ if r.status == defaultStatusCode {
+ r.status = http.StatusOK
+ }
+
+ // if the content type header is not set when we write the body we try to
+ // detect one and set it by default. If the content type cannot be detected
+ // it is automatically set to "application/octet-stream" by the
+ // DetectContentType method
+ if r.Header().Get(contentTypeHeaderKey) == "" {
+ r.Header().Add(contentTypeHeaderKey, http.DetectContentType(body))
+ }
+
+ return (&r.body).Write(body)
+}
+
+// WriteHeader sets a status code for the response. This method is used
+// for error responses.
+func (r *ProxyResponseWriterALB) WriteHeader(status int) {
+ r.status = status
+}
+
+// GetProxyResponse converts the data passed to the response writer into
+// an events.ALBTargetGroupResponse object.
+// Returns a populated proxy response object. If the response is invalid, for example
+// has no headers or an invalid status code returns an error.
+func (r *ProxyResponseWriterALB) GetProxyResponse() (events.ALBTargetGroupResponse, error) {
+ r.notifyClosed()
+
+ if r.status == defaultStatusCode {
+ return events.ALBTargetGroupResponse{}, errors.New("status code not set on response")
+ }
+
+ var output string
+ isBase64 := false
+
+ bb := (&r.body).Bytes()
+
+ if utf8.Valid(bb) {
+ output = string(bb)
+ } else {
+ output = base64.StdEncoding.EncodeToString(bb)
+ isBase64 = true
+ }
+
+ return events.ALBTargetGroupResponse{
+ StatusCode: r.status,
+ StatusDescription: http.StatusText(r.status),
+ MultiValueHeaders: http.Header(r.headers),
+ Body: output,
+ IsBase64Encoded: isBase64,
+ }, nil
+}
diff --git a/core/responseALB_test.go b/core/responseALB_test.go
new file mode 100644
index 0000000..24ace2b
--- /dev/null
+++ b/core/responseALB_test.go
@@ -0,0 +1,180 @@
+package core
+
+import (
+ "encoding/base64"
+ "math/rand"
+ "net/http"
+ "strings"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("ResponseWriterALB tests", func() {
+ Context("ALB writing to response object", func() {
+ response := NewProxyResponseWriterALB()
+
+ It("Sets the correct default status", func() {
+ Expect(defaultStatusCode).To(Equal(response.status))
+ })
+
+ It("Initializes the headers map", func() {
+ Expect(response.headers).ToNot(BeNil())
+ Expect(0).To(Equal(len(response.headers)))
+ })
+
+ It("Writes headers correctly", func() {
+ response.Header().Add("Content-Type", "application/json")
+
+ Expect(1).To(Equal(len(response.headers)))
+ Expect("application/json").To(Equal(response.headers["Content-Type"][0]))
+ })
+
+ It("Writes body content correctly", func() {
+ binaryBody := make([]byte, 256)
+ _, err := rand.Read(binaryBody)
+ Expect(err).To(BeNil())
+
+ written, err := response.Write(binaryBody)
+ Expect(err).To(BeNil())
+ Expect(len(binaryBody)).To(Equal(written))
+ })
+
+ It("Automatically set the status code to 200", func() {
+ Expect(http.StatusOK).To(Equal(response.status))
+ })
+
+ It("Forces the status to a new code", func() {
+ response.WriteHeader(http.StatusAccepted)
+ Expect(http.StatusAccepted).To(Equal(response.status))
+ })
+ })
+
+ Context("Automatically set response content type", func() {
+ xmlBodyContent := "