3 # Copyright 2009 Facebook
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
17 """HTTP utility code shared by clients and servers."""
19 class HTTPHeaders(dict):
20 """A dictionary that maintains Http-Header-Case for all keys.
22 Supports multiple values per key via a pair of new methods,
23 add() and get_list(). The regular dictionary interface returns a single
24 value per key, with multiple values joined by a comma.
26 >>> h = HTTPHeaders({"content-type": "text/html"})
32 >>> h.add("Set-Cookie", "A=B")
33 >>> h.add("Set-Cookie", "C=D")
36 >>> h.get_list("set-cookie")
39 >>> for (k,v) in sorted(h.get_all()):
40 ... print '%s: %s' % (k,v)
42 Content-Type: text/html
46 def __init__(self, *args, **kwargs):
47 # Don't pass args or kwargs to dict.__init__, as it will bypass
51 self.update(*args, **kwargs)
55 def add(self, name, value):
56 """Adds a new value for the given key."""
57 norm_name = HTTPHeaders._normalize_name(name)
59 # bypass our override of __setitem__ since it modifies _as_list
60 dict.__setitem__(self, norm_name, self[norm_name] + ',' + value)
61 self._as_list[norm_name].append(value)
63 self[norm_name] = value
65 def get_list(self, name):
66 """Returns all values for the given header as a list."""
67 norm_name = HTTPHeaders._normalize_name(name)
68 return self._as_list.get(norm_name, [])
71 """Returns an iterable of all (name, value) pairs.
73 If a header has multiple values, multiple pairs will be
74 returned with the same name.
76 for name, list in self._as_list.iteritems():
80 def parse_line(self, line):
81 """Updates the dictionary with a single header line.
84 >>> h.parse_line("Content-Type: text/html")
85 >>> h.get('content-type')
88 name, value = line.split(":", 1)
89 self.add(name, value.strip())
92 def parse(cls, headers):
93 """Returns a dictionary from HTTP header text.
95 >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n")
96 >>> sorted(h.iteritems())
97 [('Content-Length', '42'), ('Content-Type', 'text/html')]
100 for line in headers.splitlines():
105 # dict implementation overrides
107 def __setitem__(self, name, value):
108 norm_name = HTTPHeaders._normalize_name(name)
109 dict.__setitem__(self, norm_name, value)
110 self._as_list[norm_name] = [value]
112 def __getitem__(self, name):
113 return dict.__getitem__(self, HTTPHeaders._normalize_name(name))
115 def __delitem__(self, name):
116 norm_name = HTTPHeaders._normalize_name(name)
117 dict.__delitem__(self, norm_name)
118 del self._as_list[norm_name]
120 def get(self, name, default=None):
121 return dict.get(self, HTTPHeaders._normalize_name(name), default)
123 def update(self, *args, **kwargs):
124 # dict.update bypasses our __setitem__
125 for k, v in dict(*args, **kwargs).iteritems():
129 def _normalize_name(name):
130 """Converts a name to Http-Header-Case.
132 >>> HTTPHeaders._normalize_name("coNtent-TYPE")
135 return "-".join([w.capitalize() for w in name.split("-")])
138 if __name__ == "__main__":