blob: f50e0b4001c19d3b728df0a7ca6f9a7bfb63c602 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2021 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.
"""A webserver for devtools frontend assets."""
import flask
import gzip
import markdown
import mimetypes
import startup_checks
from config import MAX_CACHE_AGE
from files import get_file_from_revision, get_file_from_version
# Custom flask server to execute startup checks before serving files
class StartupCheckFlask(flask.Flask):
def run(self, *args, **kwargs):
startup_checks.run()
return super().run(*args, **kwargs)
def create_file_response(filename: str, content: bytes) -> flask.Response:
# Content Type
content_type = mimetypes.guess_type(filename)[0] or "text/plain"
# Generate response
response = flask.Response(content)
response.headers["Content-Type"] = content_type
response.headers["Cache-Control"] = f"public, max-age={MAX_CACHE_AGE}"
response.headers['Access-Control-Allow-Origin'] = '*'
# Gzip the response if supported by the client
encodings = flask.request.headers.get("accept-encoding")
if encodings and "gzip" in encodings.replace(" ", "").split(","):
new_data = gzip.compress(response.data)
response.set_data(new_data)
response.headers["Content-Encoding"] = "gzip"
return response
# If `entrypoint` is not defined in app.yaml, App Engine will look for an app
# called `app` in `main.py`.
app = StartupCheckFlask(__name__)
@app.route("/")
def index():
with open("templates/markdown.html") as pre_content_file, \
open("README.md") as readme_file:
return pre_content_file.read() + markdown.markdown(readme_file.read())
@app.route("/serve_rev/@<string:revision>/<path:filename>")
def serve_rev(revision: str, filename: str) -> flask.Response:
"""Return the devtools frontend file for a given revision.
1. Convert the revision (=commit hash) to the first Chromium version which
includes this commit.
2. Retrieve the requested filename for this version.
Returns:
200: Existing revision, remote archive and file.
404: Invalid or non-existing revisions.
404: Non-existing files within the zip-archive.
500: Unhandled code exceptions.
"""
# Retrieve file for revision
content = get_file_from_revision(revision, filename)
if content is None:
return flask.abort(404)
return create_file_response(filename, content)
@app.route("/serve_file/@<string:revision>/<path:filename>")
def serve_file(revision: str, filename: str) -> flask.Response:
# We keep this endpoint for legacy reasons
return serve_rev(revision, filename)
@app.route("/static/<string:version>/<path:filename>")
def serve_static(version: str, filename: str) -> flask.Response:
"""Return the devtools frontend file for a given version from the legacy
bucket.
Returns:
200: Existing revision, remote archive and file.
404: Invalid or non-existing revisions.
404: Non-existing files within the zip-archive.
500: Unhandled code exceptions.
"""
# Retrieve file for revision
content = get_file_from_version(version, filename)
if content is None:
return flask.abort(404)
return create_file_response(filename, content)
if __name__ == "__main__":
# This is used when running locally only. When deploying to Google App
# Engine, a webserver process such as Gunicorn will serve the app. You
# can configure startup instructions by adding `entrypoint` to app.yaml.
app.run(host="0.0.0.0", port=8080, debug=True)