前情提要
最近要把给部署的机器都升级,从Debian9升级到Debian10. Debian9的默认python版本是python3.5,而Debian10的默认版本是python3.7,为了部署方便和日后升级,我把要部署的Sanic应用也从python3.5升级到python3.7了。
升级完之后,在测试的时候却发现,request.remote_addr
不能像往常一样获取客户端IP了,经过一番折腾,终于搞定。
环境
我们的应用架构较为简单,GLB --> Nginx --> Sanic.
-
GLB:
Google Load Balancing
. 谷歌的全球范围的负载均衡器,我们通过GLB,来进行路由转发、流量分发到相应的backend
。每个backend
是一个实例组,实例组下面有若干个VM实例(VM,既虚拟机)。 -
Nginx:不多说。每个实例运行Nginx进行机器内部的负载均衡,把流量分发到机器内部的若干个Sanic进程
-
Sanic:一个python高性能web异步框架,框架特性开箱即用,无需像Flask一样需要部署
Gunicone
或者uWISG
之类的Web Server。此时升级是从Sanic 19.3.1
升级到Sanic==21.6.0
Nginx 配置如下
location / { proxy_pass_header Server; proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; real_ip_header X-Forwarded-For; real_ip_recursive on; proxy_pass http://backend_hosts; proxy_http_version 1.1; proxy_set_header Connection ""; }
正文
源码解析
查看Sanic源码,发现从
Sanic 19.3.1
升级到Sanic==21.6.0
后,request.remote_addr
代码有所更改。remote_addr
# Sanic 19.3.1 @property def remote_addr(self): """Attempt to return the original client ip based on X-Forwarded-For. :return: original client ip. """ if not hasattr(self, "_remote_addr"): forwarded_for = self.headers.get("X-Forwarded-For", "").split(",") remote_addrs = [ addr for addr in [addr.strip() for addr in forwarded_for] if addr ] if len(remote_addrs) > 0: self._remote_addr = remote_addrs[0] else: self._remote_addr = "" return self._remote_addr
# Sanic 21.6.0 @property def remote_addr(self) -> str: """ Client IP address, if available. 1. proxied remote address `self.forwarded['for']` 2. local remote address `self.ip` :return: IPv4, bracketed IPv6, UNIX socket name or arbitrary string :rtype: str """ if not hasattr(self, "_remote_addr"): self._remote_addr = str( self.forwarded.get("for", "") ) # or self.ip return self._remote_addr
从源码看来,在
19.3.1
版本获取ip时,是直接从http header获取X-Forwarded-For
字段,并且如果X-Forwarded-For
里面存在多个ip,取第一个。而升级到了21.6.0
的时候,则多了一些处理。forwarded
@property def forwarded(self) -> Options: """ Active proxy information obtained from request headers, as specified in Sanic configuration. Field names by, for, proto, host, port and path are normalized. - for and by IPv6 addresses are bracketed - port (int) is only set by port headers, not from host. - path is url-unencoded Additional values may be available from new style Forwarded headers. :return: forwarded address info :rtype: Dict[str, str] """ if self.parsed_forwarded is None: self.parsed_forwarded = ( parse_forwarded(self.headers, self.app.config) # 注释1 or parse_xforwarde