##// END OF EJS Templates
hgweb: use a multidict for holding query string parameters...
Gregory Szorc -
r36878:ec0af9c5 default
parent child Browse files
Show More
@@ -28,6 +28,90 b' from .. import ('
28 28 util,
29 29 )
30 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
114
31 115 @attr.s(frozen=True)
32 116 class parsedrequest(object):
33 117 """Represents a parsed WSGI request.
@@ -56,10 +140,8 b' class parsedrequest(object):'
56 140 havepathinfo = attr.ib()
57 141 # Raw query string (part after "?" in URL).
58 142 querystring = attr.ib()
59 # List of 2-tuples of query string arguments.
60 querystringlist = attr.ib()
61 # Dict of query string arguments. Values are lists with at least 1 item.
62 querystringdict = attr.ib()
143 # multidict of query string parameters.
144 qsparams = attr.ib()
63 145 # wsgiref.headers.Headers instance. Operates like a dict with case
64 146 # insensitive keys.
65 147 headers = attr.ib()
@@ -157,14 +239,9 b' def parserequestfromenv(env, bodyfh):'
157 239
158 240 # We store as a list so we have ordering information. We also store as
159 241 # a dict to facilitate fast lookup.
160 querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True)
161
162 querystringdict = {}
163 for k, v in querystringlist:
164 if k in querystringdict:
165 querystringdict[k].append(v)
166 else:
167 querystringdict[k] = [v]
242 qsparams = multidict()
243 for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True):
244 qsparams.add(k, v)
168 245
169 246 # HTTP_* keys contain HTTP request headers. The Headers structure should
170 247 # perform case normalization for us. We just rewrite underscore to dash
@@ -197,8 +274,7 b' def parserequestfromenv(env, bodyfh):'
197 274 dispatchparts=dispatchparts, dispatchpath=dispatchpath,
198 275 havepathinfo='PATH_INFO' in env,
199 276 querystring=querystring,
200 querystringlist=querystringlist,
201 querystringdict=querystringdict,
277 qsparams=qsparams,
202 278 headers=headers,
203 279 bodyfh=bodyfh)
204 280
@@ -350,7 +426,7 b' class wsgirequest(object):'
350 426 self.run_once = wsgienv[r'wsgi.run_once']
351 427 self.env = wsgienv
352 428 self.req = parserequestfromenv(wsgienv, inp)
353 self.form = self.req.querystringdict
429 self.form = self.req.qsparams.asdictoflists()
354 430 self.res = wsgiresponse(self.req, start_response)
355 431 self._start_response = start_response
356 432 self.server_write = None
@@ -79,7 +79,7 b' class httpv1protocolhandler(wireprototyp'
79 79 return [data[k] for k in keys]
80 80
81 81 def _args(self):
82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
82 args = self._req.qsparams.asdictoflists()
83 83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
84 84 if postlen:
85 85 args.update(urlreq.parseqs(
@@ -170,10 +170,10 b' def handlewsgirequest(rctx, wsgireq, req'
170 170 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
171 171 # string parameter. If it isn't present, this isn't a wire protocol
172 172 # request.
173 if 'cmd' not in req.querystringdict:
173 if 'cmd' not in req.qsparams:
174 174 return False
175 175
176 cmd = req.querystringdict['cmd'][0]
176 cmd = req.qsparams['cmd']
177 177
178 178 # The "cmd" request parameter is used by both the wire protocol and hgweb.
179 179 # While not all wire protocol commands are available for all transports,
General Comments 0
You need to be logged in to leave comments. Login now