ipgeobase в Nginx

    Когда возникает задача — по адресу посетителя получать его город и налоговый (автомобильный) код региона, кажется — да это же просто, в инете полно таких штук!
    А потом смотришь: одни платные, другие нельзя у себя развернуть, третьи можно, но это ресурсозатратно, четвертые о регионах РФ ничего не знают…
    И тут на помощь спешит больной мозг программиста с навязчивой идеей: «Нет у других — сделай сам»



    Как только начинаешь мыслить в таком ключе — вот же, у nginx есть отличный модуль geoip, который «не только лишь быстрый, но и оптимизирован до невозможности». Но вот незадача, ни один из известных форматов баз (MaxMind, Sypex, ipgeobase) он не понимает.

    Пара часов в обнимку с питоном и вот уже есть неплохой конвертер, выцепляющий всё что нам нужно с сайта ipgeobase.ru.
    (Да, были слухи, что там всех поувольняли уж полгода как, но базы исправно обновляются, что не может не радовать)

    И чтобы не было опасений, прокомментирую код ниже (если неинтересно, можно сразу листать к настройке)

    Код



    1. Скачиваем базы
    Тут ничего сложного, requests + zipfile:
    archive = requests.get("http://ipgeobase.ru/files/db/Main/geo_files.zip")
    if archive.status_code != 200:
        error("IPGeobase no answer: %s" % archive.status_code)
    extracteddata = ZipFile(StringIO(archive.content))
    filelist = extracteddata.namelist()
    if "cities.txt" not in filelist:
        error("cities.txt not downloaded")
    if "cidr_optim.txt" not in filelist:
        error("cidr_optim.txt not downloaded")
    



    2. Загружаем словарь регионов
    REGIONS = dict(l.decode("utf8").rstrip().split("\t")[::-1]
                   for l in open("regions.tsv").readlines())
    

    где regions.tsv — это список автомобильных/налоговых кодов регионов, вида:
    66 Свердловская область
    77 Москва
    78 Санкт-Петербург



    3. Получаем словарь городов
    Для каждого города нам необходимо знать его id, название и код региона:
    CITIES = {}
    for line in extracteddata.open("cities.txt").readlines():
        cid, city, region_name, _, _, _ = line.decode("cp1251").split("\t")
        if region_name in REGIONS:
            CITIES[cid] = {'city': b64encode(city.encode("utf8")),
                           'reg_id': REGIONS[region_name]}
            if cid == "1199":  # Zelenograd fix
                CITIES[cid]['reg_id'] = "77"
    


    Замечу, что здесь сразу, с оглядкой на будущее, utf-8 название города кодируется в base64, для расширения возможностей использования (например в логах nginx), без необходимости работы с транслитерацией.


    4. Склеиваем диапазоны адресов и города
    for line in extracteddata.open("cidr_optim.txt").readlines():
        _, _, ip_range, country, cid = line.decode("cp1251").rstrip().split("\t")
        if country == "RU" and cid in CITIES:
                database["".join(ip_range.split())] = CITIES[cid]
    

    Очевидно, что если страна не Россия, то в ipgeobase ни регионов, ни городов не найдётся, и нашим задачам такие диапазоны не нужны.


    5. Генерируем файлы для geoip модуля
    with open("region.txt", "w") as reg, open("city.txt", "w") as city:
        for ip_range in sorted(database):
            info = database[ip_range]
            city.write("%s %s;\n" % (ip_range, info['city']))
            reg.write("%s %s;\n" % (ip_range, info['reg_id']))
    



    Настройка nginx



    Чтобы все работало, необходимо включить в nginx geo модуль nginx.org/ru/docs/http/ngx_http_geo_module.html,
    положить в известное место сгенерированные файлики и добавить такой конфиг в секцию http:
    geo $region {
        ranges;
        include geo/region.txt;
    }
    
    geo $city {
        ranges;
        include geo/city.txt;
    }
    

    После таких манипуляций в nginx появится две переменные $city и $region, которые можно использовать где угодно:

    • хоть в логе:
      log_format long '$time_iso8601\t$msec\t$host\t$request_method\t$uri\t$args\t$http_referer\t$remote_addr\t$http_user_agent\t$status\t$request_time\t$request_length\t$upstream_addr\t$bytes_sent\t$upstream_response_time\t$city\t$region';
      
    • хоть отправляя в хедерах приложению:
      location / {
          proxy_set_header  X-City     $city;
          proxy_set_header  X-Region $region;
          proxy_pass   http://backend;
      }
      

      При этом, в модуле geo по умолчанию все ненайденные адреса будут возвращать пустую строку, то в таком случае хедер просто не будет устанавливаться


    На деле, такой модуль работает просто моментально, не грузит nginx, и за счет легкой автоматизации обновления баз — довольно точен (все зависит от доверия базам ipgeobase.ru). В связи с чем, появилось ощущение, что он может быть полезен кому-то еще. Так что предлагаю пользоваться и, может быть, сделать конвертеры к другим поставщикам данных.

    код на GitHub (ветка ipgeobase-importer)

    P.S. Спустя время, после написания статьи — переписал всё на Go и добавил поддержку MaxMind
    • +22
    • 13,3k
    • 9
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 9
    • 0
      Интересно! Надо попробовать на dev-сервере. Спасибо! В закладки.
      • +1
        И кстати, стандартный GeoIP в nginx заточен под MaxMind (--with-http_geoip_module). По крайней мере так гласит вики, сам я его не использовал.
        • +3
          а я использовал, всё работает просто замечательно.
          • +1
            MaxMind же теперь использует GeoIP2, а модуль — только первые, устаревшие + у MaxMind коды не совпадают с реальными налоговыми/автомобильными кодами региона, а это важно.

            И так же, не знаю, не скажу насколько быстр http_geoip_module, а http_geo_module — замерял нагрузочным.
            • +2
              Вдруг кому интересно: я подружил nginx с geoip2 посредством дампа всех адресов и агрегирования префиксов. Правда у меня еще стояла задача внести коррективы для крымских адресов. Исходник: gist.github.com/Snawoot/7826240899835a5878b6
              А вообще где-то на гитхабе есть модуль nginx для geoip2.
              • +3
                Есть такой модуль github.com/leev/ngx_http_geoip2_module даже вроде актуальный. Последний коммит 8 месяцев назад, для модулей это вполне нормально.
      • +2
        Какая гео-база сейчас имеет наиболее полную и корректную базу городов/регионов по СНГ?
        • 0
          До сих пор нет эталонной базы, чтобы оценить полноту и корректность других.
          И да, ответ будет скорей зависеть от того, что вам конкретно он неё надо.
          • +1
            Я использую параллельно IP2Location и базу от MaxMind на своем сайте для сервиса whois. Что-то не ищется в первой, что-то не ищется во второй. Данные иногда различаются.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.