基于纯真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

原文地址:https://www.cnblogs.com/rongfengliang/p/13048504.html