tomwojcik/starlette-context

lower-case key name for plugin headers

schunka opened this issue · 2 comments

Hello,
plugin system for headers extracting and placing it in context is fine, but I think i would be better if those headers in context could be accesible case insesitive or at least lower-case.
This would help in when I'm not sure what case this library used for keys.

For example x-request-id is here on key X-Request-ID but many users/devs would expect X-Request-Id or x-request-id and unless you dive in a code to explore what key your value resides on you just guessing (and in many cases incorrectly). This is counter intuitive.

Best case would be the same as for headers itself. Headers are stored as case insesitive multidict and it would be awesome to replicate this "case insesitiveness". Cheapest and more intuitive solution (than the current one) would be using lower keys for context keys based on headers. That will give me better chance to guess the key.

Thank you

you don't have to guess the key, you can just import it where it's defined from the plugin itself.
ie:

from starlette_context.plugins import RequestIdPlugin

async def my_code_using_the_x_request_id_header():
    request_id_key = RequestIdPlugin.key
    # etc using the value

I guess it just need to be better documented.

Thanks @hhamana for answering.

One thing that's worth adding is - it depends. Some RFC mention headers are case sensitive but as of rfc2616, they should not be case sensitive.

I think it's relatively simple to adjust what's put in context.

I use the names that are popular by convention when you google them, for example that's how they are presented on wikipedia.

image
and those are constants I use

class HeaderKeys(str, Enum):
api_key = "X-API-Key"
correlation_id = "X-Correlation-ID"
request_id = "X-Request-ID"
date = "Date"
forwarded_for = "X-Forwarded-For"
user_agent = "User-Agent"

Starlette by default changes all headers to lowercase, as they should.

>>> grep -nri 'lower' .
./testclient.py:124:            (key.lower().encode(), value.encode())
./responses.py:70:                (k.lower().encode("latin-1"), v.encode("latin-1"))
./responses.py:123:            assert samesite.lower() in [
./schemas.py:71:                        EndpointInfo(route.path, method.lower(), route.endpoint)
./schemas.py:79:                        EndpointInfo(route.path, method.lower(), func)
./middleware/cors.py:61:        self.allow_headers = [h.lower() for h in allow_headers]
./middleware/cors.py:125:            for header in [h.lower() for h in requested_headers.split(",")]:
./middleware/wsgi.py:124:                (name.strip().encode("ascii").lower(), value.strip().encode("ascii"))
./endpoints.py:26:        handler_name = "get" if request.method == "HEAD" else request.method.lower()
./datastructures.py:503:                (key.lower().encode("latin-1"), value.encode("latin-1"))
./datastructures.py:535:        get_header_key = key.lower().encode("latin-1")
./datastructures.py:546:        get_header_key = key.lower().encode("latin-1")
./datastructures.py:553:        get_header_key = key.lower().encode("latin-1")
./datastructures.py:584:        set_key = key.lower().encode("latin-1")
./datastructures.py:605:        del_key = key.lower().encode("latin-1")
./datastructures.py:624:        set_key = key.lower().encode("latin-1")
./datastructures.py:641:        append_key = key.lower().encode("latin-1")
./config.py:98:            value = value.lower()
./formparsers.py:209:                    field = header_field.lower()

So at the end of the day you just need to glue everything together so it works.