# -*- test-case-name: twisted.web2.test.test_vhost -*-
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.
"""I am a virtual hosts implementation.
"""
# System Imports
import urlparse
from zope.interface import implements
import urllib
import warnings
from twisted.internet import address
from twisted.python import log
# Sibling Imports
from twisted.web2 import resource
from twisted.web2 import responsecode
from twisted.web2 import iweb
from twisted.web2 import http
class NameVirtualHost(resource.Resource):
"""Resource in charge of dispatching requests to other resources based on
the value of the HTTP 'Host' header.
@param supportNested: If True domain segments will be chopped off until the
TLD is reached or a matching virtual host is found. (In which case the
child resource can do its own more specific virtual host lookup.)
"""
supportNested = True
def __init__(self, default=None):
"""
@param default: The default resource to be served when encountering an
unknown hostname.
@type default: L{twisted.web2.iweb.IResource} or C{None}
"""
resource.Resource.__init__(self)
self.hosts = {}
self.default = default
def addHost(self, name, resrc):
"""Add a host to this virtual host. - The Fun Stuff(TM)
This associates a host named 'name' with a resource 'resrc'::
nvh.addHost('nevow.com', nevowDirectory)
nvh.addHost('divmod.org', divmodDirectory)
nvh.addHost('twistedmatrix.com', twistedMatrixDirectory)
I told you that was fun.
@param name: The FQDN to be matched to the 'Host' header.
@type name: C{str}
@param resrc: The L{twisted.web2.iweb.IResource} to be served as the
given hostname.
@type resource: L{twisted.web2.iweb.IResource}
"""
self.hosts[name] = resrc
def removeHost(self, name):
"""Remove the given host.
@param name: The FQDN to remove.
@type name: C{str}
"""
del self.hosts[name]
def locateChild(self, req, segments):
"""It's a NameVirtualHost, do you know where your children are?
This uses locateChild magic so you don't have to mutate the request.
"""
host = req.host.lower()
if self.supportNested:
while not self.hosts.has_key(host) and len(host.split('.')) > 1:
host = '.'.join(host.split('.')[1:])
# Default being None is okay, it'll turn into a 404
return self.hosts.get(host, self.default), segments
class AutoVHostURIRewrite(object):
"""
I do request mangling to insure that children know what host they are being
accessed from behind apache2.
Usage:
- Twisted::
root = MyResource()
vur = vhost.AutoVHostURIRewrite(root)
- Apache2::
ProxyPass http://localhost:8538/
RequestHeader set X-App-Location /whatever/
If the trailing / is ommitted in the second argument to ProxyPass
VHostURIRewrite will return a 404 response code.
If proxying HTTPS, add this to the Apache config::
RequestHeader set X-App-Scheme https
"""
implements(iweb.IResource)
def __init__(self, resource, sendsRealHost=False):
"""
@param resource: The resource to serve after mutating the request.
@type resource: L{twisted.web2.iweb.IResource}
@param sendsRealHost: If True then the proxy will be expected to send the
HTTP 'Host' header that was sent by the requesting client.
@type sendsRealHost: C{bool}
"""
self.resource=resource
self.sendsRealHost = sendsRealHost
def renderHTTP(self, req):
return http.Response(responsecode.NOT_FOUND)
def locateChild(self, req, segments):
scheme = req.headers.getRawHeaders('x-app-scheme')
if self.sendsRealHost:
host = req.headers.getRawHeaders('host')
else:
host = req.headers.getRawHeaders('x-forwarded-host')
app_location = req.headers.getRawHeaders('x-app-location')
remote_ip = req.headers.getRawHeaders('x-forwarded-for')
if not (host and remote_ip):
if not host:
warnings.warn(
("No host was obtained either from Host or "
"X-Forwarded-Host headers. If your proxy does not "
"send either of these headers use VHostURIRewrite. "
"If your proxy sends the real host as the Host header "
"use "
"AutoVHostURIRewrite(resrc, sendsRealHost=True)"))
# some header unspecified => Error
raise http.HTTPError(responsecode.BAD_REQUEST)
host = host[0]
remote_ip = remote_ip[0]
if app_location:
app_location = app_location[0]
else:
app_location = '/'
if scheme:
scheme = scheme[0]
else:
scheme='http'
req.host, req.port = http.splitHostPort(scheme, host)
req.scheme = scheme
req.remoteAddr = address.IPv4Address('TCP', remote_ip, 0)
req.prepath = app_location[1:].split('/')[:-1]
req.path = '/'+('/'.join([urllib.quote(s, '') for s in (req.prepath + segments)]))
return self.resource, segments
class VHostURIRewrite(object):
"""
I do request mangling to insure that children know what host they are being
accessed from behind mod_proxy.
Usage:
- Twisted::
root = MyResource()
vur = vhost.VHostURIRewrite(uri='http://hostname:port/path', resource=root)
server.Site(vur)
- Apache::
ProxyPass /path/ http://localhost:8080/
Servername hostname
If the trailing / is ommitted in the second argument to ProxyPass
VHostURIRewrite will return a 404 response code.
uri must be a fully specified uri complete with scheme://hostname/path/
"""
implements(iweb.IResource)
def __init__(self, uri, resource):
"""
@param uri: The URI to be used for mutating the request. This MUST
include scheme://hostname/path.
@type uri: C{str}
@param resource: The resource to serve after mutating the request.
@type resource: L{twisted.web2.iweb.IResource}
"""
self.resource = resource
(self.scheme, self.host, self.path,
params, querystring, fragment) = urlparse.urlparse(uri)
if params or querystring or fragment:
raise ValueError("Must not specify params, query args, or fragment to VHostURIRewrite")
self.path = map(urllib.unquote, self.path[1:].split('/'))[:-1]
self.host, self.port = http.splitHostPort(self.scheme, self.host)
def renderHTTP(self, req):
return http.Response(responsecode.NOT_FOUND)
def locateChild(self, req, segments):
req.scheme = self.scheme
req.host = self.host
req.port = self.port
req.prepath=self.path[:]
req.path = '/'+('/'.join([urllib.quote(s, '') for s in (req.prepath + segments)]))
# print req.prepath, segments, req.postpath, req.path
return self.resource, segments
__all__ = ['VHostURIRewrite', 'AutoVHostURIRewrite', 'NameVirtualHost']