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

  • 相关阅读:
    Tomcat架构解析(五)-----Tomcat的类加载机制
    session与cookie
    freemarker常用标签解释遍历
    freemarker常用标签解释三
    freemarker常用标签解释二
    freemarker常用标签解释
    禁止浏览器自动填充
    使用cookie实现自动登录
    长连接和短连接
    filter防止xxs攻击
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/13048504.html
Copyright © 2020-2023  润新知