Source code for admit.util.AdmitHTTP

"""
   **AdmitHTTP** --- Data browser services module.
   -----------------------------------------------

   This module defines the classes needed to serve ADMIT data
   to the data browser on a localhost port.   These classes are
   subclasses of those provided in the Python library BaseHTTPServer
   module.
"""

import os
import posixpath
import urllib
import sys
import json
import socket
import BaseHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler

__all__ = ["AdmitHTTPServer", "AdmitHTTPRequestHandler"]

[docs]class AdmitHTTPServer(BaseHTTPServer.HTTPServer): """This class is identical to BaseHTTPServer.HTTPServer except 1) It defines a fixed document root instead of using the current working directory. 2) The handler class is fixed to be AdmitHTTPRequestHandler. This is accomplished by overriding the constructor and BaseServer.finish_request(). Parameters ---------- server_address : tuple The address on which the server is listening. This is a tuple containing a string giving the address, and an integer port number: ('127.0.0.1', 80), for example. docroot : string The document root directory for the local data web server. Requests will be served out of this directory. ** todo: possibly make this always localhost** postcallback : function The external function to call when handling a POST. Attributes ---------- _documentRoot : string The document root directory for the web server. Requests will be served out of this directory. """ def __init__(self, server_address, docroot, postcallback): self._documentRoot=docroot self._postCallbackFn = postcallback BaseHTTPServer.HTTPServer.__init__(self,server_address, AdmitHTTPRequestHandler) self.timeout = None
[docs] def finish_request(self, request, client_address): """Finish one http request by instantiating RequestHandlerClass. Construction of the handler class calls the methods to process the request. *overrides:* `BaseServer.finish_request <https://docs.python.org/2/library/socketserver.html#SocketServer.BaseServer.finish_request>`_ """ #print "server %s:%d finishing request with handler docroot: %s" % (self.server_address[0], self.server_address[1], self._documentRoot ) try: self.RequestHandlerClass(request, client_address, docroot=self._documentRoot,postcallback=self._postCallbackFn) except: self.handle_error(request,client_address)
[docs]class AdmitHTTPRequestHandler(SimpleHTTPRequestHandler): """And HTTP request handler that allows a fixed document root. Python's SimpleHTTPRequestHandler always uses the current working directory which is stored globally. Therefore, SimpleHTTPRequestHandler cannot be used to spawn off http servers in separate threads because they will overwrite each other's working directories. """ def __init__(self,request,client_address,docroot,postcallback): # Note: documentRoot must be set BEFORE instantiation of SimpleHTTPServer # because the __init__ in the base class calls the methods that # actually handle the request, including AdmitHTTPServer.finish_request() # above. self._documentRoot = docroot # for now don't log anything; it's too verbose self._logging = False self._postCallbackFn = postcallback try: SimpleHTTPRequestHandler.__init__(self, request=request, client_address=client_address, server=None) except: if self.is_broken_pipe_error(): pass else: raise
[docs] def is_broken_pipe_error(self): exc_type, exc_value = sys.exc_info()[:2] return issubclass(exc_type, socket.error) and exc_value.args[0] == 32
[docs] def handle_error(self, request, client_address): if self.is_broken_pipe_error(): print "- Broken pipe from %s\n" % str(client_address) return
"""Override base class log message method to bypass logging."""
[docs] def log_message(self,format,*args): if self._logging: SimpleHTTPRequestHandler.log_message(self, format, *args)
[docs] def translate_path(self, path): """Translate a /-separated PATH to the local filename syntax. Components that mean special things to the local file system (e.g. drive or directory names) are ignored. This method is identical to SimpleHTTPRequestHandler.translate_path() except it uses the locally stored document root instead of the current working directory. """ # abandon query parameters path = path.split('?',1)[0] path = path.split('#',1)[0] path = posixpath.normpath(urllib.unquote(path)) words = path.split('/') words = filter(None, words) # Always use document root! path = self._documentRoot for word in words: drive, word = os.path.splitdrive(word) head, word = os.path.split(word) if word in (os.curdir, os.pardir): continue path = os.path.join(path, word) return path
[docs] def do_OPTIONS(self): self.send_response(200, "ok") self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, HEAD') self.send_header("Access-Control-Allow-Headers", "X-Requested-With", "Content-Type")
[docs] def do_POST(self): length = int(self.headers['Content-Length']) self.data_string = self.rfile.read(length) #print "DATASTRING=%s" % self.data_string if self.data_string[0] != "{": # Firefox sends extra GETs disguised as POSTs with # garbage data. Need to deal with this. # Disabling network prefetch in about:config does not fix it. #print "this looks like a bogus GET" #self.send_response(200) # THE ISSUE HERE IS WHAT RESPONSE TO SEND THAT DOES # NOT BREAK THE BROWSER VIEW. 200 will refresh # a blank page. We really just want the button to # come unstuck and no refresh of html. # The correct answer may be do nothing. return try: data = json.loads(self.data_string) #print "GOT JSON: %d \n %s" % (len(data), data) #command = data["command"] #print "command = %s" % command print "User agent: %s " % self.headers['user-agent'] data["firefox"] = self.isFirefox() self._postCallbackFn(data) self.send_response(200) except Exception, e: print "Problem with server/browser connection: ", e # This is almost certainly the firefox bug, try sending 200 # instead of 400 #print "sending 200 anyway" self.send_response(200) self.send_header('Content-type','text/html') self.end_headers() return
[docs] def isFirefox(self): """Attempt to detect the Firefox browser. This is because Firefox does funny things and we have to workaround it. Return True if the user-agent header contains the string 'firefox' (case insensitive) """ return self.headers['user-agent'].lower().find('firefox') != -1