python/mozbuild/mozbuild/html_build_viewer.py
author Jonathan Watt <jwatt@jwatt.org>
Thu, 26 May 2022 12:42:16 +0000
changeset 618970 67475e519671d620a79ab66b131beccdb07234b5
parent 554551 994ae8e4833c90447d91f0e26a718573cff5a514
permissions -rw-r--r--
Bug 1770536 - Remove the print.show_print_progress pref. r=mstriemer Differential Revision: https://phabricator.services.mozilla.com/D146969

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This module contains code for running an HTTP server to view build info.
import http.server
import json
import os

import requests


class HTTPHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        s = self.server.wrapper
        p = self.path

        if p == "/build_resources.json":
            self.send_response(200)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.end_headers()

            keys = sorted(s.json_files.keys())
            s = json.dumps({"files": ["resources/%s" % k for k in keys]})
            self.wfile.write(s.encode("utf-8"))
            return

        if p.startswith("/resources/"):
            key = p[len("/resources/") :]

            if key not in s.json_files:
                self.send_error(404)
                return

            self.send_response(200)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.end_headers()

            self.wfile.write(s.json_files[key])
            return

        if p == "/":
            p = "/build_resources.html"

        self.serve_docroot(s.doc_root, p[1:])

    def do_POST(self):
        if self.path == "/shutdown":
            self.server.wrapper.do_shutdown = True
            self.send_response(200)
            return

        self.send_error(404)

    def serve_docroot(self, root, path):
        local_path = os.path.normpath(os.path.join(root, path))

        # Cheap security. This doesn't resolve symlinks, etc. But, it should be
        # acceptable since this server only runs locally.
        if not local_path.startswith(root):
            self.send_error(404)

        if not os.path.exists(local_path):
            self.send_error(404)
            return

        if os.path.isdir(local_path):
            self.send_error(500)
            return

        self.send_response(200)
        ct = "text/plain"
        if path.endswith(".html"):
            ct = "text/html"

        self.send_header("Content-Type", ct)
        self.end_headers()

        with open(local_path, "rb") as fh:
            self.wfile.write(fh.read())


class BuildViewerServer(object):
    def __init__(self, address="localhost", port=0):
        # TODO use pkg_resources to obtain HTML resources.
        pkg_dir = os.path.dirname(os.path.abspath(__file__))
        doc_root = os.path.join(pkg_dir, "resources", "html-build-viewer")
        assert os.path.isdir(doc_root)

        self.doc_root = doc_root
        self.json_files = {}

        self.server = http.server.HTTPServer((address, port), HTTPHandler)
        self.server.wrapper = self
        self.do_shutdown = False

    @property
    def url(self):
        hostname, port = self.server.server_address
        return "http://%s:%d/" % (hostname, port)

    def add_resource_json_file(self, key, path):
        """Register a resource JSON file with the server.

        The file will be made available under the name/key specified."""
        with open(path, "rb") as fh:
            self.json_files[key] = fh.read()

    def add_resource_json_url(self, key, url):
        """Register a resource JSON file at a URL."""
        r = requests.get(url)
        if r.status_code != 200:
            raise Exception("Non-200 HTTP response code")
        self.json_files[key] = r.text

    def run(self):
        while not self.do_shutdown:
            self.server.handle_request()