Add HTTP handler that redirects requests for Mojo apps
This CL adds an HTTP handler whose purpose is to redirect requests for Mojo
apps to the locations of the current versions of the binaries. It installs
itself as a handler for all requests and operates as follows:
(1) Ensure that the request is of the form "/platform/app", e.g.,
"/linux-x64/view_manager.mojo".
(2) Load the file containing the up-to-date version of the requested app, which
resides in a known location for the app and platform, e.g.,
"https://storage.googleapis.com/mojo/services/linux-x64/view_manager_location".
(3) Return a redirect to the contents of that file.
The expected usage of this app is to make it accessible from a server such as
https://foo.com and then to direct the Mojo shell to send requests for mojo:
apps to this server, e.g. "--origin=https://foo.com/linux-64". Note that the
platform must be specified explicitly since the shell does not send that
information at this time.
R=qsr@chromium.org
Review URL: https://codereview.chromium.org/953353002
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 7abd919..355778a 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -31,6 +31,6 @@
}
if (is_linux) {
- deps += [ "//services/python/content_handler" ]
+ deps += [ "//services/python" ]
}
}
diff --git a/services/python/BUILD.gn b/services/python/BUILD.gn
new file mode 100644
index 0000000..48d3421
--- /dev/null
+++ b/services/python/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2014 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.
+
+group("python") {
+ assert(is_linux)
+ deps = [
+ "//services/python/content_handler",
+ "//services/python/mojo_url_redirector",
+ ]
+}
diff --git a/services/python/mojo_url_redirector/BUILD.gn b/services/python/mojo_url_redirector/BUILD.gn
new file mode 100644
index 0000000..21a09dd
--- /dev/null
+++ b/services/python/mojo_url_redirector/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright 2015 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.
+
+import("//mojo/public/python/rules.gni")
+
+python_packaged_application("mojo_url_redirector") {
+ sources = [
+ "__mojo__.py",
+ ]
+ deps = [
+ "//mojo/public/interfaces/application",
+ "//mojo/public/python:packaged_application",
+ "//mojo/public/python:packaged_bindings",
+ "//mojo/python:packaged_utils",
+ "//mojo/services/network/public/interfaces",
+ "//services/http_server/public",
+ ]
+ datadeps = [
+ "//services/python/content_handler",
+ ]
+ debug = true
+}
diff --git a/services/python/mojo_url_redirector/__mojo__.py b/services/python/mojo_url_redirector/__mojo__.py
new file mode 100644
index 0000000..3028bb9
--- /dev/null
+++ b/services/python/mojo_url_redirector/__mojo__.py
@@ -0,0 +1,174 @@
+# Copyright 2015 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.
+
+"""Application that serves requests for Mojo apps and responds with redirects
+to the locations of the most recent versions of these apps."""
+
+import logging
+import os
+
+import http_request_mojom
+import http_response_mojom
+import http_server_mojom
+import http_server_factory_mojom
+import net_address_mojom
+import network_service_mojom
+import service_provider_mojom
+import url_loader_mojom
+
+from mojo_application import application_delegate
+from mojo_application import application_impl
+from mojo_application import application_runner
+
+from mojo_bindings import promise
+import mojo_system
+from mojo_utils import data_pipe_utils
+
+
+def _HttpResponseWithStatusCode(status_code):
+ response = http_response_mojom.HttpResponse()
+ response.status_code = status_code
+ return response
+
+
+def _HttpBadRequestResponse():
+ return _HttpResponseWithStatusCode(400)
+
+
+def _HttpNotFoundResponse():
+ return _HttpResponseWithStatusCode(404)
+
+
+def _HttpInternalServerErrorResponse():
+ return _HttpResponseWithStatusCode(500)
+
+
+def _HttpRedirectResponse(new_location):
+ response = _HttpResponseWithStatusCode(302)
+ custom_headers = {}
+ custom_headers["location"] = new_location
+ response.custom_headers = custom_headers
+ return response
+
+
+def _RequestIdentifier(requested_app, requested_platform):
+ return "[Request: %s on %s]" % (requested_app, requested_platform)
+
+
+def _LogMessageForRequest(request_identifier, message, log=logging.info,
+ exc_info=None):
+ log("%s %s" % (request_identifier, message), exc_info=exc_info)
+
+
+class MojoUrlRedirector(http_server_mojom.HttpHandler):
+ def __init__(self, network_service):
+ self.network_service = network_service
+
+ def HandleRequest(self, request):
+ # Parse the components of the request.
+ relative_url_components = request.relative_url.split("/")
+
+ # The request must have a component for the platform and for the app.
+ if len(relative_url_components) != 3:
+ return _HttpBadRequestResponse()
+
+ requested_platform = relative_url_components[1]
+ requested_app = relative_url_components[2]
+
+ def OnRedirectToAppRejected(error):
+ request_identifier = _RequestIdentifier(requested_app, requested_platform)
+ _LogMessageForRequest(request_identifier,
+ "Error in redirecting to current app location",
+ log=logging.error,
+ exc_info=getattr(error, "__traceback__", True))
+ return _HttpInternalServerErrorResponse()
+
+ return self.RedirectToCurrentAppLocation(requested_platform,
+ requested_app).Catch(
+ OnRedirectToAppRejected)
+
+ def RedirectToCurrentAppLocation(self, requested_platform, requested_app):
+ # Construct a URLRequest to fetch the app location file...
+ app_location_request = url_loader_mojom.UrlRequest()
+ mojo_services_url = "https://storage.googleapis.com/mojo/services"
+ app_name, _ = os.path.splitext(requested_app)
+ app_location_request.url = "%s/%s/%s_location" % (mojo_services_url,
+ requested_platform,
+ app_name)
+ app_location_request.auto_follow_redirects = True
+
+ # ...and start a URLLoader to fetch it.
+ url_loader_proxy, request = url_loader_mojom.UrlLoader.manager.NewRequest()
+ self.network_service.CreateUrlLoader(request)
+
+ return self.ProcessAppLocationResponse(
+ url_loader_proxy.Start(app_location_request),
+ _RequestIdentifier(requested_app, requested_platform),
+ url_loader_proxy)
+
+ @promise.async
+ def ProcessAppLocationResponse(self, app_location_response,
+ request_identifier, url_loader_proxy):
+ error_message = None
+ if app_location_response.error:
+ error_message = "Network error from app location fetch: %d" % (
+ app_location_response.error)
+ elif app_location_response.status_code != 200:
+ error_message = "Unexpected http status from app location fetch: %s" % (
+ app_location_response.status_code)
+
+ if error_message:
+ _LogMessageForRequest(request_identifier, error_message,
+ log=logging.error)
+ return _HttpNotFoundResponse()
+
+ return self.ProcessAppLocationResponseBody(
+ data_pipe_utils.CopyFromDataPipe(app_location_response.body,
+ 30 * 10**6),
+ request_identifier)
+
+ @promise.async
+ def ProcessAppLocationResponseBody(self, app_location_body,
+ request_identifier):
+ app_location = app_location_body.decode("utf-8")
+ _LogMessageForRequest(request_identifier,
+ "Redirecting to %s" % app_location)
+ return _HttpRedirectResponse(app_location)
+
+
+class MojoUrlRedirectorApp(application_delegate.ApplicationDelegate):
+ def Initialize(self, shell, application):
+ server_address = net_address_mojom.NetAddress()
+ server_address.family = net_address_mojom.NetAddressFamily.IPV4
+ server_address.ipv4 = net_address_mojom.NetAddressIPv4()
+ server_address.ipv4.addr = [0, 0, 0, 0]
+ server_address.ipv4.port = 80
+
+ # Parse args if given.
+ if application.args:
+ assert len(application.args) == 2
+ server_address_arg = application.args[1]
+ server_address_str, server_port_str = server_address_arg.split(":")
+ server_address.ipv4.addr = [int(n) for n in server_address_str.split(".")]
+ server_address.ipv4.port = int(server_port_str)
+
+ # Connect to HttpServer.
+ http_server_factory = application.ConnectToService("mojo:http_server",
+ http_server_factory_mojom.HttpServerFactory)
+ self.http_server, request = (
+ http_server_mojom.HttpServer.manager.NewRequest())
+ http_server_factory.CreateHttpServer(request, server_address)
+
+ # Connect to NetworkService.
+ self.network_service = application.ConnectToService("mojo:network_service",
+ network_service_mojom.NetworkService)
+
+ # Construct a MojoUrlRedirector and add that as a handler to the server.
+ self.app_request_handler = MojoUrlRedirector(self.network_service)
+ self.http_server.SetHandler("/.*", self.app_request_handler)
+
+
+def MojoMain(app_request_handle):
+ application_runner.RunMojoApplication(MojoUrlRedirectorApp(),
+ app_request_handle)