August'24: Kamaelia is in maintenance mode and will recieve periodic updates, about twice a year, primarily targeted around Python 3 and ecosystem compatibility. PRs are always welcome. Latest Release: 1.14.32 (2024/3/24)
The API for HTTPServer is likely to change/improve for the better - stay tuned!
The HTTP Server included in Kamaelia 0.5 / Megabundle 1.4 is a useful & powerful mechanism, but lacks an example. Hence this page!
Some initial points:
So, this therefore means in order to use the HTTP Server as is:
Also, there's one final thing: it's nice, for various reasons, to
change the socket options for the server, so we do that in how we
configure SimpleServer.
Code:
#!/usr/bin/python
# Import socket to get at constants for socketOptions
import socket
# Import the server framework, the HTTP protocol handling, the minimal request handler, and error handlers
from Kamaelia.Chassis.ConnectedServer import SimpleServer from Kamaelia.Protocol.HTTP.HTTPServer import HTTPServer from Kamaelia.Protocol.HTTP.Handlers.Minimal import Minimal import Kamaelia.Protocol.HTTP.ErrorPages as ErrorPages
# Our configuration
homedirectory = "/srv/www/htdocs" indexfilename = "index.html"
# This allows for configuring the request handlers in a nicer way. This is candidate
# for merging into the mainline code. Effectively this is a factory that creates functions
# capable of choosing which request handler to use.
def requestHandlers(URLHandlers): def createRequestHandler(request): if request.get("bad"): return ErrorPages.websiteErrorPage(400, request.get("errormsg","")) else: for (prefix, handler) in URLHandlers: if request["raw-uri"][:len(prefix)] == prefix: request["uri-prefix-trigger"] = prefix request["uri-suffix"] = request["raw-uri"][len(prefix):] return handler(request) return ErrorPages.websiteErrorPage(404, "No resource handlers could be found for the requested URL") return createRequestHandler
# This factory allows us to configure the minimal request handler.
def servePage(request): return Minimal(request=request, homedirectory=homedirectory, indexfilename=indexfilename)
# A factory to create configured HTTPServer components - ie HTTP Protocol handling components
def HTTPProtocol(): return HTTPServer(requestHandlers([ ["/", servePage ], ]))
# Finally we create the actual server and run it.
SimpleServer(protocol=HTTPProtocol, port=8082, socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ).run()
OK, so that's all well and good, and shows how to run the Kamaelia
webserver as, well, a webserver, but how do we integrate with other
components? Well this requires you to start think about requests and
responses - specifically you need to learn how to write a request
handler!
Fortunately this is relatively simple - you do this:
Specifically there's three key kinds of messages you'll probably
send:
So, given all that, this is what a handler actually looks like:
class HelloHandler(Axon.Component.component): def __init__(self, request): super(HelloHandler, self).__init__() self.request = request def main(self): resource = { "type" : "text/html", "statuscode" : "200", } self.send(resource, "outbox"); yield 1 page = { "data" : "<html><body><h1>Hello World</h1><P>Woo!!</body></html>", } self.send(page, "outbox"); yield 1 self.send(Axon.Ipc.producerFinished(self), "signal") yield 1
So, that's your handler. To integrate this into our example from
above:
#!/usr/bin/python
# Import socket to get at constants for socketOptions
import socket # We need to import Axon - Kamaelia's core component system - to write Kamaelia components! import Axon
# Import the server framework, the HTTP protocol handling, the minimal request handler, and error handlers
from Kamaelia.Chassis.ConnectedServer import SimpleServer from Kamaelia.Protocol.HTTP.HTTPServer import HTTPServer from Kamaelia.Protocol.HTTP.Handlers.Minimal import Minimal import Kamaelia.Protocol.HTTP.ErrorPages as ErrorPages
# Our configuration
homedirectory = "/srv/www/htdocs" indexfilename = "index.html"
# This allows for configuring the request handlers in a nicer way. This is candidate
# for merging into the mainline code. Effectively this is a factory that creates functions
# capable of choosing which request handler to use.
def requestHandlers(URLHandlers): def createRequestHandler(request): if request.get("bad"): return ErrorPages.websiteErrorPage(400, request.get("errormsg","")) else: for (prefix, handler) in URLHandlers: if request["raw-uri"][:len(prefix)] == prefix: request["uri-prefix-trigger"] = prefix request["uri-suffix"] = request["raw-uri"][len(prefix):] return handler(request) return ErrorPages.websiteErrorPage(404, "No resource handlers could be found for the requested URL") return createRequestHandler class HelloHandler(Axon.Component.component): def __init__(self, request): super(HelloHandler, self).__init__() self.request = request def main(self): resource = { "type" : "text/html", "statuscode" : "200", } self.send(resource, "outbox"); yield 1 page = { "data" : "<html><body><h1>Hello World</h1><P>Woo!!</body></html>", } self.send(page, "outbox"); yield 1 self.send(Axon.Ipc.producerFinished(self), "signal") yield 1 def servePage(request): return Minimal(request=request, homedirectory=homedirectory, indexfilename=indexfilename)
# A factory to create configured HTTPServer components - ie HTTP Protocol handling components
def HTTPProtocol(): return HTTPServer(requestHandlers([ ["/hello", HelloHandler ], ["/", servePage ], ]))
# Finally we create the actual server and run it.
SimpleServer(protocol=HTTPProtocol, port=8082, socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ).run()
As you can see we added in the code as expected, and added in the
handler into the method at the end.
This turns out to be quite simple if you have 2 components which can
be reused.
These are both quite simple to write.
One takes an argument (eg a request dictionary) and sends it out
its outbox "outbox" (this enables it to start off a
Pipeline)
class Cat(Axon.Component.component): def __init__(self, *args): super(Cat, self).__init__() self.args = args def main(self): self.send(self.args, "outbox") self.send(Axon.Ipc.producerFinished(self), "signal") yield 1
One that takes the first value it sees, and stuffs it into an
HTML response and sends that out its outbox.
class ExampleWrapper(Axon.Component.component): def main(self): # Tell the browser the type of data we're sending! resource = { "type" : "text/html", "statuscode" : "200", } self.send(resource, "outbox"); yield 1 # Send the header header = { "data" : "<html><body>" } self.send(header, "outbox"); yield 1 # Wait for it.... while not self.dataReady("inbox"): self.pause() yield 1 # Send the data we recieve as the page body while self.dataReady("inbox"): pageData = { "data" : str(self.recv("inbox")) } self.send(pageData, "outbox"); yield 1 # send a footer footer = { "data" : "</body></html>" } self.send(footer, "outbox"); yield 1 # and shutdown nicely self.send(Axon.Ipc.producerFinished(self), "signal") yield 1
Given these two components, which can be reused to your hearts
content, we can produce a simple "Echo" handler as follows:
from Kamaelia.Chassis.Pipeline import Pipeline def EchoHandler(request): return Pipeline ( Cat(request), ExampleWrapper() )
Which is actually quite sweet :-)
Putting this into our example, and how it modifies our server.…
#!/usr/bin/python
# Import socket to get at constants for socketOptions
import socket # We need to import Axon - Kamaelia's core component system - to write Kamaelia components! import Axon
# Import the server framework, the HTTP protocol handling, the minimal request handler, and error handlers
from Kamaelia.Chassis.ConnectedServer import SimpleServer from Kamaelia.Protocol.HTTP.HTTPServer import HTTPServer from Kamaelia.Protocol.HTTP.Handlers.Minimal import Minimal import Kamaelia.Protocol.HTTP.ErrorPages as ErrorPages from Kamaelia.Chassis.Pipeline import Pipeline
# Our configuration
homedirectory = "/srv/www/htdocs" indexfilename = "index.html"
# This allows for configuring the request handlers in a nicer way. This is candidate
# for merging into the mainline code. Effectively this is a factory that creates functions
# capable of choosing which request handler to use.
def requestHandlers(URLHandlers): def createRequestHandler(request): if request.get("bad"): return ErrorPages.websiteErrorPage(400, request.get("errormsg","")) else: for (prefix, handler) in URLHandlers: if request["raw-uri"][:len(prefix)] == prefix: request["uri-prefix-trigger"] = prefix request["uri-suffix"] = request["raw-uri"][len(prefix):] return handler(request) return ErrorPages.websiteErrorPage(404, "No resource handlers could be found for the requested URL") return createRequestHandler class HelloHandler(Axon.Component.component): def __init__(self, request): super(HelloHandler, self).__init__() self.request = request def main(self): resource = { "type" : "text/html", "statuscode" : "200", } self.send(resource, "outbox"); yield 1 page = { "data" : "<html><body><h1>Hello World</h1><P>Woo!!</body></html>", } self.send(page, "outbox"); yield 1 self.send(Axon.Ipc.producerFinished(self), "signal") yield 1 def servePage(request): return Minimal(request=request, homedirectory=homedirectory, indexfilename=indexfilename)
class Cat(Axon.Component.component): def __init__(self, *args): super(Cat, self).__init__() self.args = args def main(self): self.send(self.args, "outbox") self.send(Axon.Ipc.producerFinished(self), "signal") yield 1 class ExampleWrapper(Axon.Component.component): def main(self): # Tell the browser the type of data we're sending! resource = { "type" : "text/html", "statuscode" : "200", } self.send(resource, "outbox"); yield 1 # Send the header header = { "data" : "<html><body>" } self.send(header, "outbox"); yield 1 # Wait for it.... while not self.dataReady("inbox"): self.pause() yield 1 # Send the data we recieve as the page body while self.dataReady("inbox"): pageData = { "data" : str(self.recv("inbox")) } self.send(pageData, "outbox"); yield 1 # send a footer footer = { "data" : "</body></html>" } self.send(footer, "outbox"); yield 1 # and shutdown nicely self.send(Axon.Ipc.producerFinished(self), "signal") yield 1 def EchoHandler(request): return Pipeline ( Cat(request), ExampleWrapper() )
# A factory to create configured HTTPServer components - ie HTTP Protocol handling components
def HTTPProtocol(): return HTTPServer(requestHandlers([ ["/echo", EchoHandler ], ["/hello", HelloHandler ], ["/", servePage ], ]))
# Finally we create the actual server and run it.
SimpleServer(protocol=HTTPProtocol, port=8082, socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ).run()
Some of the API on this is likely to be revamped slightly based on
writing this cookbook page. Specifically a number of functions and
components on this page are likely to migrate into the codebase! (making
your life easier)