Carl
Carl

Reputation: 715

Twisted Web behind Apache - How to correct links?

I am attempting to write a web application using the Twisted framework for python. I want the application to work if run as a standalone server (ala twistd), or if Apache reverse proxies to it. E.g.

Apache https://example.com/twisted/ --> https://internal.example.com/

After doing some research, it seemed like I needed to use the vhost.VHostMonsterResource to make this work. So I set up apache with the following directive:

ProxyPass /twisted https://localhost:8090/twisted/https/127.0.0.1:443

Here is my basic SSL server:

from twisted.web import server, resource, static
from twisted.internet import reactor
from twisted.application import service, internet
from twisted.internet.ssl import SSL
from twisted.web import vhost

import sys
import os.path
from textwrap import dedent

PORT = 8090
KEY_PATH = "/home/waldbiec/projects/python/twisted"
PATH = "/home/waldbiec/projects/python/twisted/static_files"

class Index(resource.Resource):
    def render_GET(self, request):
        html = dedent("""\
            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
            <html>
            <head>
                <title>Index</title>
            </head>
            <body>
                <h1>Index</h1>
                <ul>
                    <li><a href="/files/">Files</a></li>
                </ul>
            </body>
            </html>
            """)
        return html

class ServerContextFactory:
    def getContext(self):
        """
        Create an SSL context.

        Similar to twisted's echoserv_ssl example, except the private key
        and certificate are in separate files.
        """
        ctx = SSL.Context(SSL.SSLv23_METHOD)
        ctx.use_privatekey_file(os.path.join(KEY_PATH, 'serverkey.pem'))
        ctx.use_certificate_file(os.path.join(KEY_PATH, 'servercert.pem'))
        return ctx

class SSLService(internet.SSLServer):
    def __init__(self):
        root = resource.Resource()
        root.putChild("", Index())
        root.putChild("twisted", vhost.VHostMonsterResource())
        root.putChild("files", static.File(PATH))

        site = server.Site(root)
        internet.SSLServer.__init__(self, PORT, site, ServerContextFactory())

application = service.Application("SSLServer")
ssl_service = SSLService()
ssl_service.setServiceParent(application)

It almost works-- but the "files" link on the index page does not behave how I want it to when using apache as a reverse proxy, because it is an absolute link.

My main question is, other than using a relative link, is there some way to compute what the full URL path of the link ought to be in such a way that the link still works in standalone server mode? A second question would be, am I using VHostMonsterResource correctly? I did not find much documentation, and I pieced together my code from examples I found on the web.

Upvotes: 0

Views: 1285

Answers (3)

Carl
Carl

Reputation: 715

So after digging into the vhost.VHostMonsterResource source, I determined I could create another resource that could let the reverse proxied URL prefix be specified by an additional marker in the Apache ProxyPass URL.

Firstly, I finally figured out that vhost.VHostMonsterResource is supposed to be a special URL in your back end web site that figures out the reverse proxy host and port from data encoded in the URL path. The URL path (sans scheme and net location) looks like:

/$PATH_TO_VHMONST_RES/$REV_PROXY_SCHEME/$REV_PROXY_NETLOC/real/url/components/

$PATH_TO_VHMONST : Path in the (internal) twisted site that corresponds to the VHostMonsterResource resource.
$REV_PROXY_SCHEME : http or https that is being used by the reverse proxy (Apache).
$REV_PROXY_NETLOC : The net location (host and port) or the reverse proxy (Apache).

So you can control the configuration from the reverse proxy by encoding this information in the URL. The result is that the twisted site will understand the HTTP request came from the reverse proxy.

However, if you are proxying a subtree of the external site as per my original example, this information is lost. So my solution was to create an additional resource that can decode the extra path information. The new proxy URL path becomes:

/$PATH_TO_MANGLE_RES/$REV_PROXY_PATH_PREFIX/$VHOSTMONST_MARKER/$REV_PROXY_SCHEME/$REV_PROXY_NETLOC/real/url/components/

$PATH_TO_MANGLE_RES : The path to the resource that decodes the reverse proxy path info.
$REV_PROXY_PATH_PREFIX : The subtree prefix of the reverse proxy.
$VHOSTMONST_MARKER : A path component (e.g. "vhost") that signals a VHostMonster Resource should be used to further decode the path.

Upvotes: 0

Phil Cooper
Phil Cooper

Reputation: 5877

This seems like too much work. Why use VHostMonsterResource at all? You may have very specific reasons for wanting some of this but....Most times:

  • Have apache handle the ssl. apache then passes off to your twisted app serving non SSL goodies back to apache. Documentation all over the net on the apache config stuff.

  • you can sill add another server on an ssl port if you really want to

Haven't tested but structure more like:

root = resource.Resource()
root.putChild("", Index())
root.putChild("files", static.File(PATH))

http = internet.TCPServer(8090, server.Site(root))
# change this port # to 443 if no apache
https= internet.SSLServer(8443, server.Site(root), ServerContextFactory())

application = service.Application("http_https_Server")
http.setServiceParent(application)
https.setServiceParent(application)

Dev tip: During development, for the cost of a couple of extra lines you can add an ssl server so that you can ssh into the running web_server and inspect variables and other state. Way cool.

ssl = internet.TCPServer(8022, getManholeFactory(globals(), waldbiec ='some non-system waldbiec passwork')) 
ssl.setServiceParent(application)

Upvotes: 1

Jean-Paul Calderone
Jean-Paul Calderone

Reputation: 48335

Configure the Twisted application so that it knows its own root location. It can use that information to generate URLs correctly.

Upvotes: 0

Related Questions