pythontwistedtwisted.web

How to define paths with multiple segments in Twisted web server


In a Twisted web server similar to this example code

from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor, endpoints
from twisted.web.static import File

root = Resource()
root.putChild(b"foo", File("/tmp"))
root.putChild(b"bar", File("/lost+found"))
root.putChild(b"baz", File("/opt"))

factory = Site(root)
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8880)
endpoint.listen(factory)
reactor.run()

How can you define a response for a multi segment path so that instead of the above example where a resource is available at

http://example.com/foo 

You want a resource to be available at, say,

http://example.com/europe/france/paris

And, in case it affects the answer the following URL's would not provide a response

http://example.com/europe/france
http://example.com/europe
http://example.com

I know the doco refers to the use of Resource Trees but the only examples given are single level trees which isn't too useful for my requirement.


RESOLUTION

With the help of @jean-paul-calderone I've written the following which does what I want.

For the question I had simplified my requirements a little , in fact I wanted to output something other than files and the code I've included below reflects that. I'm pretty sure that what I've written here isn't the best way of doing it but it does provide the multi segment URL I wanted and so might prove useful to others who have similar needs.

from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor, endpoints
from twisted.web.resource import NoResource

import html

class ContinentLevelResource(Resource):
    pass

class CountryLevelResource(Resource):
    pass

class CityLevelResource(Resource):
    def render_GET(self, request):
        return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
                b"<title>City Page</title></head><body>"
                b"<p>This is the City page</p></body>")

root = Resource()
continent = ContinentLevelResource()
country = CountryLevelResource()
city = CityLevelResource()
#    
root.putChild(b"europe", continent)
continent.putChild(b"france", country)
country.putChild(b"paris", city)
#
factory = Site(root)
endpoint = endpoints.TCP4ServerEndpoint(reactor, 8880)
endpoint.listen(factory)
reactor.run()

Solution

  • You merely apply the same API usage as appears in your question - recursively:

    Given:

    root = Resource()
    tmp = File("/tmp")
    root.putChild(b"foo", tmp)
    lost_found = File("/lost+found")
    root.putChild(b"bar", lost_found)
    opt = File("/opt")
    root.putChild(b"baz", opt)
    

    You have /foo, /bar, and /baz. If you wantsome_resourceat/foo/quux` then:

    tmp.putChild(b"quux", some_resource)
    

    The same applies to add additional levels. Therefore:

    root = Resource()
    europe = Resource()
    france = Resource()
    paris = Resource()
    
    root.putChild(b"europe", europe)
    europe.putChild(b"france", france)
    france.putChild(b"paris", paris)
    

    There is not really any such thing as "not returning a response" in HTTP. However, you can make the intermediate resources return a 404 (not found) status if you want. Just replace the intermediate Resource instances with something that behaves as you desire (for example, a NotFound instance).

    You may also want to investigate klein which implements the "route"-based resource hierarchy definition API which is often more common (in Python) these days.