[Twisted-Python] twisted.web with dynamic + static content

Glyph glyph at twistedmatrix.com
Thu Nov 1 01:43:57 MDT 2018


Hi Jeff,

Thanks for using Twisted.

Here's a version with some small changes that works, and is self-contained.

import sys

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

sys.path.append('lib')

content = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/static/test.css" type="text/css" />
</head>
<body>
    <span class='twistedTest'>This</span> is a test
</body>
</html>
"""

class tServer(Resource):
    def render_GET(self, request):
        return bytes(content, "utf-8")

if __name__ == "__main__":
    root = Resource()
    static_collection = Resource()
    static_collection.putChild(b"test.css", Data(b".twistedTest {color: red;}", "text/css"))
    root.putChild(b"static", static_collection)
    root.putChild(b"", tServer())

    site = server.Site(root)
    endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
    endpoint.listen(site)

    reactor.run()
    print("Shutting down!")

The problem with your first version was 'isLeaf', as Donal suggested.  However, the problem was not simply that the flag was set, but rather what the flag means, and why it works that way.

The root resource in any web server is a collection.  Which is to say, under normal circumstances, the root resource never has render_* invoked on it; you can't render it, because it's impossible, in the HTTP protocol, to spell a URL that doesn't start with "/".

isLeaf changes this, and says "this resource is responsible for rendering all of its children; traversal stops here".  That means that it starts invoking render_GET to render "/", but also to render every other path on the server, including (unfortunately for you) /static/test.css.

The modified example above instead uses a Resource() as the collection, and inserts a '' child for the index, and a separate 'static' child for the static index.  You can use a static.File for a directory here instead of a static resource, and anywhere you see putChild, you could also use a dynamic resource which overrides getChild to return the object rather than inserting it in advance.

Of course, you might wonder what the point of 'isLeaf' is if it short circuits this stuff and makes it impossible to tell the difference between resources.

Given that you have a directory, you want to use a static.File child resource and almost certainly don't want to set isLeaf; however, you might be wondering how one would even use isLeaf if it just cuts off the ability to tell the difference between resources.  The documentation on this is not great - it doesn't even appear as an attribute in the API reference, just an oblique reference in the docstring for https://twistedmatrix.com/documents/current/api/twisted.web.resource.Resource.html#getChild <https://twistedmatrix.com/documents/current/api/twisted.web.resource.Resource.html#getChild>.  But, the 'prepath' and 'postpath' attributes, lists of bytes, will tell you about where in the request traversal cycle you are, and allow you to distinguish which content to render directly within the body of render_*, rather than having to route to the right object using Twisted APIs.  So here's a working version with isLeaf=True:

import sys

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

content = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/static/test.css" type="text/css" />
</head>
<body>
    <span class='twistedTest'>This</span> is a test
</body>
</html>
"""

css = """
.twistedTest {
    color: red;
}
"""

class tServer(Resource):
    isLeaf = True
    def render_GET(self, request):
        if request.postpath == [b'']:
            request.setHeader("content-type", "text/html")
            return bytes(content, "utf-8")
        elif request.postpath == [b'static', b'test.css']:
            request.setHeader("content-type", "text/css")
            return bytes(css, 'utf-8')
        else:
            request.setResponseCode(404)
            return b'not found'

if __name__ == "__main__":
    site = server.Site(tServer())
    endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
    endpoint.listen(site)

    reactor.run()
    print("Shutting down!")

I hope this clears up the request traversal model a little bit.

-glyph


> On Oct 31, 2018, at 2:15 PM, Jeff Grimmett <grimmtooth at gmail.com> wrote:
> 
> Tried that, I get a big 
> 
> No Such Resource
> 
> No such child resource.
> 
> back.  Watching it in FF's development panel, I see a 404 come back for /.  /static doesn't get served at all, of course.
> 
> This, however, DID work.
> 
> class tServer(Resource):
>     isLeaf = False
> 
>     def getChild(self, path, request):
>         print('You know what you doing.')
> 
>         if path == b'':
>             print("Rendering /")
>             return self
> 
>         return Resource.getChild(self, path, request)
> 
>     def render_GET(self, request):
>         return bytes(content, "utf-8")
> 
> (ignore my printf debugging plz)
> 
> So, Thanks! :)
> 
> Regards,
> 
> Jeff 
> 
> 
> On Tue, Oct 30, 2018 at 6:42 PM Donal McMullan <donal.mcmullan at gmail.com <mailto:donal.mcmullan at gmail.com>> wrote:
> Try replacing:
> isLeaf = True
> with
> isLeaf = False
> 
> 
> On Tue, 30 Oct 2018 at 21:32, Jeff Grimmett <grimmtooth at gmail.com <mailto:grimmtooth at gmail.com>> wrote:
> I'm sure I'm overlooking something obvious here but I just can't get my head around it.
> 
> Here's the setup: twisted.web server that generates dynamic content. Child that serves up static content, e.g. css and favoicon.  However, the static content isn't making it. Instead, any hit to localhost/static actually yields up a copy of / again.  
> 
> Here's the server code
> 
> import sys
> 
> from twisted.internet import reactor, endpoints
> from twisted.web import server
> from twisted.web.resource import Resource
> from twisted.web.static import File
> 
> sys.path.append('lib')
> 
> content = """
> <!DOCTYPE html>
> <html lang="en">
> <head>
>     <meta charset="UTF-8">
>     <link rel="stylesheet" href="/static/test.css" type="text/css" />
> </head>
> <body>
>     <span class='twistedTest'>This</span> is a test
> </body>
> </html>
> """
> 
> 
> class tServer(Resource):
>     isLeaf = True
> 
>     def render_GET(self, request):
>         return bytes(content, "utf-8")
> 
> 
> if __name__ == "__main__":
>     root = tServer()
>     root.putChild(b"static", File("static"))
> 
>     site = server.Site(root)
>     endpoint = endpoints.TCP4ServerEndpoint(reactor, 8080)
>     endpoint.listen(site)
> 
>     reactor.run()
>     print("Shutting down!")
> 
> It's run with the command 'python tserver.py'.  The expectation is that what is inside the custom <span> will be red.
> 
> In the same dir as the script is a subdir 'static' with the css file inside it.
> 
> If I replace 'root' with     root = Resource() then / doesn't serve up anything, but /static is a directory listing of the static directory.
> 
> The dynamic server is basically a copy of several tutorials cooked down to something that I could use to demonstrate the problem.
> 
> What am I missing here? /headscratch
> 
> Regards,
> 
> Jeff 
> _______________________________________________
> Twisted-Python mailing list
> Twisted-Python at twistedmatrix.com <mailto:Twisted-Python at twistedmatrix.com>
> https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python <https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python>
> _______________________________________________
> Twisted-Python mailing list
> Twisted-Python at twistedmatrix.com <mailto:Twisted-Python at twistedmatrix.com>
> https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python <https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python>
> _______________________________________________
> Twisted-Python mailing list
> Twisted-Python at twistedmatrix.com
> https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python

-------------- next part --------------
An HTML attachment was scrubbed...
URL: </pipermail/twisted-python/attachments/20181101/04f3b6f6/attachment-0001.html>


More information about the Twisted-Python mailing list