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

Case insensitive dictionary for request headers #103

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 98 additions & 12 deletions brubeck/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
import urlparse
import re

try:
from collections import OrderedDict
except:
from ordereddict import OrderedDict


def parse_netstring(ns):
length, rest = ns.split(':', 1)
length = int(length)
assert rest[length] == ',', "Netstring did not end in ','"
return rest[:length], rest[length + 1:]


def to_bytes(data, enc='utf8'):
"""Convert anything to bytes
"""
Expand All @@ -23,6 +30,18 @@ def to_unicode(s, enc='utf8'):
return s if isinstance(s, unicode) else unicode(str(s), encoding=enc)


def uncgi(headers):
"""Cleaned up WSGI headers
"""
new_headers = Headers()
for key, value in headers.iteritems():
if key.upper().startswith('HTTP_'):
new_headers[key[5:].replace('_', '-')] = value
elif '_' in key:
new_headers[key.replace('_', '-')] = value
return new_headers


class Request(object):
"""Word.
"""
Expand Down Expand Up @@ -90,7 +109,7 @@ def _parse_mime_body(self, boundary, data, arguments, files):
continue
#headers = HTTPHeaders.parse(part[:eoh].decode("utf-8"))
header_string = part[:eoh].decode("utf-8")
headers = dict()
headers = Headers()
last_key = ''
for line in header_string.splitlines():
if line[0].isspace():
Expand Down Expand Up @@ -193,7 +212,7 @@ def parse_msg(msg):
sender, conn_id, path, rest = msg.split(' ', 3)
headers, rest = parse_netstring(rest)
body, _ = parse_netstring(rest)
headers = json.loads(headers)
headers = Headers(json.loads(headers))
# construct url from request
scheme = headers.get('URL_SCHEME', 'http')
netloc = headers.get('host')
Expand All @@ -216,20 +235,14 @@ def parse_wsgi_request(environ):
body = environ["wsgi.input"].read(int(environ['CONTENT_LENGTH']))
del environ["CONTENT_LENGTH"]
del environ["wsgi.input"]
#setting headers to environ dict with no manipulation
headers = environ
# normalize request dict
# normalize environ dict
headers = Headers(environ)
headers.update(uncgi(environ))
if 'REQUEST_METHOD' in headers:
headers['METHOD'] = headers['REQUEST_METHOD']
if 'QUERY_STRING' in headers:
headers['QUERY'] = headers['QUERY_STRING']
if 'CONTENT_TYPE' in headers:
headers['content-type'] = headers['CONTENT_TYPE']
headers['version'] = 1.1 #TODO: hardcoded!
if 'HTTP_COOKIE' in headers:
headers['cookie'] = headers['HTTP_COOKIE']
if 'HTTP_CONNECTION' in headers:
headers['connection'] = headers['HTTP_CONNECTION']
headers['VERSION'] = headers.get('SERVER_PROTOCOL', 'HTTP/1.1')
# construct url from request
scheme = headers['wsgi.url_scheme']
netloc = headers.get('HTTP_HOST')
Expand Down Expand Up @@ -294,3 +307,76 @@ def get_argument(self, name, default=None, strip=True):
if not args:
return default
return args[-1]


class Headers(OrderedDict):
"""Ordered dictionary with case insensitive lookup that preserves original
case when listing.
"""

class CaseInsensitiveString(str):
def __hash__(self):
"""
>>> hash(Headers.CaseInsensitiveString('Doc')) == hash('doc')
True
"""
return hash(self.lower())

def __eq__(self, other):
"""
>>> Headers.CaseInsensitiveString('Doc') == 'doc'
True
"""
return self.lower() == other.lower()

def __keytransform__(self, key):
return self.CaseInsensitiveString(key)

def __contains__(self, key):
"""
>>> 'USER-AGENT' in Headers({'User-Agent': 'doctest'})
True
"""
return super(Headers, self).__contains__(self.__keytransform__(key))

def __getitem__(self, key):
"""
>>> h = Headers({'User-Agent': 'doctest'})
>>> h['USER-AGENT']
'doctest'
"""
return super(Headers, self).__getitem__(self.__keytransform__(key))

def __setitem__(self, key, value):
"""
>>> h = Headers()
>>> h['User-Agent'] = 'doctest'
>>> h['USER-AGENT']
'doctest'
"""
return super(Headers, self).__setitem__(self.__keytransform__(key), value)

def __delitem__(self, key):
"""
>>> h = Headers({'User-Agent': 'doctest'})
>>> del h['USER-AGENT']
>>> 'User-Agent' not in h
True
"""
return super(Headers, self).__delitem__(self.__keytransform__(key))

def has_key(self, key):
"""
>>> Headers({'User-Agent': 'doctest'}).has_key('USER-AGENT')
True
"""
return super(Headers, self).has_key(self.__keytransform__(key))

def get(self, key, *args):
"""
>>> Headers({'User-Agent': 'doctest'}).get('USER-AGENT')
'doctest'
>>> Headers().get('User-Agent', 'doctest')
'doctest'
"""
return super(Headers, self).get(self.__keytransform__(key), *args)