最近搜狐的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