Sanic获取客户端的真实IP
自己之前写的DDNS脚本最近发现不好使了,查看原因发现是之前获取ip的服务大幅减少免费的请求次数了,我的脚本无法拿到自己的IP了。网上还有很多免费的获取ip的共公服务, 但可能还会发生这样的事,反正我公网上也有服务,加个获取IP地址的接口就可以。
获取ip的API使用python的Sanic框架来实现。
Nginx 部署
官方文档
https://sanicframework.org/zh/guide/advanced/proxy-headers.html
有两种方式获得真实ip
1.FORWARDED
官方文档 https://sanicframework.org/zh/guide/deployment/nginx.html
from sanic import Sanic
from sanic.response import text
app = Sanic("proxied_example")
app.config.FORWARDED_SECRET = "YOUR SECRET"
@app.get("/")
def index(request):
# 此处将会显示公网IP
return text(
f"{request.remote_addr} connected to {request.url_for('index')}\n"
f"Forwarded: {request.forwarded}\n"
)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, workers=8, access_log=False)
nginx配置
server {
server_name example.com;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
location / {
proxy_pass http://127.0.0.1:8000;
# Allow fast streaming HTTP/1.1 pipes (keep-alive, unbuffered)
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_buffering off;
# Proxy forwarding (password configured in app.config.FORWARDED_SECRET)
proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\"";
# Allow websockets and keep-alive (avoid connection: close)
proxy_set_header connection "upgrade";
proxy_set_header upgrade $http_upgrade;
}
}
nginx中的
要和sanic的配置app.config.FORWARDED_SECRET一致
新建 /etc/nginx/conf.d/forwarded.conf
# RFC 7239 Forwarded header for Nginx proxy_pass
# Add within your server or location block:
# proxy_set_header forwarded "$proxy_forwarded;secret=\"YOUR SECRET\"";
# Configure your upstream web server to identify this proxy by that password
# because otherwise anyone on the Internet could spoof these headers and fake
# their real IP address and other information to your service.
# Provide the full proxy chain in $proxy_forwarded
map $proxy_add_forwarded $proxy_forwarded {
default "$proxy_add_forwarded;by=\"_$hostname\";proto=$scheme;host=\"$http_host\";path=\"$request_uri\"";
}
# The following mappings are based on
# https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
map $remote_addr $proxy_forwarded_elem {
# IPv4 addresses can be sent as-is
~^[0-9.]+$ "for=$remote_addr";
# IPv6 addresses need to be bracketed and quoted
~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
# Unix domain socket names cannot be represented in RFC 7239 syntax
default "for=unknown";
}
map $http_forwarded $proxy_add_forwarded {
# If the incoming Forwarded header is syntactically valid, append to it
"~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";
# Otherwise, replace it
default "$proxy_forwarded_elem";
}
然后访问api就可以通过request.remote_addr
拿到客户端的ip了。
2.配置REAL_IP_HEADER
第一种方式适用于直连的方式,如果api服务套了CDN的话这样就不好使了,拿到的其实是CDN节点的IP。这时候就用第二种方式。
from sanic import Sanic
from sanic.response import json
app = Sanic()
app.config.REAL_IP_HEADER = "cf-connecting-ip"
@app.get("/")
def index(request):
# 此处将会显示公网IP
return json({"ip": request.remote_addr})
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, workers=8, access_log=False)
REAL_IP_HEADER根据实际情况配置,不同的CDN厂商可能放置IP的header不同
nginx的话就正常配置proxy_pass就行,不需要proxy_set_header forwarded
server {
server_name example.com;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header connection "upgrade";
proxy_set_header upgrade $http_upgrade;
}
}
关于Sanic
Sanic使用python的asyncio,宣称高性能,我用压测工具测试了一下我这个API接口,服务器是个1core的KVM云主机,1k并发的毫无问题,调成1w的时候就有很多请求失败了。 确实性能还不错。
Sanic和flask的写法上很像,使用flask项目的目录结构来写Sanic项目也没啥不妥
api
├── app.py
├── config.json
├── utils
│ └── response.py
└── views
├── __init__.py
└── ip.py