Twisted Web Development
Twisted Web接受(serve)實現了IResource接口的Python對象。
Main Concepts:
Site Objects
負責創建HTTPChannel實例來解析HTTP request,並且啓動對象查找過程。
他們包含root Resource,root Resource代表的是當前站點的/這個URL.
Resource
Resource對象代表一個單獨的URL片段。IResource接口描述的是一個Resource對象爲了參與對象發佈過程(the object publishing process)而必須實現的方法。
Resource trees
Resource樹就是把Resource對象整理成一顆Resource樹。從root resource對象開始,resource對象樹定義了所有有效的URL。
.rpy scripts
.rpy腳本是tiwsted.web靜態文件服務器要執行的腳本,跟CGI很像。不過,跟CGI不一樣的是他們必須創建一個resource對象,這個對象會在URL被訪問的時候渲染出去(be rendered)。
Resource rendering
Resource rendering發生在Twisted Web定位到(locates)葉節點的resource對象(a leaf Resource object)的時候。一個Resource要嘛返回一段HTML字符串,要嘛往request對象裏面寫數據。
Session
Session對象允許我們跨越多個request存儲信息。使用我們服務器的每個獨立瀏覽器都有一個唯一的Session實例。
Twisted Web Server通過Twisted Daemonizer啓動,如下:
%twistd web
Site Objects
Site對象的服務類似於“監聽HTTP請求的端口”和“主Resource對象(a root Resource object)”之間的膠水。
當使用twistd -n web --path /foo/bar/baz時,一個指定了主Resource的Site對象就創建出來了,它爲指定的路徑提供文件展示(下載)。
我們也可以手動創建一個Site實例,傳入一個Resource對象(Simple()),這個Resource對象作爲site的根提供服務(which will serve as the root of the site):
Resource objectsfrom twisted.web import server, resource from twisted.internet import reactor class Simple(resource.Resource): isLeaf = True def render_GET(self, request): return "<html>Hello, world!</html>" site = server.Site(Simple()) reactor.listenTCP(8080, site) reactor.run()
Resource對象代表站點(site)的一個單獨的URL片段(segment)。在URL解析過程中,對當前Resource調用getChild可以得到下一個Resource對象。
當到達葉節點Resource,要嘛是因爲沒有更多的URL片斷(segments)了要嘛是因爲一個Resource的idLeaf屬性被設置成True了。葉節點Resource通過調用render(request)來渲染(render)。細節見下面的“Resource Rendering”.
在Resource定位過程中,已經處理過的URL片段(URL segments)和那些還沒處理過的分別在request.prepath和request.postpath中。
Resource通過查看request.prepath來確定它在URL樹中什麼位置,request.prepath是一個URL片段(segment)的list.
Resource通過查看request.postpath來確定在它之後哪些路徑片斷(which path segments)將被處理。
如果URL以斜線(slash)結尾,例如http://example.com/foo/bar/,最後的URL片斷將是一個空串。因此,Resources可以通過上述方式知道他們收到的請求是否以斜線結尾。
下面是一個簡單的Resource對象:
from twisted.web.resource import Resource class Hello(Resource): isLeaf = False #False才能讓getChild被調用 def getChild(self, name, request): if name == '': return self return Resource.getChild(self, name, request) def render_GET(self, request): return "Hello, world! I am located at %r." % (request.prepath,) resource = Hello()
Resource Trees
可以用putChild把Resources整理(arrange)到樹裏面。putChild把一個Resource實例放入到另一個Resource實例中,使得它可被指定的path片段名使用:
如果這個root resource是作爲一個Site實例的root提供服務的,那麼下面URL將變成有效的:root = Hello() root.putChild('fred', Hello()) root.putChild('bob', Hello())
http://example.com/ http://example.com/fred http://example.com/bob http://example.com/fred/ http://example.com/bob/
.rpy scripts
擴展名爲.rpy的文件是Python腳本,把這些文件放在Twisted Web服務的目錄下,通過web訪問時這些文件會被執行。
一個.rpy腳本必須定義一個變量resource,這個變量是將要渲染(render)request的Resource對象。
.rpy文件對於快速開發和建模(prototyping)來說非常方便。因爲他們在每一次web請求(request)時被執行,所以,在.rpy中定義一個Resource子類將會在刷新頁面時使如下功能變得更簡單:可視化(visible)的呈現我們class改變帶來的結果(這個大句子不知道怎麼翻譯較好,反正意思就是說,把Resource的子類定義在一個.rpy文件中可以使得讓新修改的關於Resource子類的代碼自動生效變得更簡單)。
但是,在Python模塊中定義Resource子類是更好的主意。爲了讓模塊的改變生效(visible),我們要嘛重啓Python進程,要嘛reload模塊:from twisted.web.resource import Resource class MyResource(Resource): def render_GET(self, request): return "<html>Hello, world!</html>" resource = MyResource()
創建一個Twisted Web Server來服務於一個目錄是很簡單的:import myresource ##Comment out this line when finished debugging reload(myresource) resource = myresource.MyResource()
% twistd -n web --path /Users/dsp/Sites
Resource rendering
Resource rendering發生在Twisted Web定位到一個葉節點Resource對象來處理web請求(request)的時候。一個Resource的render方法可以做各種事情來產生結果輸出,這些輸出會發送回去給瀏覽器:
Request encoders <用firebug和tcpdump跟蹤,沒發現數據傳輸有壓縮啊,不懂這個是搞什麼的>返回一個字符串
調用request.write("stuff"),想調多少次就調多少次,然後調用request.finish()並return server.NOT_DONE_YES(這是騙人的,因爲實際上我們已經完成這個請求了,但是這樣做還是正確的(腦殘了?不明白啥意思...,直接看下面的例子吧))
請求一個Deferred,返回server.NOT_DONE_YET,然後晚點在Defferred的回調函數裏面調用request.write("stuff")和request.finish()
Resource類,一般是Resource類的子類,有一個方便的render的默認實現。它調用一個名爲self.render_METHOD(這裏的METHOD是用於請求這個resource的HTTP方法)的方法。例如:request_GET,request_POST,request_HEAD,等等。我們推薦大家繼承Resource類來定義自己的resource類,然後實現render_METHOD方法,而不是render方法。注意,對於某些resource,request_POST=request_GET可能會在下述情形下需要:某人想要處理傳給resource的參數,不論他們是使用GET(?foo=bar&baz=quux,等等)還是POST。class Hello(Resource): #isLeaf = True def getChild(self, name, request): if name == '': return self return Resource.getChild(self, name, request) def render_GET(self, request): request.write('Hello,world') request.write('</br>Hello,嚇人的鳥') request.finish() return server.NOT_DONE_YET #return "Hello,World! I am located at %r. %r is followed." % (request.prepath,request.postpath)
對於一個Resource,可以指定用EncodingResourceWrapper+傳入一個存儲encoder factories的list來對它做包裝。當一個請求被處理並且返回一個encoder的時候,encoder factories會被調用。默認情況下,twisted提供了GzipEncoderFactory,GzipEncoderFactory是管理標準的gzip壓縮的。我們可以這樣使用它:
在使用SSL服務、用戶可以影響(influence)內容的Resources上使用壓縮會導致信息泄露,所以對哪個resource使用request encoders要小心抉擇。from twisted.web.server import Site, GzipEncoderFactory from twisted.web.resource import Resource, EncodingResourceWrapper from twisted.internet import reactor class Simple(Resource): isLeaf = True def render_GET(self, request): return "<html>Hello, world!</html>" resource = Simple() wrapped = EncodingResourceWrapper(resource, [GzipEncoderFactory()]) site = Site(wrapped) reactor.listenTCP(8080, site) reactor.run()
注意,在每次request中只有(一個)encoder可以被使用:第一個encoder factory返回一個將被使用的對象,所以指定他們的順序很重要。
Session
Proxies and reverse proxiesHTTP是一個無狀態協議;每次請求-應答(request-response)都被作爲一個獨立單元對待,有別於其他任何URL請求(distinguishable from any other request only by the URL requested)。隨着九十年代中期Cookies的出現,通過給瀏覽器發送一個Cookie,動態web服務器(dynamic web servers)獲得了區分來自不同瀏覽器會話(sessions)的請求(request)的能力。然後瀏覽器在對web server發起請求的時候會同時發送這個cookie,允許服務器跟蹤哪個請求來自哪個瀏覽器會話(browser session)。
Twisted Web對這種瀏覽器跟蹤行爲(browser-tracking behavior)提供了一種抽象,名叫Session的對象。調用request.getSession()會檢測會話的cookie(a session cookie)是否已經設置;如果沒有,它創建一個唯一的session id,創建一個Session對象,存儲在Site裏,然後return它。如果一個session對象已經存在了,那麼同一個session對象會被return.通過這種方式,我們可以把針對這個會話(session)的數據存儲到session對象裏面。
Proxy代理(proxy)是對在clients和其他server之間起中介作用的server的一個籠統的術語。
Twisted支持兩種主要的代理變體(variants):代理(Proxy)和反向代理(ReverseProxy).
ReverseProxyResourceproxy把client發起的請求轉送給目標server. Proxies一般位於內部網絡中作爲客戶端或者位於互聯網出口(Proxies typically sit on the internal network for a client or out on the internet,嘛意思嘛?),它有很多用途,包括緩存,包過濾,審計(auditing),和規避對網頁訪問的本地限制。
這裏是一個簡單但完整的web代理:
運行這個代理,我們可以配置我們的web瀏覽器使用proxy_host_ip:8080作爲代理。這樣做以後,當瀏覽web的時候所有請求都會通過這個代理。(試了一下,在虛擬機centos6裏面運行上述程序,然後在本機win7上設置firefox走centos6提供的代理,真的很好使呢!!)from twisted.web import proxy, http from twisted.internet import reactor class ProxyFactory(http.HTTPFactory): def buildProtocol(self, addr): return proxy.Proxy() reactor.listenTCP(8080, ProxyFactory()) reactor.run()
Proxy繼承自http.HTTPChannel.每個對proxy的客戶端請求會產生一個從proxy到目標server的ProxyRequest來代表客戶端。ProxyRequest使用ProxyClientFactory來爲連接創建一個ProxyClient protocol的實例。ProxyClient繼承自http.HTTPClient. 我們可以繼承ProxyRequest來定製請求該怎樣處理或記日誌。
反向代理代表一個客戶端從其他server重新獲取resources。反向代理一般位於server的內部網絡中用於緩存,應用程序防火牆,和負載均衡。(代理外部網絡上的主機,訪問內部網絡)
這裏是一個基本的反向代理的例子:
把這個代理運行在本地,我們可以用瀏覽器訪問http://localhost:8080,然後反向代理會代理我們對www.yahoo.com的連接。from twisted.internet import reactor from twisted.web import proxy, server site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, '')) reactor.listenTCP(8080, site) reactor.run()
Advanced Configuration在這個例子中我們用server.Site來直接服務一個ReverseProxyResource. 在twisted.web.proxy中當然也有一個ReverseProxy家族的類來映射哪些Proxy家族:
和Proxy一樣,ReverseProxy繼承自http.HTTPChannel. 每個對反向代理的客戶端請求會產生一個對目標server的ReverseProxyRequest. 和ProxyRequest一樣,ReverseProxyRequest使用ProxyClientFactory來爲連接創建一個ProxyClient protocol的實例。
其他代理和反向代理的例子可以在Twisted Web examples(http://twistedmatrix.com/documents/13.0.0/web/howto/using-twistedweb.html)中找到。
Adding ChildrenTwisted Web的non-trivial配置是用Python的配置文件來實現的。這是一個python片段,它建立一個名爲application的變量。一般來說,twisted.application.internet.TCPServer實例用來使得應用程序在一個TCP端口(80,如果需要直接的web服務)做監聽,監聽者是一個twisted.web.server.Site. 結果文件可以用twistd -y來運行。或者一個reactor對象可以直接用來產生一個可執行的腳本。
Site會封裝(wrap)一個Resource對象--the root.from twisted.application import internet, service from twisted.web import static, server root = static.File("/var/www/htdocs") application = service.Application('web') site = server.Site(root) sc = service.IServiceCollection(application) i = internet.TCPServer(80, site) i.setServiceParent(sc)
大部分高級配置可以通過root resource對象找出來(Most advanced configurations will be in the form of tweaking the root resource object.).
一般來說,root的children是基於文件系統的內容的。顯式調用putChild方法也可以改寫文件系統。
這裏是兩個例子。第一個添加一個/doc的child來服務安裝包的文檔,第二個爲CGI腳本添加一個cgi-bin目錄。
from twisted.internet import reactor from twisted.web import static, server root = static.File("/var/www/htdocs") root.putChild("doc", static.File("/usr/share/doc")) reactor.listenTCP(80, server.Site(root)) reactor.run()
from twisted.internet import reactor from twisted.web import static, server, twcgi root = static.File("/var/www/htdocs") root.putChild("cgi-bin", twcgi.CGIDirectory("/var/www/cgi-bin")) reactor.listenTCP(80, server.Site(root)) reactor.run()
Modifying File Resources
Virtual HostsFile resources, root對象或者child對象,有兩個經常需要修改的重要屬性:indexNames和processors. indexNames決定哪些文件會被作爲"index files"對待--當請求一個目錄的時候會呈現出來。processors決定如何處理某些擴展名。
這裏是一個針對兩者的例子,創建一個site,這個site裏面所有.rpy擴展都是資源腳本,並且查找index.rpy文件來相應對目錄的請求。
File對象還有一個叫做ignoreExt的方法。這個方法可以用來爲用戶提供無擴展名的URL,這樣的話實現是可以隱藏的(意思是說URL請求時擴展名是可以隱藏的吧)。這裏是一個例子:from twisted.application import internet, service from twisted.web import static, server, script root = static.File("/var/www/htdocs") root.indexNames=['index.rpy'] root.processors = {'.rpy': script.ResourceScript} application = service.Application('web') sc = service.IServiceCollection(application) site = server.Site(root) i = internet.TCPServer(80, site) i.setServiceParent(sc)
現在,一個/foo的URL可能會被名爲foo.rpy的資源文件服務,如果沒有名爲foo的文件存在的話。from twisted.application import internet, service from twisted.web import static, server, script root = static.File("/var/www/htdocs") root.ignoreExt(".rpy") root.processors = {'.rpy': script.ResourceScript} application = service.Application('web') sc = service.IServiceCollection(application) site = server.Site(root) i = internet.TCPServer(80, site) i.setServiceParent(sc)
Virtual hosting是通過一個特別的資源(resource)實現的,這個資源應該作爲root resource來使用--NameVirtualHost. NameVirtualHost有一個名爲default的屬性,這個屬性持有(hold)默認的站點(website). 如果想要一個其他名字的不同的root,可以使用addHost方法。
from twisted.application import internet, service from twisted.web import static, server, vhost, script root = vhost.NameVirtualHost() # Add a default -- htdocs root.default=static.File("/var/www/htdocs") # Add a simple virtual host -- foo.com root.addHost("foo.com", static.File("/var/www/foo")) # Add a simple virtual host -- bar.com root.addHost("bar.com", static.File("/var/www/bar")) # The "baz" people want to use Resource Scripts in their web site baz = static.File("/var/www/baz") baz.processors = {'.rpy': script.ResourceScript} baz.ignoreExt('.rpy') root.addHost('baz', baz) application = service.Application('web') sc = service.IServiceCollection(application) site = server.Site(root) i = internet.TCPServer(80, site) i.setServiceParent(sc)
Advanced Techniques
因爲配置是一個Python片段,那麼就有可能用上Python的所有能量。下面是一些簡單的例子:
# No need for configuration of virtual hosts -- just make sure # a directory /var/vhosts/<vhost name> exists: from twisted.web import vhost, static, server from twisted.application import internet, service root = vhost.NameVirtualHost() root.default = static.File("/var/www/htdocs") for dir in os.listdir("/var/vhosts"): root.addHost(dir, static.File(os.path.join("/var/vhosts", dir))) application = service.Application('web') sc = service.IServiceCollection(application) site = server.Site(root) i = internet.TCPServer(80, site) i.setServiceParent(sc) # Determine ports we listen on based on a file with numbers: from twisted.web import vhost, static, server from twisted.application import internet, service root = static.File("/var/www/htdocs") site = server.Site(root) application = service.Application('web') serviceCollection = service.IServiceCollection(application) for num in map(int, open("/etc/web/ports").read().split()): serviceCollection.addCollection(internet.TCPServer(num, site))
Running a Twisted Web Server
Serving Flat HTML在很多情況下,我們不會重複的使用twisted.web的常見使用模式。我們會使用Twisted的預先配置好的web server setup.
運行一個Twisted Web server最簡單的方式是使用Twisted Daemonizer. 例如,下面這個命令會運行一個web server服務於一個特定目錄下的靜態文件:
如果我們僅僅想要服務於我們自己的home目錄下的內容,下面這樣就可以了:% twistd web --path /path/to/web/content
我們可以在任何時間回到啓動目錄通過運行下述命令來停止server:% twistd web --path ~/public_html/
還有其他一些配置可以使用:% kill `cat twistd.pid`
--port: 指定web server監控端口,默認是8080
--logfile: 指令日誌文件
完整的選項可以用下面的方式查看:
% twistd web --help
Resource ScriptsTwisted Web Server服務於普通(flat)HTML文件時和它服務於其他任何普通(flat)文件一樣。
資源腳本(a resource script)是一個以.rpy爲後綴名的Python文件,它要求創建一個twisted.web.resource.Resource(的子類)的實例。
資源腳本有三個特別的變量:
__file__: .rpy文件的名字,包含完整的路徑。這個變量是自動定義的並出現在名字空間(namespace)裏。
registry: 類static.Registry的對象。可以一個class爲key來訪問和設置持久數據(persistent data)。
resource: 這個變量必須定義在腳本里,並設置成將要用來渲染頁面的resource實例。
一個非常簡單的資源腳本可能看上去是這樣的:
一個稍微複雜點的訪問一些持久數據的資源腳本,可能看上去是這樣的:from twisted.web import resource class MyGreatResource(resource.Resource): def render_GET(self, request): return "<html>foo</html>" resource = MyGreatResource()
這裏假定我們已經有SillyWeb.Counter模塊了,這個模塊實現如下所述的一些事情:from twisted.web import resource from SillyWeb import Counter counter = registry.getComponent(Counter) if not counter: registry.setComponent(Counter, Counter()) counter = registry.getComponent(Counter) class MyResource(resource.Resource): def render_GET(self, request): counter.increment() return "you are visitor %d" % counter.getValue() resource = MyResource()
class Counter: def __init__(self): self.value = 0 def increment(self): self.value += 1 def getValue(self): return self.value
Web UIs
Spreadable Web ServersNevom框架,作爲Quotient工程的一部分,是一個爲我們應用程序提供web界面的高級系統。Nevow使用Twisted Web但是它自身不是Twisted的一部分。
Twisted Web最有意思的應用之一就是分佈式webserver; 用twisted.spread包做"spreadable"計算,多個server可以回覆相同端口來的請求。在兩個不同的目錄下,運行下面這些命令:
一旦開始運行這些實例,打開http://localhost:8080/your_username.twistd/--我們會看到用--personal選項創建的頁面。這裏發生了什麼呢?我們發出的請求通過一個PB連接被中心(用戶)服務器(central server)轉發給了我們自己的(私有)服務器(your own server).這項技術對於小型“社區”(community)站點非常有用;用這個demo的代碼,我們可以把一個HTTP端口跟很多resources連接,這些resources在同一臺機器上用不同權限運行着,或者在不同本地機器上運行着,或者甚至跨越網絡在其他站點上。% twistd web --user % twistd web --personal [other options, if you desire]
Serving PHP/Perl/CGI默認,一個私人的(persional)server在擁有者的home目錄下監聽一個UNIX socket。--port選項可以使其監聽一個其他的地址,比如在不同的位置的TCP/SSL server或者UNIX server。如果我們用這個選項使一個私有server監聽不同的地址,中心服務器(central(User) server)會找不到它,但是如果用一個使用相同API定製的服務器作爲中心服務器就可以找到。--port選項的另一個用途是使UNIX server變得強壯已應對系統崩潰。如果server崩潰了,UNIX socket遺留在文件系統中,私人(personal)server無法重啓除非這些socket被移除。但是,如果在創建私人server的時提供--port unix:/home/username/.twistd-web-pb:wantPID=1選項,那麼會有一個lockfile來跟蹤server socket是否在使用,並在沒被使用的時候自動刪掉它。
所有跟CGI相關的東西都在twisted.web.twcgi這裏,在這裏我們可以找到我們需要繼承的類,這些類是爲了支持我們喜歡的語言的。如果我們使用的是non-unix操作系統(比如Windows)或者默認的resources給解析器的pathnames不正確,我們需要創建一種屬於我們自己的resource。
下面的片段是一個服務於perl文件的.rpy文件。twisted.web.twcgi有更多跟twisted.web和CGI相關的的例子。
from twisted.web import static, twcgi class PerlScript(twcgi.FilteredScript): filter = '/usr/bin/perl' # Points to the perl parser resource = static.File("/perlsite") # Points to the perl website resource.processors = {".pl": PerlScript} # Files that end with .pl will be # processed b PerlScript resource.indexNames = ['index.pl']
Serving WSGI Applications
Using VHostMonsterWSGI是Web Server Gateway Interface. 它是用於web servers和application servers跟Python web applications通信的一項規範。所有現代Python web frameworks支持WSGI接口。
啓動WSGI應用程序最簡單的方式是使用twistd命令:
% twistd -n web --wsgi=helloworld.application
這裏假定我們有一個名爲application的WSGI應用在我們helloworld模塊/包裏,這個應用看上去可能是這樣的:
上述部分(setup)適合於很多application,這種應用是在站點的根目錄下服務WSGI應用程序。然後,爲了更好的控制,Twisted支持把WSGI applications作爲resources(twisted.web.wsgi.WSGIResource)使用。def application(environ, start_response): """Basic WSGI Application""" start_response('200 OK', [('Content-type','text/plain')]) return ['Hello World!']
這裏是一個作爲站點的root resuorce而提供服務的WSGI application,在下面的tac文件中:
它可以像其他.tac文件一樣運行:from twisted.web import server from twisted.web.wsgi import WSGIResource from twisted.python.threadpool import ThreadPool from twisted.internet import reactor from twisted.application import service, strports # Create and start a thread pool, wsgiThreadPool = ThreadPool() wsgiThreadPool.start() # ensuring that it will be stopped when the reactor shuts down reactor.addSystemEventTrigger('after', 'shutdown', wsgiThreadPool.stop) def application(environ, start_response): """A basic WSGI application""" start_response('200 OK', [('Content-type','text/plain')]) return ['Hello World!'] # Create the WSGI resource wsgiAppAsResource = WSGIResource(reactor, wsgiThreadPool, application) # Hooks for twistd application = service.Application('Twisted.web.wsgi Hello World Example') server = strports.service('tcp:8080', server.Site(wsgiAppAsResource)) server.setServiceParent(application)
因爲WSGI的同步特性,每個應用程序調用(每次請求)是在一個線程裏完成的,結果被寫回給web server. 爲此,需要使用twisted.python.threadpool.ThreadPool實例。% twistd -ny myapp.tac
一種常見的使用是這樣的,一個server(比如Apache)用多個名字反向代理(Apache的話是使用mod_proxy)不同的(可能在不同的機器上)內部web server。簡單(naive)配置容易導致誤解:內部server堅信自己使用"internal-name:port"的方式在運行,並且生成這種效果的URL,而被客戶端收到以後就發現完全錯了。
Apache有ProxyPassReverse指令(directive),但是它不夠好(it is really a hack and is nowhere near comprehensive enough)。作爲替代,在內部web server是Twisted Web的情況下推薦使用VHostMonster.
對Twisted來說,使用VHostMonster很簡單:只是產生一個名爲vhost.rpy文件幷包含下述代碼:
確保web server爲rpy擴展配置了正確的processors("twistd web --path" 產生的web server默認是這麼配置的).from twisted.web import vhost resource = vhost.VHostMonsterResource()
對Apache來說,不要使用下面ProxyPass指令:
而是使用下面這些指令:<VirtualHost ip-addr> ProxyPass / http://localhost:8538/ ServerName example.com </VirtualHost>
這裏是一個使用Twisted Web的反向代理:<VirtualHost ip-addr> ProxyPass / http://localhost:8538/vhost.rpy/http/example.com:80/ ServerName example.com </VirtualHost>
from twisted.application import internet, service from twisted.web import proxy, server, vhost vhostName = 'example.com' reverseProxy = proxy.ReverseProxyResource('internal', 8538, '/vhost.rpy/http/'+vhostName+'/') root = vhost.NameVirtualHost() root.addHost(vhostName, reverseProxy) site = server.Site(root) application = service.Application('web-proxy') sc = service.IServiceCollection(application) i = internet.TCPServer(80, site) i.setServiceParent(sc)
Rewriting URLs
Knowing When We're Not Wanted有時候在傳遞Request對象之前修改它的內容是很方便(convenient)的。因爲改寫URL是很常見的,twisted.web.rewrite模塊跟Apache的mod_rewrite類似。這個模塊通過封裝(wrapping)twisted.web.rewrite.RewriterResource得到的resource提供rewrite規則來完成需求。改寫規則是接受request對象的函數,同時可能會修改request對象。在所有改寫規則運行後,url處理鏈路上的子處理過程會繼續進行,並好像那個封裝後的resource(注意,不是RewriterResource)就是下一個child過程一樣(擦,這裏不太明白,反正就是這麼個意思)。
這裏是一個例子,只使用了Twisted提供的一個的規則:
這會導致URL "/~foo/bar.html"被當成"/users/foo/bar.html". 如果把default的users child設置爲distrib.UserDirectory,那麼就給出了一個類似於"自第一個NCSA服務器以來web server的經典配置"的配置。default_root = rewrite.RewriterResource(default, rewrite.tildeToUsers)
As-Is Serving有時,知道對端何時斷開了鏈接是很有用的。這裏是一個這樣的例子:
它允許我們對日誌文件做統計來看有多少用戶在僅僅十秒鐘之後就斷開連接(are frustrated,受挫??)了。from twisted.web.resource import Resource from twisted.web import server from twisted.internet import reactor from twisted.python.util import println class ExampleResource(Resource): def render_GET(self, request): request.write("hello world") d = request.notifyFinish() d.addCallback(lambda _: println("finished normally")) d.addErrback(println, "error") reactor.callLater(10, request.finish) return server.NOT_DONE_YET resource = ExampleResource()
有時,我們想直接發送頭部和狀態碼。當然我們可以用ResourceScript來做這個,一個更簡單的方式是用ASISProcessor. 要使用它,舉個例子,需要把它作爲一個processor添加進來處理.asis後綴的文件。這裏是一個樣本文件:
HTTP/1.0 200 OK Content-Type: text/html Hello world