25 from .. import ( |
25 from .. import ( |
26 error, |
26 error, |
27 pycompat, |
27 pycompat, |
28 util, |
28 util, |
29 ) |
29 ) |
|
30 |
|
31 class multidict(object): |
|
32 """A dict like object that can store multiple values for a key. |
|
33 |
|
34 Used to store parsed request parameters. |
|
35 |
|
36 This is inspired by WebOb's class of the same name. |
|
37 """ |
|
38 def __init__(self): |
|
39 # Stores (key, value) 2-tuples. This isn't the most efficient. But we |
|
40 # don't rely on parameters that much, so it shouldn't be a perf issue. |
|
41 # we can always add dict for fast lookups. |
|
42 self._items = [] |
|
43 |
|
44 def __getitem__(self, key): |
|
45 """Returns the last set value for a key.""" |
|
46 for k, v in reversed(self._items): |
|
47 if k == key: |
|
48 return v |
|
49 |
|
50 raise KeyError(key) |
|
51 |
|
52 def __setitem__(self, key, value): |
|
53 """Replace a values for a key with a new value.""" |
|
54 try: |
|
55 del self[key] |
|
56 except KeyError: |
|
57 pass |
|
58 |
|
59 self._items.append((key, value)) |
|
60 |
|
61 def __delitem__(self, key): |
|
62 """Delete all values for a key.""" |
|
63 oldlen = len(self._items) |
|
64 |
|
65 self._items[:] = [(k, v) for k, v in self._items if k != key] |
|
66 |
|
67 if oldlen == len(self._items): |
|
68 raise KeyError(key) |
|
69 |
|
70 def __contains__(self, key): |
|
71 return any(k == key for k, v in self._items) |
|
72 |
|
73 def __len__(self): |
|
74 return len(self._items) |
|
75 |
|
76 def get(self, key, default=None): |
|
77 try: |
|
78 return self.__getitem__(key) |
|
79 except KeyError: |
|
80 return default |
|
81 |
|
82 def add(self, key, value): |
|
83 """Add a new value for a key. Does not replace existing values.""" |
|
84 self._items.append((key, value)) |
|
85 |
|
86 def getall(self, key): |
|
87 """Obtains all values for a key.""" |
|
88 return [v for k, v in self._items if k == key] |
|
89 |
|
90 def getone(self, key): |
|
91 """Obtain a single value for a key. |
|
92 |
|
93 Raises KeyError if key not defined or it has multiple values set. |
|
94 """ |
|
95 vals = self.getall(key) |
|
96 |
|
97 if not vals: |
|
98 raise KeyError(key) |
|
99 |
|
100 if len(vals) > 1: |
|
101 raise KeyError('multiple values for %r' % key) |
|
102 |
|
103 return vals[0] |
|
104 |
|
105 def asdictoflists(self): |
|
106 d = {} |
|
107 for k, v in self._items: |
|
108 if k in d: |
|
109 d[k].append(v) |
|
110 else: |
|
111 d[k] = [v] |
|
112 |
|
113 return d |
30 |
114 |
31 @attr.s(frozen=True) |
115 @attr.s(frozen=True) |
32 class parsedrequest(object): |
116 class parsedrequest(object): |
33 """Represents a parsed WSGI request. |
117 """Represents a parsed WSGI request. |
34 |
118 |
54 # Whether there is a path component to this request. This can be true |
138 # Whether there is a path component to this request. This can be true |
55 # when ``dispatchpath`` is empty due to REPO_NAME muckery. |
139 # when ``dispatchpath`` is empty due to REPO_NAME muckery. |
56 havepathinfo = attr.ib() |
140 havepathinfo = attr.ib() |
57 # Raw query string (part after "?" in URL). |
141 # Raw query string (part after "?" in URL). |
58 querystring = attr.ib() |
142 querystring = attr.ib() |
59 # List of 2-tuples of query string arguments. |
143 # multidict of query string parameters. |
60 querystringlist = attr.ib() |
144 qsparams = attr.ib() |
61 # Dict of query string arguments. Values are lists with at least 1 item. |
|
62 querystringdict = attr.ib() |
|
63 # wsgiref.headers.Headers instance. Operates like a dict with case |
145 # wsgiref.headers.Headers instance. Operates like a dict with case |
64 # insensitive keys. |
146 # insensitive keys. |
65 headers = attr.ib() |
147 headers = attr.ib() |
66 # Request body input stream. |
148 # Request body input stream. |
67 bodyfh = attr.ib() |
149 bodyfh = attr.ib() |
155 |
237 |
156 querystring = env.get('QUERY_STRING', '') |
238 querystring = env.get('QUERY_STRING', '') |
157 |
239 |
158 # We store as a list so we have ordering information. We also store as |
240 # We store as a list so we have ordering information. We also store as |
159 # a dict to facilitate fast lookup. |
241 # a dict to facilitate fast lookup. |
160 querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True) |
242 qsparams = multidict() |
161 |
243 for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True): |
162 querystringdict = {} |
244 qsparams.add(k, v) |
163 for k, v in querystringlist: |
|
164 if k in querystringdict: |
|
165 querystringdict[k].append(v) |
|
166 else: |
|
167 querystringdict[k] = [v] |
|
168 |
245 |
169 # HTTP_* keys contain HTTP request headers. The Headers structure should |
246 # HTTP_* keys contain HTTP request headers. The Headers structure should |
170 # perform case normalization for us. We just rewrite underscore to dash |
247 # perform case normalization for us. We just rewrite underscore to dash |
171 # so keys match what likely went over the wire. |
248 # so keys match what likely went over the wire. |
172 headers = [] |
249 headers = [] |
195 advertisedbaseurl=advertisedbaseurl, |
272 advertisedbaseurl=advertisedbaseurl, |
196 apppath=apppath, |
273 apppath=apppath, |
197 dispatchparts=dispatchparts, dispatchpath=dispatchpath, |
274 dispatchparts=dispatchparts, dispatchpath=dispatchpath, |
198 havepathinfo='PATH_INFO' in env, |
275 havepathinfo='PATH_INFO' in env, |
199 querystring=querystring, |
276 querystring=querystring, |
200 querystringlist=querystringlist, |
277 qsparams=qsparams, |
201 querystringdict=querystringdict, |
|
202 headers=headers, |
278 headers=headers, |
203 bodyfh=bodyfh) |
279 bodyfh=bodyfh) |
204 |
280 |
205 class wsgiresponse(object): |
281 class wsgiresponse(object): |
206 """Represents a response to a WSGI request. |
282 """Represents a response to a WSGI request. |
348 self.threaded = wsgienv[r'wsgi.multithread'] |
424 self.threaded = wsgienv[r'wsgi.multithread'] |
349 self.multiprocess = wsgienv[r'wsgi.multiprocess'] |
425 self.multiprocess = wsgienv[r'wsgi.multiprocess'] |
350 self.run_once = wsgienv[r'wsgi.run_once'] |
426 self.run_once = wsgienv[r'wsgi.run_once'] |
351 self.env = wsgienv |
427 self.env = wsgienv |
352 self.req = parserequestfromenv(wsgienv, inp) |
428 self.req = parserequestfromenv(wsgienv, inp) |
353 self.form = self.req.querystringdict |
429 self.form = self.req.qsparams.asdictoflists() |
354 self.res = wsgiresponse(self.req, start_response) |
430 self.res = wsgiresponse(self.req, start_response) |
355 self._start_response = start_response |
431 self._start_response = start_response |
356 self.server_write = None |
432 self.server_write = None |
357 self.headers = [] |
433 self.headers = [] |
358 |
434 |