Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 from fnmatch import fnmatch | 5 from fnmatch import fnmatch |
| 6 import logging | 6 import logging |
| 7 import mimetypes | 7 import mimetypes |
| 8 import os | 8 import os |
| 9 import traceback | 9 import traceback |
| 10 | 10 |
| 11 from appengine_wrappers import IsDevServer | 11 from appengine_wrappers import IsDevServer |
| 12 from branch_utility import BranchUtility | 12 from branch_utility import BranchUtility |
| 13 from file_system import FileNotFoundError | 13 from file_system import FileNotFoundError |
| 14 from server_instance import ServerInstance | 14 from server_instance import ServerInstance |
| 15 from servlet import Servlet, Response | 15 from servlet import Servlet, Response |
| 16 import svn_constants | 16 import svn_constants |
| 17 | 17 |
| 18 _DEFAULT_CHANNEL = 'stable' | |
| 19 | |
| 20 _ALWAYS_ONLINE = IsDevServer() | |
| 21 | |
| 22 def _IsBinaryMimetype(mimetype): | 18 def _IsBinaryMimetype(mimetype): |
| 23 return any(mimetype.startswith(prefix) | 19 return any(mimetype.startswith(prefix) |
| 24 for prefix in ['audio', 'image', 'video']) | 20 for prefix in ['audio', 'image', 'video']) |
| 25 | 21 |
| 26 def AlwaysOnline(fn): | |
| 27 '''A function decorator which forces the rendering to be always online rather | |
| 28 than the default offline behaviour. Useful for testing. | |
| 29 ''' | |
| 30 def impl(*args, **optargs): | |
| 31 global _ALWAYS_ONLINE | |
| 32 was_always_online = _ALWAYS_ONLINE | |
| 33 try: | |
| 34 _ALWAYS_ONLINE = True | |
| 35 return fn(*args, **optargs) | |
| 36 finally: | |
| 37 _ALWAYS_ONLINE = was_always_online | |
| 38 return impl | |
| 39 | |
| 40 class RenderServlet(Servlet): | 22 class RenderServlet(Servlet): |
| 41 '''Servlet which renders templates. | 23 '''Servlet which renders templates. |
| 42 ''' | 24 ''' |
| 43 def Get(self, server_instance=None): | 25 class Delegate(object): |
| 44 path_with_channel, headers = (self._request.path.lstrip('/'), | 26 def CreateServerInstanceForChannel(self, channel): |
| 45 self._request.headers) | 27 raise NotImplementedError() |
| 28 | |
| 29 def __init__(self, request, delegate, default_channel='stable'): | |
| 30 Servlet.__init__(self, request) | |
| 31 self._delegate = delegate | |
| 32 self._default_channel = default_channel | |
| 33 | |
| 34 def Get(self): | |
| 35 path_with_channel, headers = (self._request.path, self._request.headers) | |
| 46 | 36 |
| 47 # Redirect "extensions" and "extensions/" to "extensions/index.html", etc. | 37 # Redirect "extensions" and "extensions/" to "extensions/index.html", etc. |
| 48 if (os.path.splitext(path_with_channel)[1] == '' and | 38 if (os.path.splitext(path_with_channel)[1] == '' and |
| 49 path_with_channel.find('/') == -1): | 39 path_with_channel.find('/') == -1): |
| 50 path_with_channel += '/' | 40 path_with_channel += '/' |
| 51 if path_with_channel.endswith('/'): | 41 if path_with_channel.endswith('/'): |
| 52 return Response.Redirect(path_with_channel + 'index.html') | 42 return Response.Redirect('/%sindex.html' % path_with_channel) |
| 53 | 43 |
| 54 channel, path = BranchUtility.SplitChannelNameFromPath(path_with_channel) | 44 channel, path = BranchUtility.SplitChannelNameFromPath(path_with_channel) |
| 55 | 45 |
| 56 if channel == _DEFAULT_CHANNEL: | 46 if channel == self._default_channel: |
| 57 return Response.Redirect('/%s' % path) | 47 return Response.Redirect('/%s' % path) |
| 58 | 48 |
| 59 if channel is None: | 49 if channel is None: |
| 60 channel = _DEFAULT_CHANNEL | 50 channel = self._default_channel |
| 61 | 51 |
| 62 # AppEngine instances should never need to call out to SVN. That should | 52 server_instance = self._delegate.CreateServerInstanceForChannel(channel) |
| 63 # only ever be done by the cronjobs, which then write the result into | |
| 64 # DataStore, which is as far as instances look. To enable this, crons can | |
| 65 # pass a custom (presumably online) ServerInstance into Get(). | |
| 66 # | |
| 67 # Why? SVN is slow and a bit flaky. Cronjobs failing is annoying but | |
| 68 # temporary. Instances failing affects users, and is really bad. | |
| 69 # | |
| 70 # Anyway - to enforce this, we actually don't give instances access to SVN. | |
| 71 # If anything is missing from datastore, it'll be a 404. If the cronjobs | |
| 72 # don't manage to catch everything - uhoh. On the other hand, we'll figure | |
| 73 # it out pretty soon, and it also means that legitimate 404s are caught | |
| 74 # before a round trip to SVN. | |
| 75 if server_instance is None: | |
| 76 # The ALWAYS_ONLINE thing is for tests and preview.py that shouldn't need | |
| 77 # to run the cron before rendering things. | |
| 78 constructor = (ServerInstance.CreateOnline if _ALWAYS_ONLINE else | |
| 79 ServerInstance.GetOrCreateOffline) | |
| 80 server_instance = constructor(channel) | |
| 81 | 53 |
| 82 canonical_path = server_instance.path_canonicalizer.Canonicalize(path) | 54 canonical_path = ( |
| 55 server_instance.path_canonicalizer.Canonicalize(path).lstrip('/')) | |
| 83 if path != canonical_path: | 56 if path != canonical_path: |
| 84 return Response.Redirect(canonical_path if channel is None else | 57 return Response.Redirect('/' + ( |
|
cduvall
2013/05/08 18:32:57
Use '/%s' % instead?
not at google - send to devlin
2013/05/08 18:54:16
Done.
| |
| 85 '%s/%s' % (channel, canonical_path)) | 58 canonical_path if channel is None else |
| 59 '%s/%s' % (channel, canonical_path))) | |
| 86 | 60 |
| 87 templates = server_instance.template_data_source_factory.Create( | 61 templates = server_instance.template_data_source_factory.Create( |
| 88 self._request, path) | 62 self._request, path) |
| 89 | 63 |
| 90 content = None | 64 content = None |
| 91 content_type = None | 65 content_type = None |
| 92 | 66 |
| 93 try: | 67 try: |
| 94 if fnmatch(path, 'extensions/examples/*.zip'): | 68 if fnmatch(path, 'extensions/examples/*.zip'): |
| 95 content = server_instance.example_zipper.Create( | 69 content = server_instance.example_zipper.Create( |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 119 return Response.NotFound(templates.Render('404'), headers=headers) | 93 return Response.NotFound(templates.Render('404'), headers=headers) |
| 120 | 94 |
| 121 if not content: | 95 if not content: |
| 122 logging.error('%s had empty content' % path) | 96 logging.error('%s had empty content' % path) |
| 123 | 97 |
| 124 headers.update({ | 98 headers.update({ |
| 125 'content-type': content_type, | 99 'content-type': content_type, |
| 126 'cache-control': 'max-age=300', | 100 'cache-control': 'max-age=300', |
| 127 }) | 101 }) |
| 128 return Response.Ok(content, headers=headers) | 102 return Response.Ok(content, headers=headers) |
| OLD | NEW |