1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import posixpath 7import traceback 8 9from environment import IsPreviewServer 10from file_system import FileNotFoundError 11from redirector import Redirector 12from servlet import Servlet, Response 13from third_party.handlebar import Handlebar 14 15 16def _MakeHeaders(content_type): 17 return { 18 'X-Frame-Options': 'sameorigin', 19 'Content-Type': content_type, 20 'Cache-Control': 'max-age=300', 21 } 22 23 24class RenderServlet(Servlet): 25 '''Servlet which renders templates. 26 ''' 27 28 class Delegate(object): 29 def CreateServerInstance(self): 30 raise NotImplementedError(self.__class__) 31 32 def __init__(self, request, delegate): 33 Servlet.__init__(self, request) 34 self._delegate = delegate 35 36 def Get(self): 37 ''' Render the page for a request. 38 ''' 39 # TODO(kalman): a consistent path syntax (even a Path class?) so that we 40 # can stop being so conservative with stripping and adding back the '/'s. 41 path = self._request.path.lstrip('/') 42 server_instance = self._delegate.CreateServerInstance() 43 44 try: 45 return self._GetSuccessResponse(path, server_instance) 46 except FileNotFoundError: 47 if IsPreviewServer(): 48 logging.error(traceback.format_exc()) 49 # Maybe it didn't find the file because its canonical location is 50 # somewhere else; this is distinct from "redirects", which are typically 51 # explicit. This is implicit. 52 canonical_result = server_instance.path_canonicalizer.Canonicalize(path) 53 redirect = canonical_result.path.lstrip('/') 54 if path != redirect: 55 return Response.Redirect('/' + redirect, 56 permanent=canonical_result.permanent) 57 58 # Not found for reals. Find the closest 404.html file and serve that; 59 # e.g. if the path is extensions/manifest/typo.html then first look for 60 # extensions/manifest/404.html, then extensions/404.html, then 404.html. 61 # 62 # Failing that just print 'Not Found' but that should preferrably never 63 # happen, because it would look really bad. 64 path_components = path.split('/') 65 for i in xrange(len(path_components) - 1, -1, -1): 66 try: 67 path_404 = posixpath.join(*(path_components[0:i] + ['404.html'])) 68 response = self._GetSuccessResponse(path_404, server_instance) 69 return Response.NotFound(response.content.ToString(), 70 headers=response.headers) 71 except FileNotFoundError: continue 72 logging.warning('No 404.html found in %s' % path) 73 return Response.NotFound('Not Found', headers=_MakeHeaders('text/plain')) 74 75 def _GetSuccessResponse(self, path, server_instance): 76 '''Returns the Response from trying to render |path| with 77 |server_instance|. If |path| isn't found then a FileNotFoundError will be 78 raised, such that the only responses that will be returned from this method 79 are Ok and Redirect. 80 ''' 81 content_provider, path = ( 82 server_instance.content_providers.GetByServeFrom(path)) 83 assert content_provider, 'No ContentProvider found for %s' % path 84 85 redirect = Redirector( 86 server_instance.compiled_fs_factory, 87 content_provider.file_system).Redirect(self._request.host, path) 88 if redirect is not None: 89 return Response.Redirect(redirect, permanent=False) 90 91 content_and_type = content_provider.GetContentAndType(path).Get() 92 if not content_and_type.content: 93 logging.error('%s had empty content' % path) 94 95 content = content_and_type.content 96 if isinstance(content, Handlebar): 97 template_content, template_warnings = ( 98 server_instance.template_renderer.Render(content, self._request)) 99 # HACK: the Google ID thing (google2ed...) doesn't have a title. 100 content, doc_warnings = server_instance.document_renderer.Render( 101 template_content, 102 render_title=path != 'google2ed1af765c529f57.html') 103 warnings = template_warnings + doc_warnings 104 if warnings: 105 sep = '\n - ' 106 logging.warning('Rendering %s:%s%s' % (path, sep, sep.join(warnings))) 107 108 content_type = content_and_type.content_type 109 if isinstance(content, unicode): 110 content = content.encode('utf-8') 111 content_type += '; charset=utf-8' 112 113 return Response.Ok(content, headers=_MakeHeaders(content_type)) 114