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 from third_party.json_schema_compiler.memoize import memoize |
17 | 18 |
18 _DEFAULT_CHANNEL = 'stable' | 19 _DEFAULT_CHANNEL = 'stable' |
19 | 20 |
20 _ALWAYS_ONLINE = IsDevServer() | 21 _ALWAYS_ONLINE = IsDevServer() |
21 | 22 |
22 def _IsBinaryMimetype(mimetype): | 23 def _IsBinaryMimetype(mimetype): |
23 return any(mimetype.startswith(prefix) | 24 return any(mimetype.startswith(prefix) |
24 for prefix in ['audio', 'image', 'video']) | 25 for prefix in ['audio', 'image', 'video']) |
25 | 26 |
26 def AlwaysOnline(fn): | 27 @memoize |
27 '''A function decorator which forces the rendering to be always online rather | 28 def _GetOrCreateServerInstance(channel): |
28 than the default offline behaviour. Useful for testing. | 29 return ServerInstance.CreateOffline(channel) |
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 | 30 |
40 class RenderServlet(Servlet): | 31 class RenderServlet(Servlet): |
41 '''Servlet which renders templates. | 32 '''Servlet which renders templates. |
42 ''' | 33 ''' |
43 def Get(self, server_instance=None): | 34 def Get(self, server_instance=None): |
| 35 # A note about |server_instance|: |
| 36 # |
| 37 # AppEngine instances should never need to call out to SVN. That should |
| 38 # only ever be done by the cronjobs, which then write the result into |
| 39 # DataStore, which is as far as instances look. To enable this, crons can |
| 40 # pass a custom (presumably online) ServerInstance into Get(). |
| 41 # |
| 42 # Why? SVN is slow and a bit flaky. Cronjobs failing is annoying but |
| 43 # temporary. Instances failing affects users, and is really bad. |
| 44 # |
| 45 # Anyway - to enforce this, we actually don't give instances access to SVN. |
| 46 # If anything is missing from datastore, it'll be a 404. If the cronjobs |
| 47 # don't manage to catch everything - uhoh. On the other hand, we'll figure |
| 48 # it out pretty soon, and it also means that legitimate 404s are caught |
| 49 # before a round trip to SVN. |
| 50 |
44 path_with_channel, headers = (self._request.path.lstrip('/'), | 51 path_with_channel, headers = (self._request.path.lstrip('/'), |
45 self._request.headers) | 52 self._request.headers) |
46 | 53 |
47 # Redirect "extensions" and "extensions/" to "extensions/index.html", etc. | 54 # Redirect "extensions" and "extensions/" to "extensions/index.html", etc. |
48 if (os.path.splitext(path_with_channel)[1] == '' and | 55 if (os.path.splitext(path_with_channel)[1] == '' and |
49 path_with_channel.find('/') == -1): | 56 path_with_channel.find('/') == -1): |
50 path_with_channel += '/' | 57 path_with_channel += '/' |
51 if path_with_channel.endswith('/'): | 58 if path_with_channel.endswith('/'): |
52 return Response.Redirect(path_with_channel + 'index.html') | 59 return Response.Redirect(path_with_channel + 'index.html') |
53 | 60 |
54 channel, path = BranchUtility.SplitChannelNameFromPath(path_with_channel) | 61 channel, path = BranchUtility.SplitChannelNameFromPath(path_with_channel) |
55 | 62 |
56 if channel == _DEFAULT_CHANNEL: | 63 if channel == _DEFAULT_CHANNEL: |
57 return Response.Redirect('/%s' % path) | 64 return Response.Redirect('/%s' % path) |
58 | 65 |
59 if channel is None: | 66 if channel is None: |
60 channel = _DEFAULT_CHANNEL | 67 channel = _DEFAULT_CHANNEL |
61 | 68 |
62 # AppEngine instances should never need to call out to SVN. That should | |
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: | 69 if server_instance is None: |
76 # The ALWAYS_ONLINE thing is for tests and preview.py that shouldn't need | 70 server_instance = _GetOrCreateServerInstance(channel) |
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 | 71 |
82 canonical_path = server_instance.path_canonicalizer.Canonicalize(path) | 72 canonical_path = server_instance.path_canonicalizer.Canonicalize(path) |
83 if path != canonical_path: | 73 if path != canonical_path: |
84 return Response.Redirect(canonical_path if channel is None else | 74 return Response.Redirect(canonical_path if channel is None else |
85 '%s/%s' % (channel, canonical_path)) | 75 '%s/%s' % (channel, canonical_path)) |
86 | 76 |
87 templates = server_instance.template_data_source_factory.Create( | 77 templates = server_instance.template_data_source_factory.Create( |
88 self._request, path) | 78 self._request, path) |
89 | 79 |
90 content = None | 80 content = None |
(...skipping 28 matching lines...) Expand all Loading... |
119 return Response.NotFound(templates.Render('404'), headers=headers) | 109 return Response.NotFound(templates.Render('404'), headers=headers) |
120 | 110 |
121 if not content: | 111 if not content: |
122 logging.error('%s had empty content' % path) | 112 logging.error('%s had empty content' % path) |
123 | 113 |
124 headers.update({ | 114 headers.update({ |
125 'content-type': content_type, | 115 'content-type': content_type, |
126 'cache-control': 'max-age=300', | 116 'cache-control': 'max-age=300', |
127 }) | 117 }) |
128 return Response.Ok(content, headers=headers) | 118 return Response.Ok(content, headers=headers) |
OLD | NEW |