基于纯真ip库以及openresty 模仿实现类似搜狐ip获取区域的服务
最近搜狐的ip获取区域的很不稳定,所以参考搜狐的模式基于openresty+纯真ip库+ golang rest 服务的模式,实现了一个类似的参考
相关说明
纯真ip是一个免费的,准确度也比较高的离线ip地址查询库,当然是需要自己的解析方法,这个我直接使用了网上大家写好的基于golang
的实现,同时因为基于文件查询,所以需要cache,这个参考了 https://github.com/chetansurwade/geoip2-rest-api-golang的一个实现
geoip2-rest-api-golang 基于gin以及geoip2 实现查询,但是因为geoip2 目前的下载以及对于国内的处理不是很好,所以使用了纯真ip库替换
对于cache 部分geoip2-rest-api-golang的实现基于社区的github.com/gin-contrib/cache 还是比较方便的
代码说明
纯真ip库的处理基于https://github.com/freshcn/qqwry的一个实现,直接复用了加载以及查询处理,整体就是一个代码的聚合。。。。
- main.go
代码入口,核心功能都在里边,cache 默认是分钟级别的,支持多ip 的查询处理,目前纯真ip库使用了5.30号的(目前最新),对于其他
更新自己处理下
package main
import (
"net"
"net/http"
"time"
"github.com/gin-contrib/cache"
"github.com/gin-contrib/cache/persistence"
"github.com/gin-gonic/gin"
)
// UserIP for user's ip info
type UserIP struct {
UserIP []string `form:"ip" json:"ip" xml:"ip" binding:"required"`
}
// Cors for cross origin resource sharing in header
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
if c.Request.Method != "OPTIONS" {
c.Next()
} else {
c.Writer.Header().Add("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Writer.Header().Add("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
c.Writer.Header().Add("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Writer.Header().Add("Content-Type", "application/json")
c.AbortWithStatus(http.StatusOK)
}
}
}
// main function containing the routes and db initialization
func main() {
// set gin to production mode
gin.SetMode(gin.ReleaseMode)
IPData.FilePath = "qqwry.dat"
IPData.InitIPData()
qqWry := NewQQwry()
r := gin.Default()
r.Use(Cors())
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "API for GeoIP details",
"status": true,
})
})
// for faster response an inmemory cache store of routes
store := persistence.NewInMemoryStore(time.Second)
// Supports any method GET/POST along with any content-type like json,form xml
// change the time.Minute to your choice of duration
// supports a single ip as input, for example http://localhost:8080/geoip?ip=YOUR-IP or http://localhost:8080/geoip?ip=YOUR-IP&ip=YOUR-IP2&ip=YOUR-IP3
r.Any("/geoip", cache.CachePage(store, time.Minute, func(c *gin.Context) {
var usrIP UserIP
err := c.ShouldBind(&usrIP)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "bad request",
"status": false,
})
return
}
userIPs := usrIP.UserIP
if len(userIPs) < 1 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Kindly specify the ip or array of ips",
"status": false,
})
return
}
var results []interface{}
for _, userIP := range userIPs {
data := make(map[string]interface{}, 0)
data["ip"] = userIP
ip := net.ParseIP(userIP)
if ip == nil {
data["error"] = true
results = append(results, data)
continue
}
cityRecord := qqWry.Find(userIP)
if len(cityRecord.Country) > 0 {
data["city"] = cityRecord.Country
data["area"] = cityRecord.Area
results = append(results, data)
}
}
c.JSON(http.StatusOK, gin.H{
"result": results,
"status": true,
})
return
}))
r.Run(":8080")
return
}
- openresty 集成
为了保持与搜狐ip接口的一直,使用了openresty 进行api 聚合处理,当然可以直接在代码处理,但是这样就不灵活了
nginx 参考配置
基本原理就是基于proxy 代理获取ip 服务的golang服务,同时模仿搜狐cityjson接口的基于ngx.location.capture 的,就不用
使用rest 请求类库了,简单了好多
worker_processes 1;
user root;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
lua_need_request_body on;
gzip on;
resolver 127.0.0.11 ipv6=off;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
upstream geoips {
server app:8080 weight=20 max_fails=2 fail_timeout=30s;
}
server {
listen 80;
charset utf-8;
default_type text/html;
location / {
default_type text/plain;
index index.html;
}
location /cityjson{
default_type application/javascript;
content_by_lua_block {
local headers=ngx.req.get_headers()
local cjson = require("cjson")
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
local res = ngx.location.capture('/geoip?ip='..ip)
local info =[[var returnCitySN = {"cip":"]]..""..[[", "cid": "110000"]] ..[[, "cname":"]]..""..[["}; ]]
if res.status==200 and res.body ~=nil then
local ipadd = cjson.decode(res.body)
local cipinfo = {
cip = ipadd["result"][1].ip,
cname = ipadd["result"][1].city
}
info =[[var returnCitySN = {"cip":"]]..cipinfo.cip..[[", "cid": "110000"]] ..[[, "cname":"]]..cipinfo.cname..[["}; ]]
end
ngx.say(info)
}
}
location /geoip {
proxy_pass http://geoips;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
docker 镜像
基于多阶段构建
- dockerfile
FROM golang:1.13-alpine AS build-env
WORKDIR /go/src/app
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn
COPY . .
RUN apk update && apk add git \
&& go build -o qqwry-rest
FROM alpine:latest
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY --from=build-env /go/src/app/qqwry-rest .
COPY qqwry.dat .
EXPOSE 8080
ENTRYPOINT [ "./qqwry-rest" ]
说明
以上是一个简单的代码集成,主要是不希望太依赖搜狐(还是不想花钱),而且很多时候有一个本地的兜底服务,还是比较好的
至少有可选的方案
参考资料
http://www.cz88.net/ip
https://pv.sohu.com/cityjson
https://github.com/WisdomFusion/qqwry.dat
https://github.com/freshcn/qqwry
https://github.com/chetansurwade/geoip2-rest-api-golang
https://github.com/rongfengliang/qqwry-rest-api