xref: /aosp_15_r20/external/pigweed/pw_console/py/pw_console/console_log_server.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Console HTTP Log Server functions."""
15
16import logging
17from pathlib import Path
18import mimetypes
19import http.server
20from typing import Callable
21
22_LOG = logging.getLogger(__package__)
23
24
25def _start_serving(port: int, handler: Callable) -> bool:
26    try:
27        with http.server.HTTPServer(('', port), handler) as httpd:
28            _LOG.debug('Serving on port %i', port)
29            httpd.serve_forever()
30        return True
31    except OSError:
32        _LOG.debug('Port %i failed.', port)
33        return False
34
35
36def pw_console_http_server(starting_port: int, handler: Callable) -> None:
37    for i in range(100):
38        if _start_serving(starting_port + i, handler):
39            break
40
41
42class ConsoleLogHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
43    """Request handler that serves files from pw_console.html package data."""
44
45    def __init__(self, html_files: dict[str, str], *args, **kwargs):
46        self.html_files = html_files
47        super().__init__(*args, **kwargs)
48
49    def do_GET(self):  # pylint: disable=invalid-name
50        _LOG.debug(
51            '%s: %s',
52            self.client_address[0],
53            self.raw_requestline.decode('utf-8').strip(),
54        )
55
56        path = self.path
57        if path == '/':
58            path = '/index.html'
59
60        if path not in self.html_files:
61            self.send_error(http.server.HTTPStatus.NOT_FOUND, 'File not found')
62            return
63
64        content: str = self.html_files[path].encode('utf-8')
65        content_type = 'application/octet-stream'
66        mime_guess, _ = mimetypes.guess_type(Path(path).name)
67        if mime_guess:
68            content_type = mime_guess
69
70        self.send_response(http.server.HTTPStatus.OK)
71        self.send_header('Content-type', content_type)
72        self.send_header('Content-Length', str(len(content)))
73        self.send_header('Last-Modified', self.date_time_string())
74        self.end_headers()
75        self.wfile.write(content)
76
77    def log_message(self, *args, **kwargs):
78        pass
79