Browse Source

Nifty Python wrappers for HTTP requests

M. Ian Graham 18 years ago
parent
commit
af730e1c89
2 changed files with 173 additions and 0 deletions
  1. 173 0
      direct/src/http/WebRequest.py
  2. 0 0
      direct/src/http/__init__.py

+ 173 - 0
direct/src/http/WebRequest.py

@@ -0,0 +1,173 @@
+import direct
+from libdirect import HttpRequest
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.task.TaskManagerGlobal import taskMgr
+
+if __debug__:
+    notify = directNotify.newCategory('WebRequestDispatcher')
+
+class WebRequest(object):
+    """
+    Pointer to a single web request (maps to an open HTTP socket).
+    An instance of this class maps to a single client waiting for a response.
+
+    connection is an instance of libdirect.HttpRequest
+    """
+    def __init__(self,connection):
+        self.connection = connection
+
+    def getURI(self):
+        return self.connection.GetRequestURL()
+
+    def getRequestType(self):
+        return self.connection.GetRequestType()
+
+    def dictFromGET(self):
+        result = {}
+        for pair in self.connection.GetRequestOptionString().split('&'):
+            arg = pair.split('=',1)
+            if len(arg) > 1:
+                result[arg[0]] = arg[1]
+        return result
+
+    def respondHTTP(self,status,body):
+        status = str(status)
+        msg = "HTTP/1.0 %s\r\nContent-Type: text/html\r\n\r\n%s" % (status,body)
+        self.connection.SendThisResponse(msg)
+
+    def respond(self,body):
+        self.respondHTTP("200 OK",body)
+
+    def timeout(self):
+        resp = "<html><body>Error 504: Request timed out</body></html>\r\n"
+        self.respondHTTP("504 Gateway Timeout",resp)
+
+
+# --------------------------------------------------------------------------------
+    
+
+class WebRequestDispatcher(object):
+    """
+    Request dispatcher for HTTP requests.
+    Contains registration and dispatching functionality.
+
+    Single-state class--multiple instances share all data.
+    This is because we're wrapping a singleton webserver.
+
+    How to use:
+    
+    w = WebRequestDispatcher()
+    w.listenOnPort(8888)
+    def test(replyTo,**kw):
+        print 'test got called with these options: %s' % str(kw)
+        replyTo.respond('<html><body>Thank you for the yummy arguments: %s' % str(kw))
+    w.registerGETHandler('test',test)
+    while 1:
+        w.poll()
+
+    Browse to http://localhost:8888/test?foo=bar and see the result!
+    """
+
+    _shared_state = {}
+
+    listenPort = None
+
+    uriToHandler = {}
+
+    requestTimeout = 10.0
+    
+    if __debug__:
+        notify = notify
+
+    def __new__(self, *a, **kw):
+        obj = object.__new__(self, *a, **kw)
+        obj.__dict__ = self._shared_state
+        return obj
+
+    def __init__(self):
+        pass
+
+    def listenOnPort(self,listenPort):
+        """
+        Start the web server listening if it isn't already.
+        Singleton server, so ignore multiple listen requests.
+        """
+        if self.listenPort is None:
+            HttpRequest.HttpManagerInitialize(listenPort)
+            self.notify.info("Web server is listening on port %d" % listenPort)
+        else:
+            self.notify.warning("Web server is already listening on port %d.  Ignoring request to listen on port %d." % (self.listenPort,listenPort))
+
+    def invalidURI(self,replyTo,**kw):
+        resp = "<html><body>Error 404</body></html>\r\n"
+        replyTo.respondHTTP("404 Not Found",resp)
+
+    def handleGET(self,req):
+        """
+        Parse and dispatch a single GET request.
+        Expects to receive a WebRequest object.
+        """
+        assert req.getRequestType() == "GET"
+        uri = req.getURI()
+        args = req.dictFromGET()
+        callable,returnsResponse = self.uriToHandler.get(uri,[self.invalidURI,False])
+        if returnsResponse:
+            result = apply(callable,(),args)
+            req.respond(result)
+        else:
+            args["replyTo"] = req
+            apply(callable,(),args)
+
+
+    def poll(self):
+        """
+        Pump the web server, handle any incoming requests.
+        This function should be called regularly, about 2-4
+        calls/sec for current applications is a good number.
+        """
+        request = HttpRequest.HttpManagerGetARequest()
+        while request is not None:
+            wreq = WebRequest(request)
+            if wreq.getRequestType() == "GET":
+                self.handleGET(wreq)
+            else:
+                self.notify.warning("Ignoring a non-GET request: %s" % request.GetRawRequest())
+
+            request = HttpRequest.HttpManagerGetARequest()
+
+
+    def registerGETHandler(self,uri,handler,returnsResponse=False):
+        """
+        Call this function to register a handler function to
+        be called in response to a query to the given URI.
+
+        GET options are translated into **kw arguments.
+        Handler function should accept **kw in order to handle
+        arbitrary queries.
+
+        If returnsResponse is False, the request is left open after
+        handler returns--handler or tasks it creates are responsible
+        for fulfilling the query now or in the future.  Argument
+        replyTo (a WebRequest) is guaranteed to be passed to the
+        handler, and replyTo.respond must be called with an HTML
+        response string to fulfill the query and close the socket.
+
+        If returnsResponse is True, WebRequestDispatcher expects the
+        handler to return its response string, and we will route the
+        response and close the socket ourselves.  No replyTo argument
+        is provided to the handler in this case.
+        """
+        if uri[0] != "/":
+            uri = "/" + uri
+
+        if self.uriToHandler.get(uri,None) is None:
+            self.notify.info("Registered handler %s for URI %s." % (handler,uri))
+            self.uriToHandler[uri] = [handler,returnsResponse]
+        else:
+            self.notify.warning("Attempting to register a duplicate handler for URI %s.  Ignoring." % uri)
+
+    def unregisterGETHandler(self,uri):
+        if uri[0] != "/":
+            uri = "/" + uri
+        self.uriToHandler.pop(uri,None)
+        

+ 0 - 0
direct/src/http/__init__.py