2013年10月4日星期五

Tornado 做简易反向代理

在用 Tornado 建站,打算将 UI 渲染和后端操作独立为两个进程,用 API 互相操作数据。
我的做法是前后端可以同时被外部访问,类似于这样的 nginx 配置:
location / {
    proxy_pass http://test_tornado;
    include proxy_params;
}
location ~ ^/api {
    proxy_pass http://test_tornado_backend;
    include proxy_params;
}
在本地测试的时候是没有 nginx 的,是让 Tornado 直接跑。
于是在前端的代码中绑定 /api 到下面这个 Handler:
import tornado
import tornado.gen
import tornado.options
import tornado.httpclient
import tornado.web


class APIProxyHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        yield self.post()

    @tornado.gen.coroutine
    def post(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        request_headers = self.request.headers.copy()
        request_headers.add('X-Forwarded-For', self.request.remote_ip)
        request_options = {
            'url': '?'.join([tornado.options.options.backend]+self.request.uri.split('?', 1)[1:]),
            'method': self.request.method,
            'headers': request_headers,
            'body': self.request.body,
            'user_agent': self.request.headers.get('User-Agent', 'Tornado/%s' % tornado.version),
            'connect_timeout': 60,
            'request_timeout': 60,
            'follow_redirects': False,
            'use_gzip': True,
            'allow_nonstandard_methods': True,
            'allow_ipv6': True,
        }
        try:
            response = yield http_client.fetch(tornado.httpclient.HTTPRequest(**request_options))
        except tornado.httpclient.HTTPError as e:
            response = e.response
        self.set_status(response.code, response.reason)
        existed_headers = set()
        for header_name, header_value in response.headers.get_all():
            if header_name.lower() in ('content-encoding', 'content-length'):
                continue
            elif header_name in existed_headers:
                self.add_header(header_name, header_value)
            else:
                self.set_header(header_name, header_value)
                existed_headers.add(header_name)
        self.finish(response.body or None)