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 я не проверял — к сожалению, у меня её нет. Если кто попробует, пишите в комментариях.
Вдруг кто-нибудь совсем не знает го, но забрёл на геоопределение:
Инструкция для Ubuntu
-
Установите го по инструкции: https://golang.org/doc/install
-
Создайте каталог ~/go/src/sxgeo и файл main.go в нём.
-
В файл main.go скопируйте код сервака выше.
-
Из ~/go/src/sxgeo запускайте
go run main.go -d 'path/to/SxGeo.dat' -
Пользуйтесь: http://localhost:8080/ip={IPv4 строкой типа 8.8.8.8}
Немного об устройстве 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/
Добавить комментарий