實現(xiàn)代理的方式很多種,流行的web服務(wù)器也大都有代理的功能,比如http://www.tornadoweb.cn用的就是nginx的代理功能做的tornadoweb官網(wǎng)的鏡像。
最近,我在開發(fā)一個移動運用(以下簡稱APP)的后臺程序(Server),該運用需要調(diào)用到另一平臺產(chǎn)品(Platform)的API。對于這個系統(tǒng)來說,可選的一種實現(xiàn)方式方式是APP同時跟Server&Platform兩者交互;另一種則在Server端封裝掉Platform的API,APP只和Server交互。顯然后一種方式的系統(tǒng)架構(gòu)會清晰些,APP編程時也就相對簡單。那么如何在Server端封裝Platform的API呢,我首先考慮到的就是用代理的方式來實現(xiàn)。碰巧最近Tornado郵件群組里有人在討論using Tornado as a proxy,貼主提到的運用場景跟我這碰到的場景非常的相似,我把原帖的代碼做了些整理和簡化,源代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
# -*- coding: utf-8 -*- # # Copyright(c) 2011 Felinx Lee & http://feilong.me/ # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web import tornado.httpclient from tornado.web import HTTPError, asynchronous from tornado.httpclient import HTTPRequest from tornado.options import define, options try : from tornado.curl_httpclient import CurlAsyncHTTPClient as AsyncHTTPClient except ImportError: from tornado.simple_httpclient import SimpleAsyncHTTPClient as AsyncHTTPClient define( "port" , default = 8888 , help = "run on the given port" , type = int ) define( "api_protocol" , default = "http" ) define( "api_host" , default = "feilong.me" ) define( "api_port" , default = "80" ) define( "debug" , default = True , type = bool ) class ProxyHandler(tornado.web.RequestHandler): @asynchronous def get( self ): # enable API GET request when debugging if options.debug: return self .post() else : raise HTTPError( 405 ) @asynchronous def post( self ): protocol = options.api_protocol host = options.api_host port = options.api_port # port suffix port = " " if port == " 80 " else " : % s" % port uri = self .request.uri url = "%s://%s%s%s" % (protocol, host, port, uri) # update host to destination host headers = dict ( self .request.headers) headers[ "Host" ] = host try : AsyncHTTPClient().fetch( HTTPRequest(url = url, method = "POST" , body = self .request.body, headers = headers, follow_redirects = False ), self ._on_proxy) except tornado.httpclient.HTTPError, x: if hasattr (x, "response" ) and x.response: self ._on_proxy(x.response) else : logging.error( "Tornado signalled HTTPError %s" , x) def _on_proxy( self , response): if response.error and not isinstance (response.error, tornado.httpclient.HTTPError): raise HTTPError( 500 ) else : self .set_status(response.code) for header in ( "Date" , "Cache-Control" , "Server" , "Content-Type" , "Location" ): v = response.headers.get(header) if v: self .set_header(header, v) if response.body: self .write(response.body) self .finish() def main(): tornado.options.parse_command_line() application = tornado.web.Application([ (r "/.*" , ProxyHandler), ]) http_server = tornado.httpserver.HTTPServer(application) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() if __name__ = = "__main__" : main() |
運行上面的代碼后,訪問 http://localhost:8888/ 將會完整顯示飛龍博客的首頁,即代理訪問了http://feilong.me/的內(nèi)容。
我考慮用程序的方式來做代理而不是直接用Nginx來做代理,其中一點是考慮到用程序可以很容易的控制Platform的哪些API是需要代理的,而哪些是要屏蔽掉的,還有哪些可能是要重寫的(比如Server的login可能不能直接代理Platform的login,但卻要調(diào)用到Platform的login API)。
以上這段代碼只是做了簡單的頁面內(nèi)容代理,并沒有對頁面進行進一步的解析處理,比如鏈接替換等,這些就交個有興趣的朋友去開發(fā)了。基于以上這段代碼,將其擴展一下,是完全可以實現(xiàn)一個完整的在線代理程序的。
這段代碼我已放到了我的實驗項目里,見https://bitbucket.org/felinx/labs,我將會放更多類似于這樣的實驗性質(zhì)的小項目到這個repository里來,有興趣的朋友可以關(guān)注一下。
轉(zhuǎn)載請注明出處:http://feilong.me/2011/09/tornado-as-a-proxy