##// END OF EJS Templates
hgwebdir_mod: fix a performance issue with static files
Patrick Mezard -
r7560:305efd89 default
parent child Browse files
Show More
@@ -1,328 +1,328 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.i18n import _
11 11 from mercurial.repo import RepoError
12 12 from mercurial import ui, hg, util, templater, templatefilters
13 13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb
16 16 from request import wsgirequest
17 17
18 18 # This is a stopgap
19 19 class hgwebdir(object):
20 20 def __init__(self, config, parentui=None):
21 21 def cleannames(items):
22 22 return [(util.pconvert(name).strip('/'), path)
23 23 for name, path in items]
24 24
25 25 self.parentui = parentui or ui.ui(report_untrusted=False,
26 26 interactive = False)
27 27 self.motd = None
28 28 self.style = 'paper'
29 29 self.stripecount = None
30 30 self.repos_sorted = ('name', False)
31 31 self._baseurl = None
32 32 if isinstance(config, (list, tuple)):
33 33 self.repos = cleannames(config)
34 34 self.repos_sorted = ('', False)
35 35 elif isinstance(config, dict):
36 36 self.repos = util.sort(cleannames(config.items()))
37 37 else:
38 38 if isinstance(config, util.configparser):
39 39 cp = config
40 40 else:
41 41 cp = util.configparser()
42 42 cp.read(config)
43 43 self.repos = []
44 44 if cp.has_section('web'):
45 45 if cp.has_option('web', 'motd'):
46 46 self.motd = cp.get('web', 'motd')
47 47 if cp.has_option('web', 'style'):
48 48 self.style = cp.get('web', 'style')
49 49 if cp.has_option('web', 'stripes'):
50 50 self.stripecount = int(cp.get('web', 'stripes'))
51 51 if cp.has_option('web', 'baseurl'):
52 52 self._baseurl = cp.get('web', 'baseurl')
53 53 if cp.has_section('paths'):
54 54 paths = cleannames(cp.items('paths'))
55 55 for prefix, root in paths:
56 56 roothead, roottail = os.path.split(root)
57 57 # "foo = /bar/*" makes every subrepo of /bar/ to be
58 58 # mounted as foo/subrepo
59 59 # and "foo = /bar/**" does even recurse inside the
60 60 # subdirectories, remember to use it without working dir.
61 61 try:
62 62 recurse = {'*': False, '**': True}[roottail]
63 63 except KeyError:
64 64 self.repos.append((prefix, root))
65 65 continue
66 66 roothead = os.path.normpath(roothead)
67 67 for path in util.walkrepos(roothead, followsym=True,
68 68 recurse=recurse):
69 69 path = os.path.normpath(path)
70 70 name = util.pconvert(path[len(roothead):]).strip('/')
71 71 if prefix:
72 72 name = prefix + '/' + name
73 73 self.repos.append((name, path))
74 74 if cp.has_section('collections'):
75 75 for prefix, root in cp.items('collections'):
76 76 for path in util.walkrepos(root, followsym=True):
77 77 repo = os.path.normpath(path)
78 78 name = repo
79 79 if name.startswith(prefix):
80 80 name = name[len(prefix):]
81 81 self.repos.append((name.lstrip(os.sep), repo))
82 82 self.repos.sort()
83 83
84 84 def run(self):
85 85 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
86 86 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
87 87 import mercurial.hgweb.wsgicgi as wsgicgi
88 88 wsgicgi.launch(self)
89 89
90 90 def __call__(self, env, respond):
91 91 req = wsgirequest(env, respond)
92 92 return self.run_wsgi(req)
93 93
94 94 def read_allowed(self, ui, req):
95 95 """Check allow_read and deny_read config options of a repo's ui object
96 96 to determine user permissions. By default, with neither option set (or
97 97 both empty), allow all users to read the repo. There are two ways a
98 98 user can be denied read access: (1) deny_read is not empty, and the
99 99 user is unauthenticated or deny_read contains user (or *), and (2)
100 100 allow_read is not empty and the user is not in allow_read. Return True
101 101 if user is allowed to read the repo, else return False."""
102 102
103 103 user = req.env.get('REMOTE_USER')
104 104
105 105 deny_read = ui.configlist('web', 'deny_read', default=None, untrusted=True)
106 106 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
107 107 return False
108 108
109 109 allow_read = ui.configlist('web', 'allow_read', default=None, untrusted=True)
110 110 # by default, allow reading if no allow_read option has been set
111 111 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
112 112 return True
113 113
114 114 return False
115 115
116 116 def run_wsgi(self, req):
117 117
118 118 try:
119 119 try:
120 120
121 121 virtual = req.env.get("PATH_INFO", "").strip('/')
122 122 tmpl = self.templater(req)
123 123 ctype = tmpl('mimetype', encoding=util._encoding)
124 124 ctype = templater.stringify(ctype)
125 125
126 126 # a static file
127 127 if virtual.startswith('static/') or 'static' in req.form:
128 128 if virtual.startswith('static/'):
129 129 fname = virtual[7:]
130 130 else:
131 131 fname = req.form['static'][0]
132 132 static = templater.templatepath('static')
133 return staticfile(static, fname, req)
133 return (staticfile(static, fname, req),)
134 134
135 135 # top-level index
136 136 elif not virtual:
137 137 req.respond(HTTP_OK, ctype)
138 138 return self.makeindex(req, tmpl)
139 139
140 140 # nested indexes and hgwebs
141 141
142 142 repos = dict(self.repos)
143 143 while virtual:
144 144 real = repos.get(virtual)
145 145 if real:
146 146 req.env['REPO_NAME'] = virtual
147 147 try:
148 148 repo = hg.repository(self.parentui, real)
149 149 return hgweb(repo).run_wsgi(req)
150 150 except IOError, inst:
151 151 msg = inst.strerror
152 152 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
153 153 except RepoError, inst:
154 154 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
155 155
156 156 # browse subdirectories
157 157 subdir = virtual + '/'
158 158 if [r for r in repos if r.startswith(subdir)]:
159 159 req.respond(HTTP_OK, ctype)
160 160 return self.makeindex(req, tmpl, subdir)
161 161
162 162 up = virtual.rfind('/')
163 163 if up < 0:
164 164 break
165 165 virtual = virtual[:up]
166 166
167 167 # prefixes not found
168 168 req.respond(HTTP_NOT_FOUND, ctype)
169 169 return tmpl("notfound", repo=virtual)
170 170
171 171 except ErrorResponse, err:
172 172 req.respond(err.code, ctype)
173 173 return tmpl('error', error=err.message or '')
174 174 finally:
175 175 tmpl = None
176 176
177 177 def makeindex(self, req, tmpl, subdir=""):
178 178
179 179 def archivelist(ui, nodeid, url):
180 180 allowed = ui.configlist("web", "allow_archive", untrusted=True)
181 181 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
182 182 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
183 183 untrusted=True):
184 184 yield {"type" : i[0], "extension": i[1],
185 185 "node": nodeid, "url": url}
186 186
187 187 def entries(sortcolumn="", descending=False, subdir="", **map):
188 188 def sessionvars(**map):
189 189 fields = []
190 190 if 'style' in req.form:
191 191 style = req.form['style'][0]
192 192 if style != get('web', 'style', ''):
193 193 fields.append(('style', style))
194 194
195 195 separator = url[-1] == '?' and ';' or '?'
196 196 for name, value in fields:
197 197 yield dict(name=name, value=value, separator=separator)
198 198 separator = ';'
199 199
200 200 rows = []
201 201 parity = paritygen(self.stripecount)
202 202 for name, path in self.repos:
203 203 if not name.startswith(subdir):
204 204 continue
205 205 name = name[len(subdir):]
206 206
207 207 u = ui.ui(parentui=self.parentui)
208 208 try:
209 209 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
210 210 except Exception, e:
211 211 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
212 212 continue
213 213 def get(section, name, default=None):
214 214 return u.config(section, name, default, untrusted=True)
215 215
216 216 if u.configbool("web", "hidden", untrusted=True):
217 217 continue
218 218
219 219 if not self.read_allowed(u, req):
220 220 continue
221 221
222 222 parts = [name]
223 223 if 'PATH_INFO' in req.env:
224 224 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
225 225 if req.env['SCRIPT_NAME']:
226 226 parts.insert(0, req.env['SCRIPT_NAME'])
227 227 url = ('/'.join(parts).replace("//", "/")) + '/'
228 228
229 229 # update time with local timezone
230 230 try:
231 231 d = (get_mtime(path), util.makedate()[1])
232 232 except OSError:
233 233 continue
234 234
235 235 contact = get_contact(get)
236 236 description = get("web", "description", "")
237 237 name = get("web", "name", name)
238 238 row = dict(contact=contact or "unknown",
239 239 contact_sort=contact.upper() or "unknown",
240 240 name=name,
241 241 name_sort=name,
242 242 url=url,
243 243 description=description or "unknown",
244 244 description_sort=description.upper() or "unknown",
245 245 lastchange=d,
246 246 lastchange_sort=d[1]-d[0],
247 247 sessionvars=sessionvars,
248 248 archives=archivelist(u, "tip", url))
249 249 if (not sortcolumn
250 250 or (sortcolumn, descending) == self.repos_sorted):
251 251 # fast path for unsorted output
252 252 row['parity'] = parity.next()
253 253 yield row
254 254 else:
255 255 rows.append((row["%s_sort" % sortcolumn], row))
256 256 if rows:
257 257 rows.sort()
258 258 if descending:
259 259 rows.reverse()
260 260 for key, row in rows:
261 261 row['parity'] = parity.next()
262 262 yield row
263 263
264 264 sortable = ["name", "description", "contact", "lastchange"]
265 265 sortcolumn, descending = self.repos_sorted
266 266 if 'sort' in req.form:
267 267 sortcolumn = req.form['sort'][0]
268 268 descending = sortcolumn.startswith('-')
269 269 if descending:
270 270 sortcolumn = sortcolumn[1:]
271 271 if sortcolumn not in sortable:
272 272 sortcolumn = ""
273 273
274 274 sort = [("sort_%s" % column,
275 275 "%s%s" % ((not descending and column == sortcolumn)
276 276 and "-" or "", column))
277 277 for column in sortable]
278 278
279 279 if self._baseurl is not None:
280 280 req.env['SCRIPT_NAME'] = self._baseurl
281 281
282 282 return tmpl("index", entries=entries, subdir=subdir,
283 283 sortcolumn=sortcolumn, descending=descending,
284 284 **dict(sort))
285 285
286 286 def templater(self, req):
287 287
288 288 def header(**map):
289 289 yield tmpl('header', encoding=util._encoding, **map)
290 290
291 291 def footer(**map):
292 292 yield tmpl("footer", **map)
293 293
294 294 def motd(**map):
295 295 if self.motd is not None:
296 296 yield self.motd
297 297 else:
298 298 yield config('web', 'motd', '')
299 299
300 300 def config(section, name, default=None, untrusted=True):
301 301 return self.parentui.config(section, name, default, untrusted)
302 302
303 303 if self._baseurl is not None:
304 304 req.env['SCRIPT_NAME'] = self._baseurl
305 305
306 306 url = req.env.get('SCRIPT_NAME', '')
307 307 if not url.endswith('/'):
308 308 url += '/'
309 309
310 310 staticurl = config('web', 'staticurl') or url + 'static/'
311 311 if not staticurl.endswith('/'):
312 312 staticurl += '/'
313 313
314 314 style = self.style
315 315 if style is None:
316 316 style = config('web', 'style', '')
317 317 if 'style' in req.form:
318 318 style = req.form['style'][0]
319 319 if self.stripecount is None:
320 320 self.stripecount = int(config('web', 'stripes', 1))
321 321 mapfile = style_map(templater.templatepath(), style)
322 322 tmpl = templater.templater(mapfile, templatefilters.filters,
323 323 defaults={"header": header,
324 324 "footer": footer,
325 325 "motd": motd,
326 326 "url": url,
327 327 "staticurl": staticurl})
328 328 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now