1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5# This module contains code for running an HTTP server to view build info. 6 7from __future__ import absolute_import, unicode_literals 8 9import BaseHTTPServer 10import json 11import os 12 13import requests 14 15 16class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): 17 def do_GET(self): 18 s = self.server.wrapper 19 p = self.path 20 21 if p == '/list': 22 self.send_response(200) 23 self.send_header('Content-Type', 'application/json; charset=utf-8') 24 self.end_headers() 25 26 keys = sorted(s.json_files.keys()) 27 json.dump({'files': keys}, self.wfile) 28 return 29 30 if p.startswith('/resources/'): 31 key = p[len('/resources/'):] 32 33 if key not in s.json_files: 34 self.send_error(404) 35 return 36 37 self.send_response(200) 38 self.send_header('Content-Type', 'application/json; charset=utf-8') 39 self.end_headers() 40 41 self.wfile.write(s.json_files[key]) 42 return 43 44 if p == '/': 45 p = '/index.html' 46 47 self.serve_docroot(s.doc_root, p[1:]) 48 49 def do_POST(self): 50 if self.path == '/shutdown': 51 self.server.wrapper.do_shutdown = True 52 self.send_response(200) 53 return 54 55 self.send_error(404) 56 57 def serve_docroot(self, root, path): 58 local_path = os.path.normpath(os.path.join(root, path)) 59 60 # Cheap security. This doesn't resolve symlinks, etc. But, it should be 61 # acceptable since this server only runs locally. 62 if not local_path.startswith(root): 63 self.send_error(404) 64 65 if not os.path.exists(local_path): 66 self.send_error(404) 67 return 68 69 if os.path.isdir(local_path): 70 self.send_error(500) 71 return 72 73 self.send_response(200) 74 ct = 'text/plain' 75 if path.endswith('.html'): 76 ct = 'text/html' 77 78 self.send_header('Content-Type', ct) 79 self.end_headers() 80 81 with open(local_path, 'rb') as fh: 82 self.wfile.write(fh.read()) 83 84 85class BuildViewerServer(object): 86 def __init__(self, address='localhost', port=0): 87 # TODO use pkg_resources to obtain HTML resources. 88 pkg_dir = os.path.dirname(os.path.abspath(__file__)) 89 doc_root = os.path.join(pkg_dir, 'resources', 'html-build-viewer') 90 assert os.path.isdir(doc_root) 91 92 self.doc_root = doc_root 93 self.json_files = {} 94 95 self.server = BaseHTTPServer.HTTPServer((address, port), HTTPHandler) 96 self.server.wrapper = self 97 self.do_shutdown = False 98 99 @property 100 def url(self): 101 hostname, port = self.server.server_address 102 return 'http://%s:%d/' % (hostname, port) 103 104 def add_resource_json_file(self, key, path): 105 """Register a resource JSON file with the server. 106 107 The file will be made available under the name/key specified.""" 108 with open(path, 'rb') as fh: 109 self.json_files[key] = fh.read() 110 111 def add_resource_json_url(self, key, url): 112 """Register a resource JSON file at a URL.""" 113 r = requests.get(url) 114 if r.status_code != 200: 115 raise Exception('Non-200 HTTP response code') 116 self.json_files[key] = r.text 117 118 def run(self): 119 while not self.do_shutdown: 120 self.server.handle_request() 121