Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/registerpages.py b/registerpages.py
index ed1f04c..1ceb486 100644
--- a/registerpages.py
+++ b/registerpages.py
@@ -1,79 +1,91 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file or at
-# https://developers.google.com/open-source/licenses/bsd
-
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """This file sets up all the urls for monorail pages."""
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
 
-import logging
-import webapp2
-import settings
+import flask
 
+import urllib
+
+from components import endpoints_flask
 from components import prpc
 
-from features import autolink
-from features import dateaction
 from features import banspammer
-from features import hotlistdetails
-from features import hotlistissues
-from features import hotlistissuescsv
-from features import hotlistpeople
-from features import filterrules
-from features import pubsub
-from features import userhotlists
-from features import notify
-from features import rerankhotlist
+from features import inboundemail
+from features import hotlistcreate
 from features import savedqueries
-
+from features import userhotlists
+from framework import banned
+from framework import clientmon
 from framework import csp_report
-from framework import deleteusers
-from framework import trimvisitedpages
+from framework import warmup
 from framework import reap
-from framework import registerpages_helpers
-from framework import urls
-
+from framework import deleteusers
+from framework import excessiveactivity
+from framework import teardown
+from framework import ts_mon_js
+from framework import trimvisitedpages
 from project import peopledetail
 from project import peoplelist
-from project import project_constants
 from project import projectadmin
 from project import projectadminadvanced
 from project import projectexport
 from project import projectsummary
 from project import projectupdates
+from project import project_constants
 from project import redirects
-
+from services import api_svc_v1
 from services import cachemanager_svc
 from services import client_config_svc
-
 from sitewide import custom_404
-from sitewide import userprofile
+from sitewide import hostinghome
+from sitewide import moved
 from sitewide import userclearbouncing
 from sitewide import userupdates
-
+from sitewide import userprofile
+from sitewide import projectcreate
+from sitewide import usersettings
+from sitewide import groupadmin
+from sitewide import groupcreate
+from sitewide import groupdetail
+from sitewide import grouplist
+from features import rerankhotlist
+from features import hotlistdetails
+from features import hotlistissues
+from features import hotlistissuescsv
+from features import hotlistpeople
+from features import dateaction
+from features import filterrules
+from features import pubsub
+from features import notify
+from features import hotlistcreate
+from features import savedqueries
+from features import userhotlists
+from features import banspammer
+from search import backendnonviewable
+from search import backendsearch
 from tracker import componentcreate
 from tracker import componentdetail
+from tracker import fltconversion
 from tracker import fieldcreate
 from tracker import fielddetail
+from tracker import templatecreate
+from tracker import templatedetail
 from tracker import issueadmin
 from tracker import issueadvsearch
 from tracker import issueattachment
 from tracker import issueattachmenttext
 from tracker import issuebulkedit
-from tracker import webcomponentspage
 from tracker import issuedetailezt
 from tracker import issueentry
 from tracker import issueentryafterlogin
 from tracker import issueexport
-from tracker import issueimport
 from tracker import issueoriginal
 from tracker import issuereindex
 from tracker import issuetips
-from tracker import templatecreate
-from tracker import templatedetail
-from tracker import fltconversion
+from tracker import issueimport
+
+from tracker import webcomponentspage
 
 from api import api_routes as api_routes_v0
 from api.v3 import api_routes as api_routes_v3
@@ -88,261 +100,740 @@
   def __init__(self):
     self.routes = []
 
-  def _AddRoute(self, path_regex, servlet_class, method, does_write=False):
-    """Add a GET or POST handler to our webapp2 route list.
+  def _AppendUrlToRoutes(self, rule_tuple, base=''):
+    """Register each of the given servlets."""
+    for rule in rule_tuple:
+      self.routes.append([base + rule[0], rule[1], rule[2]])
+
+  def Register(self, services, flask_instance):
+    """Register all the monorail request handlers."""
+    self._RegisterGroupUrls(services)
+    self._RegisterHostingUrl(services)
+    self._RegisterOldHostUrl(services)
+    self._RegisterRedirectProjectUrl()
+    self._RegisterCSPUrl()
+    self._RegisterProjectUrls(services, flask_instance)
+    self._RegisterUserUrls(services)
+    self._RegisterTaskUrl(services)
+    self._RegisterCronUrl(services)
+    self._RegisterBackendUrl(services)
+    self._RegisterMONSetUrl(services)
+    self._RegisterAHUrl(services)
+    self._RegisterPrpcUrl(services)
+    self._RegisterWebComponentsUrl(services)
+    self._RegisterFlaskUrlRules(flask_instance, self.routes)
+
+  def _RegisterFlaskUrlRules(
+      self, flask_instance, rule_tuple, removed_prefix=''):
+    """Add url rules to a given Flask instance.
 
     Args:
-      path_regex: string with webapp2 URL template regex.
-      servlet_class: a subclass of class Servlet.
-      method: string 'GET' or 'POST'.
-      does_write: True if the servlet could write to the database, we skip
-          registering such servlets when the site is in read_only mode. GET
-          handlers never write. Most, but not all, POST handlers do write.
+      flask_instance: The Flask app to add URLs to.
+      rule_tuple: List of tuple of path, module and method to call, HTTP method
+
+    Returns:
+      The Flask instance.
     """
-    if settings.read_only and does_write:
-      logging.info('Not registring %r because site is read-only', path_regex)
-      # TODO(jrobbins): register a helpful error page instead.
-    else:
-      self.routes.append(
-          webapp2.Route(path_regex, handler=servlet_class, methods=[method]))
+    for rule in rule_tuple:
+      url = rule[0][len(removed_prefix):] if rule[0].startswith(
+          removed_prefix) else rule[0]
+      flask_instance.add_url_rule(url, view_func=rule[1], methods=rule[2])
+    return flask_instance
 
-  def _SetupServlets(self, spec_dict, base='', post_does_write=True):
-    """Register each of the given servlets."""
-    for get_uri, servlet_class in spec_dict.items():
-      self._AddRoute(base + get_uri, servlet_class, 'GET')
-      post_uri = get_uri + ('edit.do' if get_uri.endswith('/') else '.do')
-      self._AddRoute(base + post_uri, servlet_class, 'POST',
-                     does_write=post_does_write)
+  # pylint: disable=unused-argument
+  def _RegisterGroupUrls(self, services):
+    _GROUP_URL = [
+        (
+            '/', grouplist.GroupList(services=services).GetGroupList,
+            ['GET']),
+        (
+            '/<string:viewed_username>/',
+            groupdetail.GroupDetail(services=services).GetGroupDetail, ['GET']),
+        (
+            '/<string:viewed_username>/edit.do',
+            groupdetail.GroupDetail(services=services).PostGroupDetail,
+            ['POST']),
+        (
+            '/<string:viewed_username>/groupadmin',
+            groupadmin.GroupAdmin(services=services).GetGroupAdmin, ['GET']),
+        (
+            '/<string:viewed_username>/groupadmin.do',
+            groupadmin.GroupAdmin(services=services).PostGroupAdmin, ['POST']),
+    ]
+    self._AppendUrlToRoutes(_GROUP_URL, '/g')
 
-  def _SetupProjectServlets(self, spec_dict, post_does_write=True):
-    """Register each of the given servlets in the project URI space."""
-    self._SetupServlets(
-        spec_dict, base='/p/<project_name:%s>' % self._PROJECT_NAME_REGEX,
-        post_does_write=post_does_write)
+  # pylint: disable=unused-argument
+  def _RegisterHostingUrl(self, service):
 
-  def _SetupUserServlets(self, spec_dict, post_does_write=True):
-    """Register each of the given servlets in the user URI space."""
-    self._SetupServlets(
-        spec_dict, base='/u/<viewed_username:%s>' % self._USERNAME_REGEX,
-        post_does_write=post_does_write)
+    def DefaultToMainPage():
+      url = flask.request.host_url
+      return flask.redirect(url)
 
-  def _SetupGroupServlets(self, spec_dict, post_does_write=True):
-    """Register each of the given servlets in the user group URI space."""
-    self._SetupServlets(
-        spec_dict, base='/g/<viewed_username:%s>' % self._USERNAME_REGEX,
-        post_does_write=post_does_write)
+    _HOSTING_URL = [
+        (
+            '/',
+            DefaultToMainPage,
+            ['GET'],
+        ),
+        (
+            '/excessiveActivity',
+            excessiveactivity.ExcessiveActivity(
+                services=service).GetExcessiveActivity, ['GET']),
+        (
+            '/settings',
+            usersettings.UserSettings(services=service).GetUserSetting, ['GET'
+                                                                        ]),
+        (
+            '/settings.do',
+            usersettings.UserSettings(services=service).PostUserSetting,
+            ['POST']),
+        ('/noAccess', banned.Banned(services=service).GetNoAccessPage, ['GET']),
+        (
+            '/moved', moved.ProjectMoved(services=service).GetProjectMoved,
+            ['GET']),
+        (
+            '/createProject',
+            projectcreate.ProjectCreate(services=service).GetCreateProject,
+            ['GET']),
+        (
+            '/createProject.do',
+            projectcreate.ProjectCreate(services=service).PostCreateProject,
+            ['POST']),
+        (
+            '/createHotlist',
+            hotlistcreate.HotlistCreate(services=service).GetCreateHotlist,
+            ['GET']),
+        (
+            '/createHotlist.do',
+            hotlistcreate.HotlistCreate(services=service).PostCreateHotlist,
+            ['POST']),
+        (
+            '/createGroup',
+            groupcreate.GroupCreate(services=service).GetGroupCreate, ['GET']),
+        (
+            '/createGroup.do',
+            groupcreate.GroupCreate(services=service).PostGroupCreate, ['POST'
+                                                                       ]),
+        (
+            '/deleteGroup',
+            grouplist.GroupList(services=service).GetGroupDelete, ['GET']),
+        (
+            '/deleteGroup.do',
+            grouplist.GroupList(services=service).PostGroupDelete,
+            ['POST']),
+    ]
 
-  def _SetupUserHotlistServlets(self, spec_dict, post_does_write=True):
-    """ Register given user hotlist servlets in the user URI space."""
-    self._SetupServlets(
-        spec_dict,
-        base ='/u/<viewed_username:%s>/hotlists/<hotlist_id:%s>'
-        % (self._USERNAME_REGEX, self._HOTLIST_ID_NAME_REGEX),
-        post_does_write=post_does_write)
+    self._AppendUrlToRoutes(_HOSTING_URL, '/hosting')
 
-  def Register(self, services):
-    """Register all the monorail request handlers."""
-    self._RegisterSitewideHandlers()
-    self._RegisterProjectHandlers()
-    self._RegisterIssueHandlers()
-    self._RegisterWebComponentsHanders()
-    self._RegisterRedirects()
+  def _RegisterOldHostUrl(self, service):
+    _OLD_HOSTING_URL = [
+        (
+            '/hosting_old/',
+            hostinghome.HostingHome(services=service).GetOldHostingHome,
+            ['GET']),
+    ]
+    self._AppendUrlToRoutes(_OLD_HOSTING_URL, '')
 
-    # Register pRPC API routes
-    prpc_server = prpc.Server(
+  def _RegisterRedirectProjectUrl(self):
+
+    def GetRedirectProject():
+      url = flask.request.host_url
+      return flask.redirect(url)
+
+    _PROJECT_REDIRECT_URL = [
+        ('/projects/', GetRedirectProject, ['GET']),
+    ]
+    self._AppendUrlToRoutes(_PROJECT_REDIRECT_URL, '')
+
+  def _RegisterCSPUrl(self):
+    self._AppendUrlToRoutes([('/csp.do', csp_report.postCsp, ['POST'])], '')
+
+  def _RegisterProjectUrls(self, service, flaskapp_project):
+    _PROJECT_URLS = [
+        (
+            '/<string:project_name>/<string:unrecognized>',
+            custom_404.ErrorPage(services=service).Get404Page,
+            ['GET'],
+        ),
+        (
+            '/<string:project_name>/adminComponents',
+            issueadmin.AdminComponents(services=service).GetAdminComponentsPage,
+            ['GET']),
+        (
+            '/<string:project_name>/adminComponents.do',
+            issueadmin.AdminComponents(
+                services=service).PostAdminComponentsPage, ['POST']),
+        (
+            '/<string:project_name>/adminIntro',
+            projectsummary.ProjectSummary(
+                services=service).GetProjectSummaryPage, ['GET']),
+        (
+            '/<string:project_name>/adminLabels',
+            issueadmin.AdminLabels(services=service).GetAdminLabelsPage,
+            ['GET']),
+        (
+            '/<string:project_name>/adminLabels.do',
+            issueadmin.AdminLabels(services=service).PostAdminLabelsPage,
+            ['POST']),
+        (
+            '/<string:project_name>/adminRules',
+            issueadmin.AdminRules(services=service).GetAdminRulesPage, ['GET']),
+        (
+            '/<string:project_name>/adminRules.do',
+            issueadmin.AdminRules(services=service).PostAdminRulesPage,
+            ['POST']),
+        (
+            '/<string:project_name>/adminStatuses',
+            issueadmin.AdminStatuses(services=service).GetAdminStatusesPage,
+            ['GET']),
+        (
+            '/<string:project_name>/adminStatuses.do',
+            issueadmin.AdminStatuses(services=service).PostAdminStatusesPage,
+            ['POST']),
+        (
+            '/<string:project_name>/adminTemplates',
+            issueadmin.AdminTemplates(services=service).GetAdminTemplatesPage,
+            ['GET']),
+        (
+            '/<string:project_name>/adminTemplates.do',
+            issueadmin.AdminTemplates(services=service).PostAdminTemplatesPage,
+            ['POST']),
+        (
+            '/<string:project_name>/adminViews',
+            issueadmin.AdminViews(services=service).GetAdminViewsPage, ['GET']),
+        (
+            '/<string:project_name>/adminViews.do',
+            issueadmin.AdminViews(services=service).PostAdminViewsPage,
+            ['POST']),
+        (
+            '/<string:project_name>/admin',
+            projectadmin.ProjectAdmin(services=service).GetProjectAdminPage,
+            ['GET']),
+        (
+            '/<string:project_name>/admin.do',
+            projectadmin.ProjectAdmin(services=service).PostProjectAdminPage,
+            ['POST']),
+        (
+            '/<string:project_name>/adminAdvanced',
+            projectadminadvanced.ProjectAdminAdvanced(
+                services=service).GetProjectAdminAdvancedPage, ['GET']),
+        (
+            '/<string:project_name>/adminAdvanced.do',
+            projectadminadvanced.ProjectAdminAdvanced(
+                services=service).PostProjectAdminAdvancedPage, ['POST']),
+        (
+            '/<string:project_name>/components/create',
+            componentcreate.ComponentCreate(
+                services=service).GetComponentCreatePage, ['GET']),
+        (
+            '/<string:project_name>/components/create.do',
+            componentcreate.ComponentCreate(
+                services=service).PostComponentCreatePage, ['POST']),
+        (
+            '/<string:project_name>/components/detail',
+            componentdetail.ComponentDetail(
+                services=service).GetComponentDetailPage, ['GET']),
+        (
+            '/<string:project_name>/components/detail.do',
+            componentdetail.ComponentDetail(
+                services=service).PostComponentDetailPage, ['POST']),
+        (
+            '/<string:project_name>/fields/create',
+            fieldcreate.FieldCreate(services=service).GetFieldCreate, ['GET']),
+        (
+            '/<string:project_name>/fields/create.do',
+            fieldcreate.FieldCreate(services=service).PostFieldCreate, ['POST'
+                                                                       ]),
+        (
+            '/<string:project_name>/fields/detail',
+            fielddetail.FieldDetail(services=service).GetFieldDetail, ['GET']),
+        (
+            '/<string:project_name>/fields/detail.do',
+            fielddetail.FieldDetail(services=service).PostFieldDetail, ['POST'
+                                                                       ]),
+        (
+            '/<string:project_name>/issues/advsearch',
+            issueadvsearch.IssueAdvancedSearch(
+                services=service).GetIssueAdvSearchPage, ['GET']),
+        (
+            '/<string:project_name>/issues/advsearch.do',
+            issueadvsearch.IssueAdvancedSearch(
+                services=service).PostIssueAdvSearchPage, ['POST']),
+        (
+            '/<string:project_name>/issues/detail',
+            webcomponentspage.WebComponentsPage(
+                services=service).GetWebComponentsIssueDetail, ['GET']),
+        (
+            '/<string:project_name>/issues/export',
+            issueexport.IssueExport(services=service).GetIssueExport, ['GET']),
+        (
+            '/<string:project_name>/issues/export/json',
+            issueexport.IssueExportJSON(services=service).GetIssueExportJSON,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/export/json.do',
+            issueexport.IssueExportJSON(services=service).PostIssueExportJSON,
+            ['POST']),
+        (
+            '/<string:project_name>/issues/import',
+            issueimport.IssueImport(services=service).GetIssueImport, ['GET']),
+        (
+            '/<string:project_name>/issues/import.do',
+            issueimport.IssueImport(services=service).PostIssueImport, ['POST'
+                                                                       ]),
+        (
+            '/<string:project_name>/issues/original',
+            issueoriginal.IssueOriginal(services=service).GetIssueOriginal,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/entry',
+            issueentry.IssueEntry(services=service).GetIssueEntry, ['GET']),
+        (
+            '/<string:project_name>/issues/entry.do',
+            issueentry.IssueEntry(services=service).PostIssueEntry, ['POST']),
+        (
+            '/<string:project_name>/issues/entry_new',
+            webcomponentspage.WebComponentsPage(
+                services=service).GetWebComponentsIssueNewEntry, ['GET']),
+        (
+            '/<string:project_name>/issues/list',
+            webcomponentspage.WebComponentsPage(
+                services=service).GetWebComponentsIssueList, ['GET']),
+        (
+            '/<string:project_name>/issues/reindex',
+            issuereindex.IssueReindex(services=service).GetIssueReindex,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/reindex.do',
+            issuereindex.IssueReindex(services=service).PostIssueReindex,
+            ['POST']),
+        (
+            '/<string:project_name>/issues/detail/list',
+            issuedetailezt.FlipperList(services=service).GetFlipperList,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/detail/flipper',
+            issuedetailezt.FlipperIndex(services=service).GetFlipperIndex,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/detail/flipper.do',
+            issuedetailezt.FlipperIndex(services=service).PostFlipperIndex,
+            ['POST']),
+        (
+            '/<string:project_name>/issues/wizard',
+            webcomponentspage.WebComponentsPage(
+                services=service).GetWebComponentsIssueWizard, ['GET']),
+        (
+            '/<string:project_name>/templates/create',
+            templatecreate.TemplateCreate(services=service).GetTemplateCreate,
+            ['GET']),
+        (
+            '/<string:project_name>/templates/create.do',
+            templatecreate.TemplateCreate(services=service).PostTemplateCreate,
+            ['POST']),
+        (
+            '/<string:project_name>/templates/detail',
+            templatedetail.TemplateDetail(services=service).GetTemplateDetail,
+            ['GET']),
+        (
+            '/<string:project_name>/templates/detail.do',
+            templatedetail.TemplateDetail(services=service).PostTemplateDetail,
+            ['POST']),
+        (
+            '/<string:project_name>/people/list',
+            peoplelist.PeopleList(services=service).GetPeopleListPage, ['GET']),
+        (
+            '/<string:project_name>/people/list.do',
+            peoplelist.PeopleList(services=service).PostPeopleListPage,
+            ['POST']),
+        (
+            '/<string:project_name>/people/detail',
+            peopledetail.PeopleDetail(services=service).GetPeopleDetailPage,
+            ['GET']),
+        (
+            '/<string:project_name>/people/detail.do',
+            peopledetail.PeopleDetail(services=service).PostPeopleDetailPage,
+            ['POST']),
+        (
+            '/<string:project_name>/projectExport',
+            projectexport.ProjectExport(services=service).GetProjectExportPage,
+            ['GET']),
+        (
+            '/<string:project_name>/projectExport/json',
+            projectexport.ProjectExportJSON(
+                services=service).GetProjectExportJSONPage, ['GET']),
+        (
+            '/<string:project_name>/projectExport/json.do',
+            projectexport.ProjectExportJSON(
+                services=service).PostProjectExportJSONPage, ['POST']),
+        (
+            '/<string:project_name>/updates/list',
+            projectupdates.ProjectUpdates(
+                services=service).GetProjectUpdatesPage, ['GET']),
+        (
+            '/<string:project_name>/w/list',
+            redirects.WikiRedirect(services=service).GetWikiListRedirect,
+            ['GET']),
+        (
+            '/<string:project_name>/wiki/<string:wiki_page>',
+            redirects.WikiRedirect(services=service).GetWikiRedirect, ['GET']),
+        (
+            '/<string:project_name>/source/<string:source_page>',
+            redirects.SourceRedirect(services=service).GetSourceRedirect,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/entryafterlogin',
+            issueentryafterlogin.IssueEntryAfterLogin(
+                services=service).GetIssueEntryAfterLogin,
+            ['GET'],
+        ),
+        (
+            '/<string:project_name>/issues/searchtips',
+            issuetips.IssueSearchTips(services=service).GetIssueSearchTips,
+            ['GET'],
+        ),
+        (
+            '/<string:project_name>/issues/attachment',
+            issueattachment.AttachmentPage(services=service).GetAttachmentPage,
+            ['GET'],
+        ),
+        (
+            '/<string:project_name>/issues/attachmentText',
+            issueattachmenttext.AttachmentText(
+                services=service).GetAttachmentText,
+            ['GET'],
+        ),
+        (
+            '/<string:project_name>/issues/bulkedit',
+            issuebulkedit.IssueBulkEdit(services=service).GetIssueBulkEdit,
+            ['GET']),
+        (
+            '/<string:project_name>/issues/bulkedit.do',
+            issuebulkedit.IssueBulkEdit(services=service).PostIssueBulkEdit,
+            ['POST']),
+        (
+            '/<string:project_name>/issues/detail/next',
+            issuedetailezt.FlipperNext(
+                services=service).GetFlipperNextRedirectPage, ['GET']),
+        (
+            '/<string:project_name>/issues/detail/previous',
+            issuedetailezt.FlipperPrev(
+                services=service).GetFlipperPrevRedirectPage, ['GET']),
+    ]
+    self._AppendUrlToRoutes(_PROJECT_URLS, '/p')
+
+    # pylint: disable=unused-variable
+    @flaskapp_project.route('/p/<string:project_name>/issues/approval')
+    @flaskapp_project.route('/p/<string:project_name>/issues/detail_ezt')
+    def ProjectRedirectToIssueDetail(project_name):
+      host_url = flask.request.host_url
+      url = host_url + 'p/' + project_name + '/issues/detail'
+      query_string = flask.request.query_string
+      if query_string:
+        url = '%s?%s' % (url, query_string)
+      return flask.redirect(url)
+
+    # pylint: disable=unused-variable
+    @flaskapp_project.route('/p/<string:project_name>/issues/list_new')
+    @flaskapp_project.route('/p/<string:project_name>/')
+    @flaskapp_project.route('/p/<string:project_name>/issues/')
+    def ProjectRedirectToIssueList(project_name):
+      host_url = flask.request.host_url
+      url = host_url + 'p/' + project_name + '/issues/list'
+      query_string = urllib.parse.urlencode(flask.request.args)
+      if query_string:
+        url = '%s?%s' % (url, query_string)
+      return flask.redirect(url)
+
+    # pylint: disable=unused-variable
+    @flaskapp_project.route('/p/')
+    def ProjectRedirectToMainPage():
+      url = flask.request.host_url
+      return flask.redirect(url)
+
+    # pylint: disable=unused-variable
+    @flaskapp_project.route('/p/<string:project_name>/people/')
+    def ProjectRedirectToPeopleList(project_name):
+      host_url = flask.request.host_url
+      url = host_url + 'p/' + project_name + '/people/list'
+      return flask.redirect(url)
+
+  def _RegisterUserUrls(self, service):
+
+    def UserRedirectToMainPage():
+      url = flask.request.host_url
+      return flask.redirect(url)
+
+    _USER_URLS = [
+        (
+            '/',
+            UserRedirectToMainPage,
+            ['GET'],
+        ),
+        (
+            '/<string:viewed_username>/queries',
+            savedqueries.SavedQueries(services=service).GetSavedQueriesPage,
+            ['GET']),
+        (
+            '/<string:viewed_username>/queries.do',
+            savedqueries.SavedQueries(services=service).PostSavedQueriesPage,
+            ['Post']),
+        (
+            '/<string:viewed_username>/hotlists',
+            userhotlists.UserHotlists(services=service).GetUserHotlistsPage,
+            ['GET']),
+        (
+            '/<string:viewed_username>/hotlists.do',
+            userhotlists.UserHotlists(services=service).PostUserHotlistsPage,
+            ['Post']),
+        (
+            '/<string:viewed_username>/',
+            userprofile.UserProfile(services=service).GetUserProfilePage,
+            ['GET']),
+        (
+            '/<string:viewed_username>/edit.do',
+            userprofile.UserProfile(services=service).PostUserProfilePage,
+            ['POST']),
+        (
+            '/<string:viewed_username>/ban.do',
+            userprofile.BanUser(services=service).PostBanUserPage, ['POST']),
+        (
+            '/<string:viewed_username>/banSpammer.do',
+            banspammer.BanSpammer(services=service).PostBanSpammerPage,
+            ['POST']),
+        (
+            '/<string:viewed_username>/clearBouncing',
+            userclearbouncing.UserClearBouncing(
+                services=service).GetUserClearBouncingPage, ['GET']),
+        (
+            '/<string:viewed_username>/clearBouncing.do',
+            userclearbouncing.UserClearBouncing(
+                services=service).PostUserClearBouncingPage, ['Post']),
+        (
+            '/<string:viewed_username>/updates/projects',
+            userupdates.UserUpdatesProjects(
+                services=service).GetUserUpdatesProjectsPage, ['GET']),
+        (
+            '/<string:viewed_username>/updates/developers',
+            userupdates.UserUpdatesDevelopers(
+                services=service).GetUserUpdatesDevelopersPage, ['GET']),
+        (
+            '/<string:viewed_username>/updates',
+            userupdates.UserUpdatesIndividual(
+                services=service).GetUserUpdatesPage, ['GET']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>',
+            hotlistissues.HotlistIssues(services=service).GetHotlistIssuesPage,
+            ['GET']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>.do',
+            hotlistissues.HotlistIssues(services=service).PostHotlistIssuesPage,
+            ['POST']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/csv',
+            hotlistissuescsv.HotlistIssuesCsv(
+                services=service).GetHotlistIssuesCsvPage, ['GET']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/people',
+            hotlistpeople.HotlistPeopleList(
+                services=service).GetHotlistPeoplePage, ['GET']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/people.do',
+            hotlistpeople.HotlistPeopleList(
+                services=service).PostHotlistPeoplePage, ['POST']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/details',
+            hotlistdetails.HotlistDetails(
+                services=service).GetHotlistDetailsPage, ['GET']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/details.do',
+            hotlistdetails.HotlistDetails(
+                services=service).PostHotlistDetailsPage, ['POST']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/rerank',
+            rerankhotlist.RerankHotlistIssue(
+                services=service).GetRerankHotlistIssuePage, ['GET']),
+        (
+            '/<string:viewed_username>/hotlists/<string:hotlist_id>/rerank.do',
+            rerankhotlist.RerankHotlistIssue(
+                services=service).PostRerankHotlistIssuePage, ['POST']),
+    ]
+
+    self._AppendUrlToRoutes(_USER_URLS, '/u')
+
+  # pylint: disable=unused-argument
+  def _RegisterTaskUrl(self, service):
+    _TASK_URL = [
+        (
+            '/banSpammer.do',
+            banspammer.BanSpammerTask(services=service).PostBanSpammer,
+            ['POST']),
+        (
+            '/sendWipeoutUserListsTask.do',
+            deleteusers.SendWipeoutUserListsTask(
+                services=service).PostSendWipeoutUserListsTask, ['POST']),
+        (
+            '/deleteWipeoutUsersTask.do',
+            deleteusers.DeleteWipeoutUsersTask(
+                services=service).PostDeleteWipeoutUsersTask, ['POST']),
+        (
+            '/deleteUsersTask.do',
+            deleteusers.DeleteUsersTask(services=service).PostDeleteUsersTask,
+            ['POST']),
+        (
+            '/notifyRulesDeleted.do',
+            notify.NotifyRulesDeletedTask(
+                services=service).PostNotifyRulesDeletedTask, ['POST']),
+        (
+            '/notifyIssueChange.do',
+            notify.NotifyIssueChangeTask(
+                services=service).PostNotifyIssueChangeTask, ['POST']),
+        (
+            '/notifyBlockingChange.do',
+            notify.NotifyBlockingChangeTask(
+                services=service).PostNotifyBlockingChangeTask, ['POST']),
+        (
+            '/notifyBulkEdit.do', notify.NotifyBulkChangeTask(
+                services=service).PostNotifyBulkChangeTask, ['POST']),
+        (
+            '/notifyApprovalChange.do',
+            notify.NotifyApprovalChangeTask(
+                services=service).PostNotifyApprovalChangeTask, ['POST']),
+        (
+            '/publishPubsubIssueChange.do',
+            pubsub.PublishPubsubIssueChangeTask(
+                services=service).PostPublishPubsubIssueChangeTask, ['POST']),
+        (
+            '/issueDateAction.do',
+            dateaction.IssueDateActionTask(
+                services=service).PostIssueDateActionTask, ['POST']),
+        (
+            '/fltConversionTask.do',
+            fltconversion.FLTConvertTask(services=service).PostFLTConvertTask,
+            ['POST']),
+        (
+            '/outboundEmail.do',
+            notify.OutboundEmailTask(services=service).PostOutboundEmailTask,
+            ['POST']),
+        (
+            '/recomputeDerivedFields.do',
+            filterrules.RecomputeDerivedFieldsTask(
+                services=service).PostRecomputeDerivedFieldsTask, ['POST']),
+    ]
+    self._AppendUrlToRoutes(_TASK_URL, '/_task')
+
+  # pylint: disable=unused-argument
+  def _RegisterCronUrl(self, service):
+    _CRON_URL = [
+        (
+            '/wipeoutSync',
+            deleteusers.WipeoutSyncCron(services=service).GetWipeoutSyncCron,
+            ['GET']),
+        (
+            '/reindexQueue',
+            filterrules.ReindexQueueCron(services=service).GetReindexQueueCron,
+            ['GET']),
+        (
+            '/dateAction',
+            dateaction.DateActionCron(services=service).GetDateActionCron,
+            ['GET']),
+        (
+            '/ramCacheConsolidate',
+            cachemanager_svc.RamCacheConsolidate(
+                services=service).GetRamCacheConsolidate, ['GET']),
+        ('/reap', reap.Reap(services=service).GetReap, ['GET']),
+        (
+            '/loadApiClientConfigs', client_config_svc.GetLoadApiClientConfigs,
+            ['GET']),
+        (
+            '/trimVisitedPages',
+            trimvisitedpages.TrimVisitedPages(
+                services=service).GetTrimVisitedPages, ['GET']),
+    ]
+    self._AppendUrlToRoutes(_CRON_URL, '/_cron')
+
+  # pylint: disable=unused-argument
+  def _RegisterBackendUrl(self, service):
+    _BACKEND_URL = [
+        (
+            '/search',
+            backendsearch.BackendSearch(services=service).GetBackendSearch,
+            ['GET']),
+        (
+            '/nonviewable',
+            backendnonviewable.BackendNonviewable(
+                services=service).GetBackendNonviewable, ['GET']),
+    ]
+    self._AppendUrlToRoutes(_BACKEND_URL, '/_backend')
+
+  # pylint: disable=unused-argument
+  def _RegisterMONSetUrl(self, service):
+    _MON_URL = [
+        (
+            '/clientmon.do',
+            clientmon.ClientMonitor(services=service).PostClientMonitor,
+            ['POST']),
+        (
+            '/jstsmon.do',
+            ts_mon_js.MonorailTSMonJSHandler(
+                services=service).PostMonorailTSMonJSHandler,
+            ['POST'],
+        )
+    ]
+    self._AppendUrlToRoutes(_MON_URL, '/_')
+
+  def _RegisterAHUrl(self, service):
+    _AH_URL = [
+        ('/warmup', warmup.Warmup, ['GET']), ('/start', warmup.Start, ['GET']),
+        ('/stop', warmup.Stop, ['GET']),
+        (
+            '/bounce',
+            inboundemail.BouncedEmail(services=service).postBouncedEmail,
+            ['POST']),
+        (
+            '/mail/<string:project_addr>',
+            inboundemail.InboundEmail(services=service).HandleInboundEmail,
+            ['GET', 'POST'])
+    ]
+    self._AppendUrlToRoutes(_AH_URL, '/_ah')
+
+  def _RegisterPrpcUrl(self, service):
+    prpc_server = prpc.FlaskServer(
         allowed_origins=client_config_svc.GetAllowedOriginsSet())
-    api_routes_v0.RegisterApiHandlers(prpc_server, services)
-    api_routes_v3.RegisterApiHandlers(prpc_server, services)
-    self.routes.extend(prpc_server.get_routes())
+    api_routes_v0.RegisterApiHandlers(prpc_server, service)
+    api_routes_v3.RegisterApiHandlers(prpc_server, service)
+    routes = prpc_server.get_routes()
+    self._AppendUrlToRoutes(routes, '')
 
-    autolink.RegisterAutolink(services)
-    # Error pages should be the last to register.
-    self._RegisterErrorPages()
-    return self.routes
+  def _RegisterWebComponentsUrl(self, service):
+    self.routes.append(
+        [
+            '/',
+            webcomponentspage.ProjectListPage(
+                services=service).GetProjectListPage, ['GET']
+        ])
+    self.routes.append(
+        [
+            '/hotlists/<path:subpath>',
+            webcomponentspage.WebComponentsPage(
+                services=service).GetWebComponentsHotlist, ['GET']
+        ])
+    self.routes.append(
+        [
+            '/users/<path:subpath>',
+            webcomponentspage.WebComponentsPage(
+                services=service).GetWebComponentsUser, ['GET']
+        ])
 
-  def _RegisterProjectHandlers(self):
-    """Register page and form handlers that operate within a project."""
 
-    self._SetupProjectServlets(
-        {
-            urls.ADMIN_INTRO: projectsummary.ProjectSummary,
-            urls.PEOPLE_LIST: peoplelist.PeopleList,
-            urls.PEOPLE_DETAIL: peopledetail.PeopleDetail,
-            urls.UPDATES_LIST: projectupdates.ProjectUpdates,
-            urls.ADMIN_META: projectadmin.ProjectAdmin,
-            urls.ADMIN_ADVANCED: projectadminadvanced.ProjectAdminAdvanced,
-            urls.ADMIN_EXPORT: projectexport.ProjectExport,
-            urls.ADMIN_EXPORT_JSON: projectexport.ProjectExportJSON,
-        })
+def RegisterEndpointsUrls(app):
+  api_classes = [api_svc_v1.MonorailApi, api_svc_v1.ClientConfigApi]
+  routes = endpoints_flask.api_routes(api_classes, '/_ah/api')
+  for rule, endpoint, view_func, methods in routes:
+    app.add_url_rule(
+        rule, endpoint=endpoint, view_func=view_func, methods=methods)
+  app.view_functions['cors_handler'] = endpoints_flask.cors_handler
 
-  def _RegisterIssueHandlers(self):
-    """Register page and form handlers for the issue tracker."""
 
-    self._SetupProjectServlets(
-        {
-            urls.ISSUE_APPROVAL:
-                registerpages_helpers.MakeRedirectInScope(
-                    urls.ISSUE_DETAIL, 'p', keep_qs=True),
-            urls.ISSUE_LIST:
-                webcomponentspage.WebComponentsPage,
-            urls.ISSUE_LIST_NEW_TEMP:
-                registerpages_helpers.MakeRedirectInScope(
-                    urls.ISSUE_LIST, 'p', keep_qs=True),
-            urls.ISSUE_REINDEX:
-                issuereindex.IssueReindex,
-            urls.ISSUE_DETAIL_FLIPPER_NEXT:
-                issuedetailezt.FlipperNext,
-            urls.ISSUE_DETAIL_FLIPPER_PREV:
-                issuedetailezt.FlipperPrev,
-            urls.ISSUE_DETAIL_FLIPPER_LIST:
-                issuedetailezt.FlipperList,
-            urls.ISSUE_DETAIL_FLIPPER_INDEX:
-                issuedetailezt.FlipperIndex,
-            urls.ISSUE_DETAIL_LEGACY:
-                registerpages_helpers.MakeRedirectInScope(
-                    urls.ISSUE_DETAIL, 'p', keep_qs=True),
-            urls.ISSUE_WIZARD:
-                webcomponentspage.WebComponentsPage,
-            urls.ISSUE_ENTRY:
-                issueentry.IssueEntry,
-            urls.ISSUE_ENTRY_NEW:
-                webcomponentspage.WebComponentsPage,
-            urls.ISSUE_ENTRY_AFTER_LOGIN:
-                issueentryafterlogin.IssueEntryAfterLogin,
-            urls.ISSUE_TIPS:
-                issuetips.IssueSearchTips,
-            urls.ISSUE_ATTACHMENT:
-                issueattachment.AttachmentPage,
-            urls.ISSUE_ATTACHMENT_TEXT:
-                issueattachmenttext.AttachmentText,
-            urls.ISSUE_BULK_EDIT:
-                issuebulkedit.IssueBulkEdit,
-            urls.COMPONENT_CREATE:
-                componentcreate.ComponentCreate,
-            urls.COMPONENT_DETAIL:
-                componentdetail.ComponentDetail,
-            urls.FIELD_CREATE:
-                fieldcreate.FieldCreate,
-            urls.FIELD_DETAIL:
-                fielddetail.FieldDetail,
-            urls.TEMPLATE_CREATE:
-                templatecreate.TemplateCreate,
-            urls.TEMPLATE_DETAIL:
-                templatedetail.TemplateDetail,
-            urls.WIKI_LIST:
-                redirects.WikiRedirect,
-            urls.WIKI_PAGE:
-                redirects.WikiRedirect,
-            urls.SOURCE_PAGE:
-                redirects.SourceRedirect,
-            urls.ADMIN_STATUSES:
-                issueadmin.AdminStatuses,
-            urls.ADMIN_LABELS:
-                issueadmin.AdminLabels,
-            urls.ADMIN_RULES:
-                issueadmin.AdminRules,
-            urls.ADMIN_TEMPLATES:
-                issueadmin.AdminTemplates,
-            urls.ADMIN_COMPONENTS:
-                issueadmin.AdminComponents,
-            urls.ADMIN_VIEWS:
-                issueadmin.AdminViews,
-            urls.ISSUE_ORIGINAL:
-                issueoriginal.IssueOriginal,
-            urls.ISSUE_EXPORT:
-                issueexport.IssueExport,
-            urls.ISSUE_EXPORT_JSON:
-                issueexport.IssueExportJSON,
-            urls.ISSUE_IMPORT:
-                issueimport.IssueImport,
-        })
-
-    # GETs for /issues/detail are now handled by the web components page.
-    base = '/p/<project_name:%s>' % self._PROJECT_NAME_REGEX
-    self._AddRoute(base + urls.ISSUE_DETAIL,
-                   webcomponentspage.WebComponentsPage, 'GET')
-
-    self._SetupUserServlets({
-        urls.SAVED_QUERIES: savedqueries.SavedQueries,
-        urls.HOTLISTS: userhotlists.UserHotlists,
-        })
-
-    user_hotlists_redir = registerpages_helpers.MakeRedirectInScope(
-        urls.HOTLISTS, 'u', keep_qs=True)
-    self._SetupUserServlets({
-        '/hotlists/': user_hotlists_redir,
-        })
-
-    # These servlets accept POST, but never write to the database, so they can
-    # still be used when the site is read-only.
-    self._SetupProjectServlets({
-        urls.ISSUE_ADVSEARCH: issueadvsearch.IssueAdvancedSearch,
-        }, post_does_write=False)
-
-    list_redir = registerpages_helpers.MakeRedirectInScope(
-        urls.ISSUE_LIST, 'p', keep_qs=True)
-    self._SetupProjectServlets({
-        '': list_redir,
-        '/': list_redir,
-        '/issues': list_redir,
-        '/issues/': list_redir,
-        })
-
-  def _RegisterSitewideHandlers(self):
-    """Register page and form handlers that aren't associated with projects."""
-
-    self._SetupUserServlets({
-        urls.USER_PROFILE: userprofile.UserProfile,
-        urls.BAN_USER: userprofile.BanUser,
-        urls.BAN_SPAMMER: banspammer.BanSpammer,
-        urls.USER_CLEAR_BOUNCING: userclearbouncing.UserClearBouncing,
-        urls.USER_UPDATES_PROJECTS: userupdates.UserUpdatesProjects,
-        urls.USER_UPDATES_DEVELOPERS: userupdates.UserUpdatesDevelopers,
-        urls.USER_UPDATES_MINE: userupdates.UserUpdatesIndividual,
-        })
-
-    self._SetupUserHotlistServlets(
-        {
-            urls.HOTLIST_ISSUES: hotlistissues.HotlistIssues,
-            urls.HOTLIST_ISSUES_CSV: hotlistissuescsv.HotlistIssuesCsv,
-            urls.HOTLIST_PEOPLE: hotlistpeople.HotlistPeopleList,
-            urls.HOTLIST_DETAIL: hotlistdetails.HotlistDetails,
-            urls.HOTLIST_RERANK_JSON: rerankhotlist.RerankHotlistIssue,
-        })
-
-    profile_redir = registerpages_helpers.MakeRedirectInScope(
-        urls.USER_PROFILE, 'u')
-    self._SetupUserServlets({'': profile_redir})
-
-  def _RegisterWebComponentsHanders(self):
-    """Register page handlers that are handled by WebComponentsPage."""
-    self._AddRoute('/', webcomponentspage.ProjectListPage, 'GET')
-    self._AddRoute(
-        '/hotlists<unused:.*>', webcomponentspage.WebComponentsPage, 'GET')
-    self._AddRoute('/users<unused:.*>', webcomponentspage.WebComponentsPage,
-                   'GET')
-
-  def _RegisterRedirects(self):
-    """Register redirects among pages inside monorail."""
-    redirect = registerpages_helpers.MakeRedirect('/')
-    self._SetupServlets(
-        {
-            '/p': redirect,
-            '/p/': redirect,
-            '/u': redirect,
-            '/u/': redirect,
-            '/': redirect,
-        })
-
-    redirect = registerpages_helpers.MakeRedirectInScope(
-        urls.PEOPLE_LIST, 'p')
-    self._SetupProjectServlets({
-        '/people': redirect,
-        '/people/': redirect,
-        })
-
-  def _RegisterErrorPages(self):
-    """Register handlers for errors."""
-    self._AddRoute(
-        '/p/<project_name:%s>/<unrecognized:.+>' % self._PROJECT_NAME_REGEX,
-        custom_404.ErrorPage, 'GET')
+def RegisterTeardown(app):
+  app.teardown_request(teardown.Teardown)