首先,nginx必定會設置一個Header傳送過來真實的IP
nginx.conf
server { proxy_set_header X-Real-IP $remote_addr; location / { proxy_pass http://192.168.7.206:8888/; } }
然后,tornado的Handler里面進行如下處理:
class Handler(BaseRequestHandler): def post(self): self.get() def get(self): remote_ip = self.request.headers.get("X-Real-Ip", "")
此處,下面兩行其實是沒有任何區別的,ng的header到了tornado會做一次統一整理(具體可以參考class HTTPHeaders)。
ip = self.request.headers.get("X-Real-Ip", "") ip = self.request.headers.get("X-Real-IP", "")
然后,就可以了。
但是,這里為啥還有但是呢?這段日志還有奇怪的地方,它記錄的還不對,這個是tornado的默認日志。
[I 150806 14:54:21 web:1811] 200 POST / (192.168.7.62) 2.74ms
看tornado==v4.2.0的代碼web.py文件某處一定有如下類似的部分:
def log_request(self, handler): """Writes a completed HTTP request to the logs. By default writes to the python root logger. To change this behavior either subclass Application and override this method, or pass a function in the application settings dictionary as ``log_function``. """ if "log_function" in self.settings: self.settings["log_function"](handler) return if handler.get_status() < 400: log_method = access_log.info elif handler.get_status() < 500: log_method = access_log.warning else: log_method = access_log.error request_time = 1000.0 * handler.request.request_time() log_method("%d %s %.2fms", handler.get_status(), handler._request_summary(), request_time)
這塊其實就是日志的實現,_request_summary 又是啥?
def _request_summary(self): return "%s %s (%s)" % (self.request.method, self.request.uri, self.request.remote_ip)
后續繼續看,發現,self.request.remote_ip這個其實就是tornado針對客戶端IP做的快捷訪問,這里為啥不對呢?
原來要在初始化listen的時候設置一個參數:
def main():
app = Application() app.listen(options.port, xheaders = True)
產生的效果就是
[I 150806 13:52:53 web:1908] 200 POST / (192.168.7.214) 1.86ms
真實IP反映到Tornado的基礎日志里了。
具體實現追蹤可以查看 tornado\httpserver.py 中的相關代碼。
class _ServerRequestAdapter(httputil.HTTPMessageDelegate): def headers_received(self, start_line, headers): if self.server.xheaders: self.connection.context._apply_xheaders(headers) class _HTTPRequestContext(object): def _apply_xheaders(self, headers): """Rewrite the ``remote_ip`` and ``protocol`` fields.""" # Squid uses X-Forwarded-For, others use X-Real-Ip ip = headers.get("X-Forwarded-For", self.remote_ip) ip = ip.split(',')[-1].strip() ip = headers.get("X-Real-Ip", ip) if netutil.is_valid_ip(ip): self.remote_ip = ip # AWS uses X-Forwarded-Proto proto_header = headers.get( "X-Scheme", headers.get("X-Forwarded-Proto", self.protocol)) if proto_header in ("http", "https"): self.protocol = proto_header def _unapply_xheaders(self): """Undo changes from `_apply_xheaders`. Xheaders are per-request so they should not leak to the next request on the same connection. """ self.remote_ip = self._orig_remote_ip self.protocol = self._orig_protocol