Python と Twisted できみにも書ける Web サーバ(2)

もう少し Web サーバらしく細かな設定をする

名前ベースのバーチャルホスト

IP アドレスを共有する名前ベースのバーチャルホストを設定するときは nevow.vhost.NameVirtualHost をリソースツリーのルートに使用します。 nevow.vhost.NameVirtualHost もまたリソースタイプのオブジェクトです。

>>> from nevow import vhost
>>> list(zope.interface.implementedBy(vhost.NameVirtualHost))
[<InterfaceClass nevow.inevow.IResource>, <InterfaceClass nevow.inevow.IRenderer
>, <InterfaceClass nevow.inevow.IGettable>, <InterfaceClass nevow.inevow.IContai
ner>, <InterfaceClass nevow.inevow.IRendererFactory>, <InterfaceClass nevow.inev
ow.IMacroFactory>, <_MetaConfigurable formless.iformless.IConfigurable>, <Interf
aceClass formless.iformless.IConfigurableFactory>]

以下は2つのバーチャルホスト www.example.com と www.example.org を設定する場合の例です。NameVirtualHost オブジェクトの addHost メソッドを使ってホスト名と対応するリソースを登録していきます。

# 名前ベースのバーチャルホスト
# nvhost.tac

from nevow import appserver, static, vhost
from twisted.web import error, twcgi
from twisted.application import service, internet

# 各バーチャルホストに共通の設定
PORT = 80
TIMEOUT = 300
UID = 33
GID = 33
LOGPATH = '/var/log/www/access.log'

# バーチャルホスト毎の設定
config = (
    
(
    
# www.example.com 用の設定
    
'www.example.com', # ホスト名
    
'/var/www/www.example.com/html', # HTML 用ディレクトリ
    
'/var/www/www.example.com/cgi-bin', # CGI 用ディレクトリ
    
),
    
(
    
# www.example.org 用の設定
    
'www.example.org',
    
'/var/www/www.example.org/html',
    
'/var/www/www.example.org/cgi-bin',
    
),
    
)

class FileWithoutListing(static.File):
    
"""ファイル一覧を表示しないリソース"""
    
def directoryListing(self):
        
return error.ForbiddenResource()

root = vhost.NameVirtualHost(listHosts=False)

for hostname, htmlDir, cgiDir in config:
    
resrc = FileWithoutListing(htmlDir)
    
cgiResrc = twcgi.CGIDirectory(cgiDir)
    
resrc.putChild('cgi-bin', cgiResrc)
    
root.addHost(hostname, resrc)

site = appserver.NevowSite(root, logPath=LOGPATH, timeout=TIMEOUT)
server = internet.TCPServer(PORT, site)
application = service.Application('my_httpd', uid=UID, gid=GID)
server.setServiceParent(application)

なお上記例では、バーチャルホストとして登録されていない名前やアドレス直接指定でアクセスがあった場合 404 NotFound を返すようになっています。 nevow.vhost.NameVirtualHost のオブジェクト作成時に指定できるオプション引数は以下の通りです。

default

登録されたホスト以外の名前でアクセスがあったときに使用するリソースオブジェクトを指定。デフォルトは None。

listHosts

default オプションの指定が None の場合のみ意味を持ちます。True を指定すると nevow.vhost.VirtualHostList オブジェクトを使用してバーチャルホストの一覧を出力します。False の場合は 404 Not Found が返されます。デフォルトは True。

リダイレクト

twisted.web には一時的なリダイレクト(302 Found)用のリソース twisted.web.util.Redirect が用意されていますが、恒久的なリダイレクト(301 Moved Permanently)用のリソースがありません。ホスト名の変更などで恒久的リダイレクトを実行したいときはクラスを作成する必要があります。

以下は example.com へのアクセスをすべて http://www.example.com/ へリダイレクトする例です。

# 恒久的なリダイレクトの実行
# redirect.tac

from nevow import appserver, static, vhost
from twisted.web import error, twcgi, util
from twisted.application import service, internet
from twisted.web.http import MOVED_PERMANENTLY

PORT = 80
TIMEOUT = 300
UID = 33
GID = 33
LOGPATH = '/var/log/www/access.log'

# www.example.com 用の設定
DOCUMENT_ROOT = '/var/www/www.example.com/html'
CGI_DIR = '/var/www/www.example.com/cgi-bin'

class FileWithoutListing(static.File):
    
"""ファイル一覧を表示しないリソース"""
    
def directoryListing(self):
        
return error.ForbiddenResource()

class MovedPermanently(util.Redirect):
    
"""恒久的リダイレクト(301 Moved Permanently)用リソース"""

    
def render(self, request):
        
request.setResponseCode(MOVED_PERMANENTLY)
        
request.setHeader('location', self.url)
        
return = """
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<h1>301 Moved Permanently</h1>
<p>The object has moved to <a href="%s">%s</a>.</p>
</body>
</html>
"""
 % (self.url, self.url)

root = vhost.NameVirtualHost(listHosts=False)

# www.example.com
resrc = FileWithoutListing(DOCUMENT_ROOT)
cgiResrc = twcgi.CGIDirectory(CGI_DIR)
resrc.putChild('cgi-bin', cgiResrc)
root.addHost('www.example.com', resrc)
# example.com へのアクセスは www.example.com へリダイレクト
root.addHost('example.com', MovedPermanently('http://www.example.com/'))

site = appserver.NevowSite(root, logPath=LOGPATH, timeout=TIMEOUT)
server = internet.TCPServer(PORT, site)
application = service.Application('my_httpd', uid=UID, gid=GID)
server.setServiceParent(application)

IPアドレスベースのバーチャルホスト

各バーチャルホストにそれぞれ独自の IP アドレスを割り当てる場合は、それぞれ独立したサービスオブジェクトを作成して、アプリケーションオブジェクトに登録します。各ホストがリッスンする IP アドレスは twisted.application.internet.TCPServer の interface オプションで指定します。

下記は2つのアドレス192.168.1.1と192.168.1.2をそれぞれ別のホスト設定で使用する例です。

# IP アドレスベースのバーチャルホスト
# multihost.tac

from nevow import appserver, static
from twisted.web import error, twcgi
from twisted.application import service, internet

# 各バーチャルホストに共通の設定
PORT = 80
TIMEOUT = 300
UID = 33
GID = 33

# バーチャルホスト毎の設定
config = (
    
(
    
# 192.168.1.1用の設定
    
'192.168.1.1', # 使用 IP アドレス
    
'/var/www/www.example.com/html', # HTML 用ディレクトリ
    
'/var/www/www.example.com/cgi-bin', # CGI 用ディレクトリ
    
'/var/log/www/www.example.com.log', # アクセスログ
    
),
    
(
    
# 192.168.1.2用の設定
    
'192.168.1.2',
    
'/var/www/www.example.org/html',
    
'/var/www/www.example.org/cgi-bin',
    
'/var/log/www/www.example.org.log',
    
),
    
)

class FileWithoutListing(static.File):
    
"""ファイル一覧を表示しないリソース"""
    
def directoryListing(self):
        
return error.ForbiddenResource()

application = service.Application('my_httpd', uid=UID, gid=GID)

for address, htmlDir, cgiDir, logPath in config:
    
resrc = FileWithoutListing(htmlDir)
    
cgiResrc = twcgi.CGIDirectory(cgiDir)
    
resrc.putChild('cgi-bin', cgiResrc)
    
site = appserver.NevowSite(resrc, logPath=logPath, timeout=TIMEOUT)
    
server = internet.TCPServer(PORT, site, interface=address)
    
server.setServiceParent(application)

SSL を使う

いよいよ SSL 対応のサーバを書きます。 pyOpenSSL をあらかじめインストールしておいてください。暗号化通信のややこしいところは pyOpenSSL (と OpenSSL)におまかせなので、特に難しいことはありません。

SSL 通信用のサービスオブジェクトを作成するときは twisted.application.internet.TCPServer ではなく twisted.application.internet.SSLServer を使います。またオブジェクト作成時の引数に SSL コンテクストを生成するファクトリオブジェクトを渡してやる必要があります。SSL コンテクストファクトリのクラスは twisted.internet.ssl.DefaultOpenSSLContextFactory として用意されています。

サーバ証明書と秘密鍵ファイルは Apache の mod_ssl で使用する場合と同じ PEM 形式のものを用意します。以下はポート80番で通常の Web サーバ、443番で SSL サーバを動かす場合の例です。どちらも配信するコンテンツ内容は基本的に同じですが、 http://hostname/secure/ だけは SSL を使わなければアクセスできないようにしています。

# SSL サーバの設定例
# sslserv.tac

from nevow import appserver, static
from twisted.web import error, twcgi
from twisted.application import service, internet
from twisted.internet import ssl

DOCUMENT_ROOT = '/var/www/html'
CGI_DIR = '/var/www/cgi-bin'
PORT = 80
LOGPATH = '/var/log/www/access.log'
TIMEOUT = 300
UID = 33
GID = 33

SSL_PORT = 443
SSL_LOGPATH = '/var/log/www/ssl.log'
SECURE_DOCUMENT = '/var/www/secure' # SSL 専用コンテンツ
CERT_FILE = '/var/www/ssl/server.crt' # サーバ証明書
KEY_FILE = '/var/www/ssl/server.key' # 秘密鍵

class FileWithoutListing(static.File):
    
"""ファイル一覧を表示しないリソース"""
    
def directoryListing(self):
        
return error.ForbiddenResource()

application = service.Application('my_httpd', uid=UID, gid=GID)

# 通常の Web サーバ
resrc = FileWithoutListing(DOCUMENT_ROOT)
cgiResrc = twcgi.CGIDirectory(CGI_DIR)
resrc.putChild('cgi-bin', cgiResrc)
site = appserver.NevowSite(resrc, logPath=LOGPATH, timeout=TIMEOUT)
server = internet.TCPServer(PORT, site)
server.setServiceParent(application)

# SSL サーバ
resrc2 = FileWithoutListing(DOCUMENT_ROOT)
cgiResrc2 = twcgi.CGIDirectory(CGI_DIR)
secureResrc = FileWithoutListing(SECURE_DOCUMENT)
resrc2.putChild('cgi-bin', cgiResrc2)
resrc2.putChild('secure', secureResrc) # SSL を通じてのみアクセス可
site2 = appserver.NevowSite(resrc2, logPath=SSL_LOGPATH, timeout=TIMEOUT)
# SSL サーバ用のサービス登録
sslCtx = ssl.DefaultOpenSSLContextFactory(KEY_FILE, CERT_FILE)
sslServer = internet.SSLServer(SSL_PORT, site2, sslCtx)
sslServer.setServiceParent(application)

MIME タイプのカスタマイズ

スタテッィクファイルの配信で用いる MIME タイプの定義は /etc/mime.types から読み込むようになっています。定義内容は拡張子をキーに持つ辞書として nevow.static.File の contentTypes にセットされます。定義をカスタマイズしたい場合は、インスタンス作成後に辞書を更新します。

>>> from nevow import static
>>> resrc = static.File('/var/www/html')
>>> type(resrc.contentTypes)
<type 'dict'>
>>> resrc.contentTypes['.swf']
'application/x-shockwave-flash'
>>> resrc.contentTypes['.bz2']
Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: '.bz2'
>>> resrc.contentTypes['.bz2'] = 'application/x-bzip2'

/etc/mime.types 以外のファイルを使いたいときは 関数 nevow.static.loadMimeTypes() を使って読み込みます。オプション引数 mimetype_locations にはパス名のリスト(またはタプル)を指定します。

>>> mimetypes = ['/var/www/etc/mime.types']
>>> resrc.contentTypes = static.loadMimeTypes(mimetype_locations=mimetypes)

(つづく)

最終更新 2007-03-12 20:37:36

3月 2007
    1 2 3 4
5 6 7 8 91011
12131415161718
19202122232425
262728293031 
2月
2007
 4月
2007

Python と Twisted、それに Nevow を使った Web サーバの書き方

XML-Image Letterimage

© 2007-2010, Yasushi Iwata