1 # Copyright (c) 2011 Google Inc. All rights reserved. 2 # 3 # Redistribution and use in source and binary forms, with or without 4 # modification, are permitted provided that the following conditions are 5 # met: 6 # 7 # * Redistributions of source code must retain the above copyright 8 # notice, this list of conditions and the following disclaimer. 9 # * Redistributions in binary form must reproduce the above 10 # copyright notice, this list of conditions and the following disclaimer 11 # in the documentation and/or other materials provided with the 12 # distribution. 13 # * Neither the name of Google Inc. nor the names of its 14 # contributors may be used to endorse or promote products derived from 15 # this software without specific prior written permission. 16 # 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 import BaseHTTPServer 30 31 import cgi 32 import codecs 33 import datetime 34 import fnmatch 35 import json 36 import mimetypes 37 import os.path 38 import shutil 39 import threading 40 import time 41 import urlparse 42 import wsgiref.handlers 43 import BaseHTTPServer 44 45 46 class ReflectionHandler(BaseHTTPServer.BaseHTTPRequestHandler): 47 STATIC_FILE_EXTENSIONS = ['.js', '.css', '.html'] 48 # Subclasses should override. 49 STATIC_FILE_DIRECTORY = None 50 51 # Setting this flag to True causes the server to send 52 # Access-Control-Allow-Origin: * 53 # with every response. 54 allow_cross_origin_requests = False 55 56 def do_GET(self): 57 self._handle_request() 58 59 def do_POST(self): 60 self._handle_request() 61 62 def do_HEAD(self): 63 self._handle_request() 64 65 def read_entity_body(self): 66 length = int(self.headers.getheader('content-length')) 67 return self.rfile.read(length) 68 69 def _read_entity_body_as_json(self): 70 return json.loads(self.read_entity_body()) 71 72 def _handle_request(self): 73 if "?" in self.path: 74 path, query_string = self.path.split("?", 1) 75 self.query = cgi.parse_qs(query_string) 76 else: 77 path = self.path 78 self.query = {} 79 function_or_file_name = path[1:] or "index.html" 80 81 _, extension = os.path.splitext(function_or_file_name) 82 if extension in self.STATIC_FILE_EXTENSIONS: 83 self._serve_static_file(function_or_file_name) 84 return 85 86 function_name = function_or_file_name.replace(".", "_") 87 if not hasattr(self, function_name): 88 self.send_error(404, "Unknown function %s" % function_name) 89 return 90 if function_name[0] == "_": 91 self.send_error(401, "Not allowed to invoke private or protected methods") 92 return 93 function = getattr(self, function_name) 94 function() 95 96 def _serve_static_file(self, static_path): 97 self._serve_file(os.path.join(self.STATIC_FILE_DIRECTORY, static_path)) 98 99 def quitquitquit(self): 100 self._serve_text("Server quit.\n") 101 # Shutdown has to happen on another thread from the server's thread, 102 # otherwise there's a deadlock 103 threading.Thread(target=lambda: self.server.shutdown()).start() 104 105 def _send_access_control_header(self): 106 if self.allow_cross_origin_requests: 107 self.send_header('Access-Control-Allow-Origin', '*') 108 109 def _serve_text(self, text): 110 self.send_response(200) 111 self._send_access_control_header() 112 self.send_header("Content-type", "text/plain") 113 self.end_headers() 114 self.wfile.write(text) 115 116 def _serve_json(self, json_object): 117 self.send_response(200) 118 self._send_access_control_header() 119 self.send_header('Content-type', 'application/json') 120 self.end_headers() 121 json.dump(json_object, self.wfile) 122 123 def _serve_file(self, file_path, cacheable_seconds=0, headers_only=False): 124 if not os.path.exists(file_path): 125 self.send_error(404, "File not found") 126 return 127 with codecs.open(file_path, "rb") as static_file: 128 self.send_response(200) 129 self._send_access_control_header() 130 self.send_header("Content-Length", os.path.getsize(file_path)) 131 mime_type, encoding = mimetypes.guess_type(file_path) 132 if mime_type: 133 self.send_header("Content-type", mime_type) 134 135 if cacheable_seconds: 136 expires_time = (datetime.datetime.now() + 137 datetime.timedelta(0, cacheable_seconds)) 138 expires_formatted = wsgiref.handlers.format_date_time( 139 time.mktime(expires_time.timetuple())) 140 self.send_header("Expires", expires_formatted) 141 self.end_headers() 142 143 if not headers_only: 144 shutil.copyfileobj(static_file, self.wfile) 145 146 def _serve_xml(self, xml): 147 self.send_response(200) 148 self._send_access_control_header() 149 self.send_header("Content-type", "text/xml") 150 self.end_headers() 151 xml = xml.encode('utf-8') 152 self.wfile.write(xml) 153