| Index: chrome/common/extensions/docs/server2/handler.py
|
| diff --git a/chrome/common/extensions/docs/server2/handler.py b/chrome/common/extensions/docs/server2/handler.py
|
| index 8fbad1695bba8a2cc354c114712f9b798e414b10..24ceb226476365efbdfa0231d691520d12ead58b 100644
|
| --- a/chrome/common/extensions/docs/server2/handler.py
|
| +++ b/chrome/common/extensions/docs/server2/handler.py
|
| @@ -5,302 +5,105 @@
|
| import logging
|
| import os
|
| from StringIO import StringIO
|
| -import sys
|
|
|
| from appengine_wrappers import webapp
|
| from appengine_wrappers import memcache
|
| from appengine_wrappers import urlfetch
|
| -
|
| -from api_data_source import APIDataSource
|
| -from api_list_data_source import APIListDataSource
|
| -from appengine_blobstore import AppEngineBlobstore
|
| -from in_memory_object_store import InMemoryObjectStore
|
| -from appengine_url_fetcher import AppEngineUrlFetcher
|
| from branch_utility import BranchUtility
|
| -from example_zipper import ExampleZipper
|
| -from compiled_file_system import CompiledFileSystem
|
| -import compiled_file_system as compiled_fs
|
| -from github_file_system import GithubFileSystem
|
| -from intro_data_source import IntroDataSource
|
| -from known_issues_data_source import KnownIssuesDataSource
|
| -from local_file_system import LocalFileSystem
|
| -from memcache_file_system import MemcacheFileSystem
|
| -from reference_resolver import ReferenceResolver
|
| -from samples_data_source import SamplesDataSource
|
| from server_instance import ServerInstance
|
| -from sidenav_data_source import SidenavDataSource
|
| -from subversion_file_system import SubversionFileSystem
|
| -from template_data_source import TemplateDataSource
|
| -from third_party.json_schema_compiler.model import UnixName
|
| -import url_constants
|
| -
|
| -# Increment this version to force the server to reload all pages in the first
|
| -# cron job that is run.
|
| -_VERSION = 1
|
| +import svn_constants
|
| +import time
|
|
|
| # The default channel to serve docs for if no channel is specified.
|
| _DEFAULT_CHANNEL = 'stable'
|
|
|
| -BRANCH_UTILITY_MEMCACHE = InMemoryObjectStore('branch_utility')
|
| -BRANCH_UTILITY = BranchUtility(url_constants.OMAHA_PROXY_URL,
|
| - AppEngineUrlFetcher(None),
|
| - BRANCH_UTILITY_MEMCACHE)
|
| -
|
| -GITHUB_MEMCACHE = InMemoryObjectStore('github')
|
| -GITHUB_FILE_SYSTEM = GithubFileSystem(
|
| - AppEngineUrlFetcher(url_constants.GITHUB_URL),
|
| - GITHUB_MEMCACHE,
|
| - AppEngineBlobstore())
|
| -GITHUB_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(GITHUB_FILE_SYSTEM,
|
| - GITHUB_MEMCACHE)
|
| -
|
| -EXTENSIONS_PATH = 'chrome/common/extensions'
|
| -DOCS_PATH = 'docs'
|
| -API_PATH = 'api'
|
| -TEMPLATE_PATH = DOCS_PATH + '/templates'
|
| -INTRO_PATH = TEMPLATE_PATH + '/intros'
|
| -ARTICLE_PATH = TEMPLATE_PATH + '/articles'
|
| -PUBLIC_TEMPLATE_PATH = TEMPLATE_PATH + '/public'
|
| -PRIVATE_TEMPLATE_PATH = TEMPLATE_PATH + '/private'
|
| -EXAMPLES_PATH = DOCS_PATH + '/examples'
|
| -JSON_PATH = TEMPLATE_PATH + '/json'
|
| -
|
| -# Global cache of instances because Handler is recreated for every request.
|
| -SERVER_INSTANCES = {}
|
| -
|
| -def _GetURLFromBranch(branch):
|
| - if branch == 'trunk':
|
| - return url_constants.SVN_TRUNK_URL + '/src'
|
| - return url_constants.SVN_BRANCH_URL + '/' + branch + '/src'
|
| -
|
| -def _SplitFilenameUnix(base_dir, files):
|
| - return [UnixName(os.path.splitext(f.split('/')[-1])[0]) for f in files]
|
| -
|
| -def _CreateMemcacheFileSystem(branch, branch_memcache):
|
| - svn_url = _GetURLFromBranch(branch) + '/' + EXTENSIONS_PATH
|
| - stat_fetcher = AppEngineUrlFetcher(
|
| - svn_url.replace(url_constants.SVN_URL, url_constants.VIEWVC_URL))
|
| - fetcher = AppEngineUrlFetcher(svn_url)
|
| - return MemcacheFileSystem(SubversionFileSystem(fetcher, stat_fetcher),
|
| - branch_memcache)
|
| -
|
| -_default_branch = BRANCH_UTILITY.GetBranchNumberForChannelName(_DEFAULT_CHANNEL)
|
| -APPS_MEMCACHE = InMemoryObjectStore(_default_branch)
|
| -APPS_FILE_SYSTEM = _CreateMemcacheFileSystem(_default_branch, APPS_MEMCACHE)
|
| -APPS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(
|
| - APPS_FILE_SYSTEM,
|
| - APPS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.APPS_FS)
|
| -
|
| -EXTENSIONS_MEMCACHE = InMemoryObjectStore(_default_branch)
|
| -EXTENSIONS_FILE_SYSTEM = _CreateMemcacheFileSystem(_default_branch,
|
| - EXTENSIONS_MEMCACHE)
|
| -EXTENSIONS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(
|
| - EXTENSIONS_FILE_SYSTEM,
|
| - EXTENSIONS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.EXTENSIONS_FS)
|
| -
|
| -KNOWN_ISSUES_DATA_SOURCE = KnownIssuesDataSource(
|
| - InMemoryObjectStore('KnownIssues'),
|
| - AppEngineUrlFetcher(None))
|
| -
|
| -def _MakeInstanceKey(branch, number):
|
| - return '%s/%s' % (branch, number)
|
| -
|
| -def _GetInstanceForBranch(channel_name, local_path):
|
| - branch = BRANCH_UTILITY.GetBranchNumberForChannelName(channel_name)
|
| -
|
| - # The key for the server is a tuple of |channel_name| with |branch|, since
|
| - # sometimes stable and beta point to the same branch.
|
| - instance_key = _MakeInstanceKey(channel_name, branch)
|
| - instance = SERVER_INSTANCES.get(instance_key, None)
|
| - if instance is not None:
|
| - return instance
|
| -
|
| - branch_memcache = InMemoryObjectStore(branch)
|
| - file_system = _CreateMemcacheFileSystem(branch, branch_memcache)
|
| - cache_factory = CompiledFileSystem.Factory(file_system, branch_memcache)
|
| - api_list_data_source_factory = APIListDataSource.Factory(cache_factory,
|
| - file_system,
|
| - API_PATH,
|
| - PUBLIC_TEMPLATE_PATH)
|
| - api_data_source_factory = APIDataSource.Factory(
|
| - cache_factory,
|
| - API_PATH)
|
| -
|
| - # Give the ReferenceResolver a memcache, to speed up the lookup of
|
| - # duplicate $refs.
|
| - ref_resolver_factory = ReferenceResolver.Factory(
|
| - api_data_source_factory,
|
| - api_list_data_source_factory,
|
| - branch_memcache)
|
| - api_data_source_factory.SetReferenceResolverFactory(ref_resolver_factory)
|
| - samples_data_source_factory = SamplesDataSource.Factory(
|
| - channel_name,
|
| - file_system,
|
| - GITHUB_FILE_SYSTEM,
|
| - cache_factory,
|
| - GITHUB_COMPILED_FILE_SYSTEM,
|
| - ref_resolver_factory,
|
| - EXAMPLES_PATH)
|
| - api_data_source_factory.SetSamplesDataSourceFactory(
|
| - samples_data_source_factory)
|
| - intro_data_source_factory = IntroDataSource.Factory(
|
| - cache_factory,
|
| - ref_resolver_factory,
|
| - [INTRO_PATH, ARTICLE_PATH])
|
| - sidenav_data_source_factory = SidenavDataSource.Factory(cache_factory,
|
| - JSON_PATH)
|
| - template_data_source_factory = TemplateDataSource.Factory(
|
| - channel_name,
|
| - api_data_source_factory,
|
| - api_list_data_source_factory,
|
| - intro_data_source_factory,
|
| - samples_data_source_factory,
|
| - KNOWN_ISSUES_DATA_SOURCE,
|
| - sidenav_data_source_factory,
|
| - cache_factory,
|
| - ref_resolver_factory,
|
| - PUBLIC_TEMPLATE_PATH,
|
| - PRIVATE_TEMPLATE_PATH)
|
| - example_zipper = ExampleZipper(file_system,
|
| - cache_factory,
|
| - DOCS_PATH)
|
| -
|
| - instance = ServerInstance(template_data_source_factory,
|
| - example_zipper,
|
| - cache_factory)
|
| - SERVER_INSTANCES[instance_key] = instance
|
| - return instance
|
| -
|
| -def _CleanBranches():
|
| - keys = [_MakeInstanceKey(branch, number)
|
| - for branch, number in BRANCH_UTILITY.GetAllBranchNumbers()]
|
| - for key in SERVER_INSTANCES.keys():
|
| - if key not in keys:
|
| - SERVER_INSTANCES.pop(key)
|
| -
|
| -class _MockResponse(object):
|
| - def __init__(self):
|
| - self.status = 200
|
| - self.out = StringIO()
|
| - self.headers = {}
|
| -
|
| - def set_status(self, status):
|
| - self.status = status
|
| -
|
| - def clear(self, *args):
|
| - pass
|
| -
|
| -class _MockRequest(object):
|
| - def __init__(self, path):
|
| - self.headers = {}
|
| - self.path = path
|
| - self.url = 'http://localhost' + path
|
| -
|
| class Handler(webapp.RequestHandler):
|
| - def __init__(self, request, response, local_path=EXTENSIONS_PATH):
|
| - self._local_path = local_path
|
| + def __init__(self, request, response):
|
| super(Handler, self).__init__(request, response)
|
|
|
| def _HandleGet(self, path):
|
| - channel_name, real_path = BRANCH_UTILITY.SplitChannelNameFromPath(path)
|
| + channel_name, real_path = BranchUtility.SplitChannelNameFromPath(path)
|
|
|
| if channel_name == _DEFAULT_CHANNEL:
|
| self.redirect('/%s' % real_path)
|
| return
|
|
|
| - # TODO: Detect that these are directories and serve index.html out of them.
|
| + if channel_name is None:
|
| + channel_name = _DEFAULT_CHANNEL
|
| +
|
| + # TODO(kalman): Check if |path| is a directory and serve path/index.html
|
| + # rather than special-casing apps/extensions.
|
| if real_path.strip('/') == 'apps':
|
| real_path = 'apps/index.html'
|
| if real_path.strip('/') == 'extensions':
|
| real_path = 'extensions/index.html'
|
|
|
| - if (not real_path.startswith('extensions/') and
|
| - not real_path.startswith('apps/') and
|
| - not real_path.startswith('static/')):
|
| - if self._RedirectBadPaths(real_path, channel_name):
|
| - return
|
| -
|
| - _CleanBranches()
|
| -
|
| - # Yes, do this after it's passed to RedirectBadPaths. That needs to know
|
| - # whether or not a branch was specified.
|
| - if channel_name is None:
|
| - channel_name = _DEFAULT_CHANNEL
|
| - _GetInstanceForBranch(channel_name, self._local_path).Get(real_path,
|
| - self.request,
|
| - self.response)
|
| -
|
| - def _Render(self, files, channel):
|
| - original_response = self.response
|
| - for f in files:
|
| - if f.endswith('404.html'):
|
| - continue
|
| - path = channel + f.split(PUBLIC_TEMPLATE_PATH)[-1]
|
| - self.request = _MockRequest(path)
|
| - self.response = _MockResponse()
|
| - try:
|
| - self._HandleGet(path)
|
| - except Exception as e:
|
| - logging.error('Error rendering %s: %s' % (path, str(e)))
|
| - self.response = original_response
|
| -
|
| - class _ValueHolder(object):
|
| - """Class to allow a value to be changed within a lambda.
|
| - """
|
| - def __init__(self, starting_value):
|
| - self._value = starting_value
|
| + server_instance = ServerInstance.GetOrCreate(channel_name)
|
|
|
| - def Set(self, value):
|
| - self._value = value
|
| + canonical_path = server_instance.path_canonicalizer.Canonicalize(real_path)
|
| + if real_path != canonical_path:
|
| + self.redirect(canonical_path)
|
| + return
|
|
|
| - def Get(self):
|
| - return self._value
|
| + ServerInstance.GetOrCreate(channel_name).Get(real_path,
|
| + self.request,
|
| + self.response)
|
|
|
| def _HandleCron(self, path):
|
| - # Cache population strategy:
|
| - #
|
| - # We could list all files in PUBLIC_TEMPLATE_PATH then render them. However,
|
| - # this would be inefficient in the common case where files haven't changed
|
| - # since the last cron.
|
| + # Cron strategy:
|
| #
|
| - # Instead, let the CompiledFileSystem give us clues when to re-render: we
|
| - # use the CFS to check whether the templates, examples, or API folders have
|
| - # been changed. If there has been a change, the compilation function will
|
| - # be called. The same is then done separately with the apps samples page,
|
| - # since it pulls its data from Github.
|
| - channel = path.split('/')[-1]
|
| - branch = BRANCH_UTILITY.GetBranchNumberForChannelName(channel)
|
| - logging.info('Running cron job for %s.' % branch)
|
| - branch_memcache = InMemoryObjectStore(branch)
|
| - file_system = _CreateMemcacheFileSystem(branch, branch_memcache)
|
| - factory = CompiledFileSystem.Factory(file_system, branch_memcache)
|
| + # Find all public template files and static files, and render them. Most of
|
| + # the time these won't have changed since the last cron run, so it's a
|
| + # little wasteful, but hopefully rendering is really fast (if it isn't we
|
| + # have a problem).
|
| + class MockResponse(object):
|
| + def __init__(self):
|
| + self.status = 200
|
| + self.out = StringIO()
|
| + self.headers = {}
|
| + def set_status(self, status):
|
| + self.status = status
|
| + def clear(self, *args):
|
| + pass
|
| +
|
| + class MockRequest(object):
|
| + def __init__(self, path):
|
| + self.headers = {}
|
| + self.path = path
|
| + self.url = '//localhost/%s' % path
|
|
|
| - needs_render = self._ValueHolder(False)
|
| - invalidation_cache = factory.Create(lambda _, __: needs_render.Set(True),
|
| - compiled_fs.CRON_INVALIDATION,
|
| - version=_VERSION)
|
| - for path in [TEMPLATE_PATH, EXAMPLES_PATH, API_PATH]:
|
| - invalidation_cache.GetFromFile(path + '/')
|
| -
|
| - if needs_render.Get():
|
| - file_listing_cache = factory.Create(lambda _, x: x,
|
| - compiled_fs.CRON_FILE_LISTING)
|
| - self._Render(file_listing_cache.GetFromFileListing(PUBLIC_TEMPLATE_PATH),
|
| - channel)
|
| + channel = path.split('/')[-1]
|
| + logging.info('cron/%s: starting' % channel)
|
| +
|
| + server_instance = ServerInstance.GetOrCreate(channel)
|
| +
|
| + def run_cron_for_dir(d):
|
| + error = None
|
| + start_time = time.time()
|
| + files = [f for f in server_instance.content_cache.GetFromFileListing(d)
|
| + if not f.endswith('/')]
|
| + for f in files:
|
| + try:
|
| + server_instance.Get(f, MockRequest(f), MockResponse())
|
| + except error:
|
| + logging.error('cron/%s: error rendering %s/%s: %s' % (
|
| + channel, d, f, error))
|
| + logging.info('cron/%s: rendering %s files in %s took %s seconds' % (
|
| + channel, len(files), d, time.time() - start_time))
|
| + return error
|
| +
|
| + # Don't use "or" since we want to evaluate everything no matter what.
|
| + was_error = any((run_cron_for_dir(svn_constants.PUBLIC_TEMPLATE_PATH),
|
| + run_cron_for_dir(svn_constants.STATIC_PATH)))
|
| +
|
| + if was_error:
|
| + self.response.status = 500
|
| + self.response.out.write('Failure')
|
| else:
|
| - # If |needs_render| was True, this page was already rendered, and we don't
|
| - # need to render again.
|
| - github_invalidation_cache = GITHUB_COMPILED_FILE_SYSTEM.Create(
|
| - lambda _, __: needs_render.Set(True),
|
| - compiled_fs.CRON_GITHUB_INVALIDATION)
|
| - if needs_render.Get():
|
| - self._Render([PUBLIC_TEMPLATE_PATH + '/apps/samples.html'], channel)
|
| -
|
| - # It's good to keep the extensions samples page fresh, because if it
|
| - # gets dropped from the cache ALL the extensions pages time out.
|
| - self._Render([PUBLIC_TEMPLATE_PATH + '/extensions/samples.html'], channel)
|
| + self.response.status = 200
|
| + self.response.out.write('Success')
|
|
|
| - self.response.out.write('Success')
|
| + logging.info('cron/%s: finished' % channel)
|
|
|
| def _RedirectSpecialCases(self, path):
|
| google_dev_url = 'http://developer.google.com/chrome'
|
| @@ -314,38 +117,16 @@ class Handler(webapp.RequestHandler):
|
|
|
| return False
|
|
|
| - def _RedirectBadPaths(self, path, channel_name):
|
| - if '/' in path or path == '404.html':
|
| - return False
|
| - apps_templates = APPS_COMPILED_FILE_SYSTEM.GetFromFileListing(
|
| - PUBLIC_TEMPLATE_PATH + '/apps')
|
| - extensions_templates = EXTENSIONS_COMPILED_FILE_SYSTEM.GetFromFileListing(
|
| - PUBLIC_TEMPLATE_PATH + '/extensions')
|
| - unix_path = UnixName(os.path.splitext(path)[0])
|
| - if channel_name is None:
|
| - apps_path = '/apps/%s' % path
|
| - extensions_path = '/extensions/%s' % path
|
| - else:
|
| - apps_path = '/%s/apps/%s' % (channel_name, path)
|
| - extensions_path = '/%s/extensions/%s' % (channel_name, path)
|
| - if unix_path in extensions_templates:
|
| - self.redirect(extensions_path)
|
| - elif unix_path in apps_templates:
|
| - self.redirect(apps_path)
|
| - else:
|
| - self.redirect(extensions_path)
|
| - return True
|
| -
|
| def _RedirectFromCodeDotGoogleDotCom(self, path):
|
| if (not self.request.url.startswith(('http://code.google.com',
|
| 'https://code.google.com'))):
|
| return False
|
|
|
| - newUrl = 'http://developer.chrome.com/'
|
| + new_url = 'http://developer.chrome.com/'
|
|
|
| # switch to https if necessary
|
| if (self.request.url.startswith('https')):
|
| - newUrl = newUrl.replace('http', 'https', 1)
|
| + new_url = new_url.replace('http', 'https', 1)
|
|
|
| path = path.split('/')
|
| if len(path) > 0 and path[0] == 'chrome':
|
| @@ -355,8 +136,8 @@ class Handler(webapp.RequestHandler):
|
| position = path.index(channel)
|
| path.pop(position)
|
| path.insert(0, channel)
|
| - newUrl += '/'.join(path)
|
| - self.redirect(newUrl)
|
| + new_url += '/'.join(path)
|
| + self.redirect(new_url)
|
| return True
|
|
|
| def get(self):
|
| @@ -375,5 +156,7 @@ class Handler(webapp.RequestHandler):
|
| return
|
|
|
| path = path.strip('/')
|
| - if not self._RedirectFromCodeDotGoogleDotCom(path):
|
| - self._HandleGet(path)
|
| + if self._RedirectFromCodeDotGoogleDotCom(path):
|
| + return
|
| +
|
| + self._HandleGet(path)
|
|
|