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