OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 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 import logging | 5 import logging |
6 import os | 6 import os |
7 from StringIO import StringIO | 7 from StringIO import StringIO |
8 | 8 |
9 from appengine_wrappers import webapp | 9 from appengine_wrappers import webapp |
10 from appengine_wrappers import memcache | 10 from appengine_wrappers import memcache |
11 from appengine_wrappers import urlfetch | 11 from appengine_wrappers import urlfetch |
12 from branch_utility import BranchUtility | 12 from branch_utility import BranchUtility |
13 from server_instance import ServerInstance | 13 from server_instance import ServerInstance |
14 import svn_constants | 14 import svn_constants |
15 import time | 15 import time |
16 | 16 |
17 # The default channel to serve docs for if no channel is specified. | 17 # The default channel to serve docs for if no channel is specified. |
18 _DEFAULT_CHANNEL = 'stable' | 18 _DEFAULT_CHANNEL = 'stable' |
19 | 19 |
20 class Handler(webapp.RequestHandler): | 20 class Handler(webapp.RequestHandler): |
| 21 # AppEngine instances should never need to call out to SVN. That should only |
| 22 # ever be done by the cronjobs, which then write the result into DataStore, |
| 23 # which is as far as instances look. |
| 24 # |
| 25 # Why? SVN is slow and a bit flaky. Cronjobs failing is annoying but |
| 26 # temporary. Instances failing affects users, and is really bad. |
| 27 # |
| 28 # Anyway - to enforce this, we actually don't give instances access to SVN. |
| 29 # If anything is missing from datastore, it'll be a 404. If the cronjobs |
| 30 # don't manage to catch everything - uhoh. On the other hand, we'll figure it |
| 31 # out pretty soon, and it also means that legitimate 404s are caught before a |
| 32 # round trip to SVN. |
| 33 # |
| 34 # However, we can't expect users of preview.py to run a cronjob first, so, |
| 35 # this is a hack allow that to be online all of the time. |
| 36 # TODO(kalman): achieve this via proper dependency injection. |
| 37 ALWAYS_ONLINE = False |
| 38 |
21 def __init__(self, request, response): | 39 def __init__(self, request, response): |
22 super(Handler, self).__init__(request, response) | 40 super(Handler, self).__init__(request, response) |
23 | 41 |
24 def _HandleGet(self, path): | 42 def _HandleGet(self, path): |
25 channel_name, real_path = BranchUtility.SplitChannelNameFromPath(path) | 43 channel_name, real_path = BranchUtility.SplitChannelNameFromPath(path) |
26 | 44 |
27 if channel_name == _DEFAULT_CHANNEL: | 45 if channel_name == _DEFAULT_CHANNEL: |
28 self.redirect('/%s' % real_path) | 46 self.redirect('/%s' % real_path) |
29 return | 47 return |
30 | 48 |
31 if channel_name is None: | 49 if channel_name is None: |
32 channel_name = _DEFAULT_CHANNEL | 50 channel_name = _DEFAULT_CHANNEL |
33 | 51 |
34 # TODO(kalman): Check if |path| is a directory and serve path/index.html | 52 # TODO(kalman): Check if |path| is a directory and serve path/index.html |
35 # rather than special-casing apps/extensions. | 53 # rather than special-casing apps/extensions. |
36 if real_path.strip('/') == 'apps': | 54 if real_path.strip('/') == 'apps': |
37 real_path = 'apps/index.html' | 55 real_path = 'apps/index.html' |
38 if real_path.strip('/') == 'extensions': | 56 if real_path.strip('/') == 'extensions': |
39 real_path = 'extensions/index.html' | 57 real_path = 'extensions/index.html' |
40 | 58 |
41 server_instance = ServerInstance.GetOrCreate(channel_name) | 59 constructor = ( |
| 60 ServerInstance.GetOrCreateOnline if Handler.ALWAYS_ONLINE else |
| 61 ServerInstance.GetOrCreateOffline) |
| 62 server_instance = constructor(channel_name) |
42 | 63 |
43 canonical_path = server_instance.path_canonicalizer.Canonicalize(real_path) | 64 canonical_path = server_instance.path_canonicalizer.Canonicalize(real_path) |
44 if real_path != canonical_path: | 65 if real_path != canonical_path: |
45 self.redirect(canonical_path) | 66 self.redirect(canonical_path) |
46 return | 67 return |
47 | 68 |
48 ServerInstance.GetOrCreate(channel_name).Get(real_path, | 69 server_instance.Get(real_path, self.request, self.response) |
49 self.request, | |
50 self.response) | |
51 | 70 |
52 def _HandleCron(self, path): | 71 def _HandleCron(self, path): |
53 # Cron strategy: | 72 # Cron strategy: |
54 # | 73 # |
55 # Find all public template files and static files, and render them. Most of | 74 # Find all public template files and static files, and render them. Most of |
56 # the time these won't have changed since the last cron run, so it's a | 75 # the time these won't have changed since the last cron run, so it's a |
57 # little wasteful, but hopefully rendering is really fast (if it isn't we | 76 # little wasteful, but hopefully rendering is really fast (if it isn't we |
58 # have a problem). | 77 # have a problem). |
59 class MockResponse(object): | 78 class MockResponse(object): |
60 def __init__(self): | 79 def __init__(self): |
61 self.status = 200 | 80 self.status = 200 |
62 self.out = StringIO() | 81 self.out = StringIO() |
63 self.headers = {} | 82 self.headers = {} |
64 def set_status(self, status): | 83 def set_status(self, status): |
65 self.status = status | 84 self.status = status |
66 def clear(self, *args): | 85 def clear(self, *args): |
67 pass | 86 pass |
68 | 87 |
69 class MockRequest(object): | 88 class MockRequest(object): |
70 def __init__(self, path): | 89 def __init__(self, path): |
71 self.headers = {} | 90 self.headers = {} |
72 self.path = path | 91 self.path = path |
73 self.url = '//localhost/%s' % path | 92 self.url = '//localhost/%s' % path |
74 | 93 |
75 channel = path.split('/')[-1] | 94 channel = path.split('/')[-1] |
76 logging.info('cron/%s: starting' % channel) | 95 logging.info('cron/%s: starting' % channel) |
77 | 96 |
78 server_instance = ServerInstance.GetOrCreate(channel) | 97 server_instance = ServerInstance.GetOrCreateOnline(channel) |
79 | 98 |
80 def run_cron_for_dir(d): | 99 def run_cron_for_dir(d, path_prefix=''): |
81 error = None | 100 error = None |
82 start_time = time.time() | 101 start_time = time.time() |
83 files = [f for f in server_instance.content_cache.GetFromFileListing(d) | 102 files = [f for f in server_instance.content_cache.GetFromFileListing(d) |
84 if not f.endswith('/')] | 103 if not f.endswith('/')] |
85 for f in files: | 104 for f in files: |
| 105 path = '%s%s' % (path_prefix, f) |
86 try: | 106 try: |
87 server_instance.Get(f, MockRequest(f), MockResponse()) | 107 response = MockResponse() |
| 108 server_instance.Get(path, MockRequest(path), response) |
| 109 if response.status != 200: |
| 110 error = 'Got %s response' % response.status |
88 except error: | 111 except error: |
89 logging.error('cron/%s: error rendering %s/%s: %s' % ( | 112 pass |
90 channel, d, f, error)) | 113 if error: |
91 logging.info('cron/%s: rendering %s files in %s took %s seconds' % ( | 114 logging.error('cron/%s: error rendering %s: %s' % ( |
92 channel, len(files), d, time.time() - start_time)) | 115 channel, path, error)) |
| 116 logging.info('cron/%s: rendering %s files took %s seconds' % ( |
| 117 channel, len(files), time.time() - start_time)) |
93 return error | 118 return error |
94 | 119 |
95 # Don't use "or" since we want to evaluate everything no matter what. | 120 # Don't use "or" since we want to evaluate everything no matter what. |
96 was_error = any((run_cron_for_dir(svn_constants.PUBLIC_TEMPLATE_PATH), | 121 was_error = any(( |
97 run_cron_for_dir(svn_constants.STATIC_PATH))) | 122 run_cron_for_dir(svn_constants.PUBLIC_TEMPLATE_PATH), |
| 123 run_cron_for_dir(svn_constants.STATIC_PATH, path_prefix='static/'))) |
98 | 124 |
99 if was_error: | 125 if was_error: |
100 self.response.status = 500 | 126 self.response.status = 500 |
101 self.response.out.write('Failure') | 127 self.response.out.write('Failure') |
102 else: | 128 else: |
103 self.response.status = 200 | 129 self.response.status = 200 |
104 self.response.out.write('Success') | 130 self.response.out.write('Success') |
105 | 131 |
106 logging.info('cron/%s: finished' % channel) | 132 logging.info('cron/%s: finished' % channel) |
107 | 133 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
153 # file paths will know to treat this as a directory. | 179 # file paths will know to treat this as a directory. |
154 if os.path.splitext(path)[1] == '' and path[-1] != '/': | 180 if os.path.splitext(path)[1] == '' and path[-1] != '/': |
155 self.redirect(path + '/') | 181 self.redirect(path + '/') |
156 return | 182 return |
157 | 183 |
158 path = path.strip('/') | 184 path = path.strip('/') |
159 if self._RedirectFromCodeDotGoogleDotCom(path): | 185 if self._RedirectFromCodeDotGoogleDotCom(path): |
160 return | 186 return |
161 | 187 |
162 self._HandleGet(path) | 188 self._HandleGet(path) |
OLD | NEW |