Sypex Geo API на go

от автора

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

  1. Установите го по инструкции: https://golang.org/doc/install

  2. Создайте каталог ~/go/src/sxgeo и файл main.go в нём.

  3. В файл main.go скопируйте код сервака выше.

  4. Из ~/go/src/sxgeo запускайте go run main.go -d 'path/to/SxGeo.dat'

  5. Пользуйтесь: 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *