Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Gateway Proxy: headers are case sensitive #117

Open
cloudlena opened this issue Sep 11, 2018 · 7 comments
Open

API Gateway Proxy: headers are case sensitive #117

cloudlena opened this issue Sep 11, 2018 · 7 comments

Comments

@cloudlena
Copy link

cloudlena commented Sep 11, 2018

Accessing the headers from an APIGatewayProxyRequest event as request.Headers["Authorization"] makes them case sensitive. HTTP headers are case insensitive by definition. I think it would make sense to have a getter method for them like the one in net/http where case sensitivity would be handled.

- request.Headers["Authorization"]
+ request.Header.Get("Authorization")
@ilazakis
Copy link

ilazakis commented Mar 5, 2019

Any progress on this? Thanks!

@sbstjn
Copy link

sbstjn commented Apr 3, 2019

Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion. However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2. A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6).

https://httpwg.org/specs/rfc7540.html#HttpHeaders

@jspri
Copy link

jspri commented Jun 20, 2019

For a workaround using the stdlib, adjusted using #117 (comment) below.

import (
	"context"
	"net/http"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func HandleRequest(_ context.Context, input events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	headers := http.Header{}

	for header, values := range request.MultiValueHeaders {
		for _, value := range values {
			headers.Add(header, value)
		}
	}

	foo := headers.Get("Foo")

        // ...
}

func main() {
	lambda.Start(HandleRequest)
}

@piotrkubisa
Copy link
Contributor

piotrkubisa commented Jun 20, 2019

@Crazometer: Heads up, you might encounter a problem when one of your headers is lowercased. Let's take a following unit test as an illustrative example:

package lambda_test

import (
	"net/http"
	"testing"

	"github.com/aws/aws-lambda-go/events"
	"github.com/stretchr/testify/assert"
)

func TestNewRequest_header(t *testing.T) {
	e := events.APIGatewayProxyRequest{
		MultiValueHeaders: map[string][]string{
			"Content-Type": {"application/json"},
			"host":         {"localhost"},
			"X-Foo":        {"bar"},
		},
	}

	headers := http.Header(e.MultiValueHeaders)

	assert.Equal(t, `application/json`, headers.Get("Content-Type"))
	assert.Equal(t, `application/json`, headers.Get("content-type"))

	assert.Equal(t, `localhost`, headers.Get("Host"))
	assert.Equal(t, `localhost`, headers.Get("host"))

	assert.Equal(t, `bar`, headers.Get("x-Foo"))
	assert.Equal(t, `bar`, headers.Get("X-foo"))
}

If I would run this test I will encounter two errors and following output:

=== RUN   TestNewRequest_header
--- FAIL: TestNewRequest_header (0.00s)
    ./request_test.go:25: 
        	Error Trace:	request_test.go:25
        	Error:      	Not equal: 
        	            	expected: "localhost"
        	            	actual  : ""
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1 +1 @@
        	            	-localhost
        	            	+
        	Test:       	TestNewRequest_header
    ./request_test.go:26: 
        	Error Trace:	request_test.go:26
        	Error:      	Not equal: 
        	            	expected: "localhost"
        	            	actual  : ""
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1 +1 @@
        	            	-localhost
        	            	+
        	Test:       	TestNewRequest_header
FAIL
FAIL	lambda_test	0.003s
Error: Tests failed.

I am author of the https://github.com/piotrkubisa/apigo, a package that behaves like L7 proxy for APIG, and for aforementioned "issue" I used the loop with a http.Header.Add function to stop worring if input headers are given lowercased or not in the APIG event (take a look at: https://github.com/piotrkubisa/apigo/blob/656834471275a41b2766599f7f5b93cc739ec097/request.go#L130-L136).

http.Header is a map behind with a definition of type similar to MultiValueHeaders, but when we just do http.Header(request.MultiValueHeaders) we don't call logic from stdlib that converts all headers that make a feel of their case-insensitive nature i.e. when obtaining Host header. Of course, this operation consumes some CPU cycles to compute unified headers, but I would not say that is very noticeable (you might see a benchmark on https://gist.github.com/piotrkubisa/03e04aaa8a4c5992189c556596f12689).

@jspri
Copy link

jspri commented Jun 21, 2019

@piotrkubisa Thanks, that's fairly counter intuitive to me but makes sense with the example.

Updated the workaround snippet!

New test

package main

import (
	"net/http"
	"testing"

	"github.com/aws/aws-lambda-go/events"
	"github.com/stretchr/testify/assert"
)

func TestNewRequest_header(t *testing.T) {
	e := events.APIGatewayProxyRequest{
		MultiValueHeaders: map[string][]string{
			"Content-Type": {"application/json"},
			"host":         {"localhost"},
			"X-Foo":        {"bar"},
		},
	}

	headers := http.Header{}

	for header, values := range e.MultiValueHeaders {
		for _, value := range values {
			headers.Add(header, value)
		}
	}

	assert.Equal(t, `application/json`, headers.Get("Content-Type"))
	assert.Equal(t, `application/json`, headers.Get("content-type"))

	assert.Equal(t, `localhost`, headers.Get("Host"))
	assert.Equal(t, `localhost`, headers.Get("host"))

	assert.Equal(t, `bar`, headers.Get("x-Foo"))
	assert.Equal(t, `bar`, headers.Get("X-foo"))
}

//PASS
//ok      scratch 0.016s

@evanfuller
Copy link

evanfuller commented Mar 5, 2020

I'd like to add that the same problem occurs when accessing headers from the ALBTargetGroupRequest struct.

Generally, I use the gin framework, which exposes a GetHeader() method to deal with case-sensitivity. Under the hood, it uses net/textproto.CanonicalMIMEHeaderKey(s string) to canonicalize all headers.

What are the odds that these AWS structs could be augmented with a similar method?

@p0n
Copy link

p0n commented May 19, 2020

Any progress?
Seems REST API provides “Authorization” header, but HTTP API provides “authorization”. It’ll be helpful if SDK handles this kind of things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants