##// 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 util,
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 @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.
@@ -56,10 +140,8 b' class parsedrequest(object):'
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()
@@ -157,14 +239,9 b' def parserequestfromenv(env, bodyfh):'
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
@@ -197,8 +274,7 b' def parserequestfromenv(env, bodyfh):'
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
@@ -350,7 +426,7 b' class wsgirequest(object):'
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
@@ -79,7 +79,7 b' class httpv1protocolhandler(wireprototyp'
79 return [data[k] for k in keys]
79 return [data[k] for k in keys]
80
80
81 def _args(self):
81 def _args(self):
82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
82 args = self._req.qsparams.asdictoflists()
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
84 if postlen:
84 if postlen:
85 args.update(urlreq.parseqs(
85 args.update(urlreq.parseqs(
@@ -170,10 +170,10 b' def handlewsgirequest(rctx, wsgireq, req'
170 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
170 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
171 # string parameter. If it isn't present, this isn't a wire protocol
171 # string parameter. If it isn't present, this isn't a wire protocol
172 # request.
172 # request.
173 if 'cmd' not in req.querystringdict:
173 if 'cmd' not in req.qsparams:
174 return False
174 return False
175
175
176 cmd = req.querystringdict['cmd'][0]
176 cmd = req.qsparams['cmd']
177
177
178 # The "cmd" request parameter is used by both the wire protocol and hgweb.
178 # The "cmd" request parameter is used by both the wire protocol and hgweb.
179 # While not all wire protocol commands are available for all transports,
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