Sypex Geo API на go

Kate

Administrator
Команда форума
Sypex Geo — периодически обновляемая база данных для определения местоположения по IP-адресу. Распространяется по лицензии BSD, можно использовать в коммерческих продуктах. Подробно про нее в публикациях автора @zapimir: Sypex Geo — быстрое определение города по IP, В Sypex Geo добавлена привязка к API ВКонтакте.

На сайте разработчиков, кроме собственного клиента, есть несколько реализаций API на разных ЯП. На PHP доступны бандл для Symphony 2, расширение для Laravel и Yii.

В рамках хардкорного обучения языку golang я написал к Sypex Geo 2.2 своё api.

На гите лежит версия alfa, брать с осторожностью. Все баги и косяки, конечно, на моей совести обычного PHP-шника, пишу, чтобы умерло через 30 сек (привет, Серёга C#), и в прод без проверки я б пока не тянул.

Как пользоваться​

Ниже приведен пример готового http-сервера, который по запросу http://localhost:8080/ip=2.4.30.5 выдаст JSON-объект с городом, страной и регионом.

{
"city": {
"id": 2992166,
"name_ru": "Монпелье",
"name_en": "Montpellier",
"lat": 43.61092,
"lon": 3.87723,
"region_seek": 0
},
"country": {
"id": 74,
"iso": "FR",
"name_ru": "Франция",
"name_en": "France"
},
"region": {
"id": 3007670,
"iso": "FR-K",
"name_ru": "Лангедок-Руссильон",
"name_en": "Region Languedoc-Roussillon"
}
}
Для работы надо скачать с сайта разрабов последнюю версию Sypex Geo City. Закиньте её в каталог с программой (или куда-то ещё, но тогда передайте серверу полный путь с флагом -d 'full/path/SxGeoCity.dat')

package main

import (
"encoding/json"
"flag"
"fmt"
"github.com/barsuk/sxgeo" // сама библиотека, о ней дальше
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
)

func main() {
var ip string
var endian bool
var setEndian int
var dbPath string
flag.StringVar(&ip, "ip", "", "ip address to convert")
flag.IntVar(&setEndian, "se", 0, "set endianness")
flag.BoolVar(&endian, "e", false, "check endianness of your system")
flag.StringVar(&dbPath, "d", "./SxGeoCity.dat", "path to SxGeoCity.dat file")
flag.Parse()

// можно передать флаг endian и проверить, как скомпилирована ваша система: little/big Endian
if endian {
sxgeo.DetectEndian()
os.Exit(0)
}

// можно установить правильный вариант архитектуры. В случае ошибки библиотека должна выдавать чушь, или я что-то забыл..
if setEndian > 0 {
sxgeo.SetEndian(sxgeo.BIG)
fmt.Printf("host binary endian set to %s\n", sxgeo.Endian())
}

// для работы надо считать файл SxGeoCity.dat в память.
if _, err := sxgeo.ReadDBToMemory(dbPath); err != nil {
log.Fatalf("error: cannot read database file: %v", err)
}

// можно и не запускать сервер, а использовать прогу из командной строки
// я использовал этот вариант для проверки корректности очередной обновлённой базы от ребят из Sypex Geo.
if len(ip) > 0 {
city, err := sxgeo.GetCityFull(ip)
if err != nil {
fmt.Printf("error: %v", err)
os.Exit(1)
}

enc, err := json.Marshal(city)
if err != nil {
fmt.Printf("error: %v", err)
os.Exit(1)
}

fmt.Printf("%s\n", enc)
os.Exit(0)
}
r := gin.New()
r.GET("/", sxgeoHandler)
erro := r.Run(fmt.Sprintf(":%d", 8080))
if erro != nil {
log.Fatalf("gin felt: %v", erro)
}
}

// обработчик запроса с простой проверкой
func sxgeoHandler(c *gin.Context) {
// проверим на длину
ip := c.Query("ip")
if len(ip) < 4 {
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "give me an IP, please"})
return
}
fmt.Printf("IP: %s\n", ip) // отложим в лог запрос

city, err := sxgeo.GetCityFull(ip) // вызываем библиотечный метод
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.IndentedJSON(http.StatusAccepted, city)
}
Ещё один пример готового кода для использования лежит в sxgeo_test.go.

Язык go кроссплатформенный, но в Windows я не проверял — к сожалению, у меня её нет. Если кто попробует, пишите в комментариях.

Вдруг кто-нибудь совсем не знает го, но забрёл на геоопределение:

Немного об устройстве sxgeo​

Модуль sxgeo работает с файлом в формате SxGeo v2.2. Разработчики очень подробно специфицировали формат, за что им большое человеческое спасибо.

Формат базы данных предполагает зависимость от Byte Order системы: LittleEndian или BigEndian. Поэтому первое, что делаем — устанавливаем или определяем его, иначе получим чушь на выходе распаковки.

Определитель этого параметра системы в sxgeo использует пакет unsafe и намекает на осторожность. Ещё большее опасение должен вызвать источник этого метода, Stackoverflow. Пока проблем не было, но вдруг что. Во избежание, переменная hbo (Host Byte Order) сделана глобальной, и порядок байтов можно определить другим, своим и безопасным способом.

Следующий этап — распаковка БД в память. Родной php-клиент предоставляет возможность или считать всю базу в память, или распаковывать постепенно. В моих условиях памяти было достаточно, а свободного времени мало, поэтому всё в память. Так и быстрее работать будет.

За распаковку отвечает ReadDBToMemory. Функция делает то же, что и конструктор класса в родном клиенте — считывает SxGeo.dat и разбивает бинарную запись в структуры языка: нескольких слайсов байт с городами, регионами, собственно IP, плюс метаданные.

Всё, что упаковано в базу для IP, выдаёт метод модуля — GetCityFull. Внутри него две функции — Seek(ip), определяющая необходимое смещение в БД, и parseFullCity, которая прочитает набор байт после смещения и превратит их в человекочитаемую структуру.

Функция Seek — перевод get_num($ip) из php-клиента. Она отсеет мультикасты и loopback, проверит IP на IPv4-шность и проверит, что IP попадает в диапазон из метаданных базы. Потом вызовет searchDb — этот монстрик и найдёт точное смещение нужной последовательности байтов.

Функция parseFullCity прочитает байты и распарсит их в один из двух наборов: либо страну и пустые регион с городом (мне там почему-то попадалась только одна такая страна), либо полный нормальный комплект. Самая ответственная работа лежит на функции unpack — она из прочтённого слайса байтов в цикле вычленит всё, что предполагается в метаинформации. Тут-то и пригодится правильно определённый byte order вашей системы.

Что дальше​

Скорее всего, в этом году библиотека дойдёт до какого-нибудь прода, но уже под другим именем. На гите всё останется в том виде, как есть сейчас. Единственное, что может добавиться — погоняю по скорости с клиентом на PHP.

Сравнивая работу с программами на PHP и на go, отмечу, что в go мне удобнее и понятнее работается с бинарными данными. В PHP оно всё какое-то неродное, что ли.

Цель, которую себе ставил, достигнута — определённый барьер сложности на go взят. Надеюсь, кому-то этот код тоже пригодится.

Источник статьи: https://habr.com/ru/post/557822/
 
Сверху