Home | History | Annotate | Download | only in servers
      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