1 """
2 The root resource of the data center.
3 """
4
5
6
7
8
9
10
11 from __future__ import print_function
12
13 import os
14 import re
15 import time
16
17 from twisted.python import threadable
18 threadable.init()
19
20 from nevow import appserver
21 from nevow import compression
22 from nevow import inevow
23 from nevow import rend
24 from nevow import tags as T
25 from nevow import static
26
27 from gavo import base
28 from gavo import svcs
29 from gavo import utils
30 from gavo.imp import formal
31 from gavo.web import caching
32 from gavo.web import common
33 from gavo.web import grend
34 from gavo.web import ifpages
35 from gavo.web import jsonquery
36 from gavo.web import metarender
37 from gavo.web import weberrors
38
39 from gavo.svcs import (UnknownURI, WebRedirect)
42 """helps formatDefaultLog.
43 """
44 if isinstance(s, unicode):
45 s = s.encode('ascii', 'ignore')
46 return "'%s'"%(s.replace('"', '\\"'))
47
71
86
87
88 -def makeDynamicPage(pageClass):
89 """returns a resource that returns a "dynamic" resource of pageClass.
90
91 pageClass must be a rend.Page subclass that is constructed with a
92 request context (like ifpages.LoginPage). We want such things
93 when the pages have some internal state (since you're not supposed
94 to keep such things in the context any more, which I personally agree
95 with).
96
97 The dynamic pages are directly constructed, their locateChild methods
98 are not called (do we want to change this)?
99 """
100 class DynPage(rend.Page):
101 def renderHTTP(self, ctx):
102 return pageClass(ctx)
103 return DynPage()
104
108 """returns a "small" version of the logo.
109
110 This is used mainly for SAMP logos at them moment.
111 """
112 from gavo.utils import imgtools
113 imgPath = os.path.join(
114 base.getConfig("webDir"), "nv_static", "logo_medium.png")
115 if not os.path.exists(imgPath):
116 imgPath = base.getPathForDistFile("web/img/logo_medium.png")
117 return static.Data(
118 imgtools.getScaledPNG(imgPath, 62),
119 type="image/png")
120
123 """adds cross-origin authorisation headers if appropriate.
124
125 This evaluates the [web]corsOrigins config item.
126 """
127 origin = request.getHeader("Origin")
128 if not origin:
129 return
130 pat = base.getConfig("web", "corsoriginpat")
131 if pat and re.match(pat, origin):
132 request.setHeader("Access-Control-Allow-Origin", origin)
133
134
135
136
137
138
139 base.caches.makeCache("getPageCache", lambda rdId: {})
143 """The root resource on the data center.
144
145 It does the main dispatching based on four mechanisms:
146
147 0. redirects -- one-segments fragments that redirect somewhere else.
148 This is for "bad" shortcuts corresponding to input directory name
149 exclusively (since it's so messy). These will not match if
150 path has more than one segment.
151 1. statics -- first segment leads to a resource that gets passed any
152 additional segments.
153 2. mappings -- first segment is replaced by something else, processing
154 continues.
155 3. resource based -- consisting of an RD id, a service id, a renderer and
156 possibly further segments.
157
158 The first three mechanisms only look at the first segment to determine
159 any action (except that redirect is skipped if len(segments)>1).
160
161 The statics and mappings are configured on the class level.
162 """
163 timestampStarted = time.time()
164 statics = {}
165 mappings = {}
166 redirects = {}
167
169 rend.Page.__init__(self)
170 self.maintFile = os.path.join(base.getConfig("stateDir"), "MAINT")
171 self.rootSegments = tuple(s for s in
172 base.getConfig("web", "nevowRoot").split("/") if s)
173 self.rootLen = len(self.rootSegments)
174
175 @classmethod
178
179 @classmethod
182
183 @classmethod
186
187 @classmethod
198
199 @classmethod
201 """builds the redirects prescribed by the system-wide vanity map.
202 """
203 for src, (dest, options) in svcs.getVanityMap().shortToLong.iteritems():
204 cls._addVanityRedirect(src, dest, options)
205
207
208
209
210
211 return self.locateChild(ctx, self.rootSegments)
212
214 """shortcuts if ctx's request can be cached with rendC.
215
216 This function returns a cached item if a page is in the cache and
217 request allows caching, None otherwise. For cacheable requests,
218 it instruments the request such that the page is actually cached.
219
220 Cacheable pages also cause request's lastModified to be set.
221
222 Requests with arguments or a user info are never cacheable.
223 """
224 request = inevow.IRequest(ctx)
225 if request.method!="GET" or request.args or request.getUser():
226 return None
227
228 if not rendC.isCacheable(segments, request):
229 return None
230
231 request.setLastModified(
232 max(self.timestampStarted, service.rd.timestampUpdated))
233
234 cache = base.caches.getPageCache(service.rd.sourceId)
235 segments = tuple(segments)
236 if segments in cache:
237 return compression.CompressingResourceWrapper(cache[segments])
238
239 caching.instrumentRequestForCaching(request,
240 caching.enterIntoCacheAs(segments, cache))
241 return None
242
244 """returns a standard, resource-based service renderer.
245
246 Their URIs look like <rd id>/<service id>{/<anything>}.
247
248 This works by successively trying to use parts of the query path
249 of increasing length as RD ids. If one matches, the next
250 segment is the service id, and the following one the renderer.
251
252 The remaining segments are returned unconsumed.
253
254 If no RD matches, an UnknwownURI exception is raised.
255 """
256 for srvInd in range(1, len(segments)):
257 try:
258 rd = base.caches.getRD("/".join(segments[:srvInd]))
259 except base.RDNotFound:
260 continue
261 else:
262 break
263 else:
264 raise UnknownURI("No matching RD")
265 try:
266 subId, rendName = segments[srvInd], segments[srvInd+1]
267 except IndexError:
268
269 subId, rendName = segments[srvInd], None
270
271 service = rd.getService(subId)
272 if service is None:
273 if rd.hasProperty("superseded-url"):
274 return weberrors.NotFoundPageWithFancyMessage([
275 "This resource is stale and has been superseded; you should really"
276 " not be directed here. Anyway, you might find what you"
277 " were looking for at this ",
278 T.a(href=rd.getProperty("superseded-url"))["new location"],
279 "."]), ()
280 raise UnknownURI("No such service: %s"%subId, rd=rd)
281
282 if not rendName:
283 rendName = service.defaultRenderer
284 if rendName is None:
285 raise UnknownURI("No renderer given and service has no default")
286 try:
287 rendC = svcs.getRenderer(rendName)
288 except Exception as exc:
289 exc.rd = rd
290 raise
291 cached = self._processCache(ctx, service, rendC, segments)
292 if cached:
293 return cached, ()
294 else:
295 return rendC(ctx, service), segments[srvInd+2:]
296
297
299 if False:
300 from gavo.helpers import testtricks
301 testtricks.memdebug()
302
303 request = inevow.IRequest(ctx)
304 if "x-forwarded-host" in request.received_headers:
305
306
307
308 request.setHost(request.getHeader("x-forwarded-host"), 80)
309 if "origin" in request.received_headers:
310 _authorizeCORS(request)
311
312 if segments[0]!='static' and os.path.exists(self.maintFile):
313 return ifpages.ServiceUnavailable(), ()
314 if self.rootSegments:
315 if segments[:self.rootLen]!=self.rootSegments:
316 raise UnknownURI("Misconfiguration: Saw a URL outside of the server's"
317 " scope")
318 segments = segments[self.rootLen:]
319
320 curPath = "/".join(segments)
321
322 if curPath.startswith ("/"):
323 segments = ("__system__",)+segments[1:]
324 curPath = "/".join(segments).strip("/")
325
326 curPath = curPath.strip("/")
327 if curPath=="":
328 segments = ("__system__", "services", "root", "fixed")
329 if curPath in self.redirects:
330 raise WebRedirect(self.redirects[curPath])
331
332 if segments[0] in self.statics:
333 return self.statics[segments[0]], segments[1:]
334
335 if segments[0] in self.mappings:
336 segments = self.mappings[segments[0]]+list(segments[1:])
337
338 try:
339 res = self._locateResourceBasedChild(ctx, segments)
340 return res
341 except grend.RDBlocked:
342 return static.File(svcs.getTemplatePath("blocked.html")), ()
343
344
345 ArchiveService.addStatic("login", makeDynamicPage(ifpages.LoginPage))
346 ArchiveService.addStatic("static", ifpages.StaticServer())
347 ArchiveService.addStatic("robots.txt", makeDynamicPage(ifpages.RobotsTxt))
348 ArchiveService.addStatic("clientcount", ifpages.CurReaders())
349
350
351 ArchiveService.addStatic("getRR", metarender.ResourceRecordMaker())
352
353 ArchiveService.addStatic('formal.css', formal.defaultCSS)
354 ArchiveService.addStatic('formal.js', formal.formsJS)
355
356
357 ArchiveService.addStatic(".well-known", ifpages.WellKnown())
358
359
360
361 ArchiveService.addStatic('_portaljs', jsonquery.PortalPage())
362
363 if base.getConfig("web", "enabletests"):
364 from gavo.web import webtests
365 ArchiveService.addStatic("test", webtests.Tests())
366
367 ArchiveService.addStatic("favicon.png",
368 makeFaviconPNG())
369 if (base.getConfig("web", "favicon")
370 and os.path.exists(base.getConfig("web", "favicon"))):
371 ArchiveService.addStatic("favicon.ico",
372 static.File(base.getConfig("web", "favicon")))
373
374 ArchiveService.installVanityMap()
375
376 root = ArchiveService()
377
378 site = appserver.NevowSite(root,
379 timeout=300,
380 logFormatter=getLogFormatter())
381 site.remember(weberrors.DCExceptionHandler())
382 site.requestFactory = common.Request
383