系统需求要在用户注册的时候,自动获取用户所在的区域,最简单的,不通过浏览器或微信获取位置权限的方式,就是通过解析IP归属地。IP归属地查询很简单,通过高德IP定位或者其他服务商提供的API就可以实现,但是在实施过程中,发现Docker内部的Web程序,获取的IP都是Docker容器的IP🤦‍♂️.

这是在使用Docker Compose后者Docker Swarm 的时候的一个Bug,也可以说是一个Docker network 的可预期行为,一个Container就是相当于一台机器,我们是以Docker network的身份(IP)去访问服务,其remote_addr也就是Docker内的IP了。可以参考Real remote IP adress

那么要如何才能获得客户端的真实IP呢,方法也很简单,那就是使用一个多一层的proxy(如果是直接访问Docker web服务器的情况下),如果已经是由其他Web服务器转发过来的,就更简单了。

一般的,在代理服务器中有设置(以Nginx为例):

1
2
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

那么在我们容器内部,这个X-Real-IP header是正确的IP,这是可预期的,那么该如何获得真实IP呢,这里我是通过Cookiecutter Django建立的工程,使用的是Caddy作为Web服务器,在转发给Django容器之前,设置Caddyfile有

1
2
header_upstream X-Real-IP {>X-Real-IP}
header_upstream X-Forwarded-For {>X-Forwarded-For}

这样,我们在Django中就可以通过X-Real-IP或者是X-Forwarded-For拿到正确的IP了,注意这个时候在web应用程序中,request.META[‘REMOTE_ADDR’]是Caddy容器的内部IP,X-Real-IPX-Forwarded-For是如果想通过默认的request.META['REMOTE_ADDR'],可以添加一个Middleware,放在settings.MIDDLEWARE的第一个位置即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
class RestoreRemoteAddrMiddleware:
"""
Docker cover X-Forwarded-For header user internal ip, so use X-Real-IP to set META['REMOTE_ADDR']
"""

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
remote_addr = request.META.get('HTTP_X_REAL_IP', None)
if remote_addr:
request.META['REMOTE_ADDR'] = remote_addr
return self.get_response(request)
1
2
3
4
MIDDLEWARE = [
'your.module.RestoreRemoteAddrMiddleware',
# other middlewarees
]