|
|
@@ -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)
|
|
|
+
|