🗺️

GeoLite2を使ってIPアドレスから都道府県を特定する

2022/10/24に公開約5,800字

前提知識

  • Railsで簡単なCRUDアプリを作れる
  • 事前にGeoIP2の会員登録を済ませてIDとパスワードを取得してあること

今回触れないこと

  • gemのインストール方法
  • active_hashの説明
  • CRUDの実装
  • GeoLiteの会員登録方法

テーブルイメージ

id title body get_prefecture_id
1 タイトル1 内容1 13
2 タイトル2 内容2 14

コード実装

controllers/concerns/geoip_module.rbを実装する

  • geoip_module.rbを以下のように実装する
    • 環境変数は適宜変更してください
app/controllers/concerns/geoip_module.rb
  module GeoipModule
 
    def get_to_geoip(ip)
      uri = URI.parse("https://geolite.info/geoip/v2.1/city/#{ip}")
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true
      req = Net::HTTP::Get.new(uri)
      # 環境変数には会員登録時に発行されるIDとパスワードを設定する
      req.basic_auth(ENV['ID'], ENV['PASSWORD'])
      prefecture = http.request(req)
      JSON.parse(prefecture.body.encode('SJIS', 'UTF-8', invalid: :replace, undef: :replace, replace: '')&.encode('UTF-8'), symbolize_names: true)
    end
  end

models/prefecture.rbを実装する

  • prefecture.rbを以下のように実装する
app/models/prefecture.rb
 class Prefecture < ActiveHash::Base
  #-----------------------
  #  Active_hash 
  #-----------------------
   self.data = [
    {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
    {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
    {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
    {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
    {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
    {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
    {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
    {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
    {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
    {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
    {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
    {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
    {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
    {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
    {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
    {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
   ]
   #-----------------------
   #  Scope
   #-----------------------
   scope :get_prefecture_name, ->(prefecture_name) { find_by(name: prefecture_name) }
end

controllers/article_controller.rbを実装する

  • article_controller.rbを以下のように実装する
app/controllers/article_controller.rb
class ArticleController < ApplicationController
  include GeoipModule

  def create
      @articles = Article.new(article_params)
      begin
        remote_ip = request.env["HTTP_X_FORWARDED_FOR"]&.to_s&.split(', ')&.first || request.remote_ip
        subdivisions = get_to_geoip(remote_ip)&.dig(:subdivisions)
        prefecture_name = subdivisions.present? ? subdivisions[0].dig(:names, :ja) : nil
        @articles.get_prefecture_id = Prefecture.get_prefecture_name(prefecture_name)&.id
      rescue => e
        Rails.logger.error(e)
        Rails.logger.error(e.backtrace.join("\n"))
      end
      
      if @articles.save
        redirect_to home_index_path
      else
        render "new"
      end
   end
end

controllers/article_controller.rbの解説

※今回remote_ipの中には東京都のIPアドレスが入っていることを想定しています。
まずget_to_geoip(remote_ip)でGeoIPにAPIを投げます。
結果として以下のような値が返ってきます。

{:city=>{:geoname_id=>1850147, :names=>{:"zh-CN"=>"京", :de=>"Tokio", :en=>"Tokyo", :es=>"Tokio", :fr=>"Tokyo", :ja=>"東京", :"pt-BR"=>"Tquio", :ru=>"Токио"}}, 
:continent=>{:code=>"AS", :geoname_id=>6255147, :names=>{:"zh-CN"=>"洲", :de=>"Asien", :en=>"Asia", :es=>"Asia", :fr=>"Asie", :ja=>"アジア", :"pt-BR"=>"sia", :ru=>"Азия"}}, 
:country=>{:iso_code=>"JP", :geoname_id=>1861060, :names=>{:fr=>"Japon", :ja=>"日本", :"pt-BR"=>"Japo", :ru=>"Япония", :"zh-CN"=>"日本", :de=>"Japan", :en=>"Japan", :es=>"Japn"}}, 
:location=>{:accuracy_radius=>50, :latitude=>35.6893, :longitude=>139.6899, :time_zone=>"Asia/Tokyo"}, :postal=>{:code=>"102-0082"},
:registered_country=>{:iso_code=>"JP", :geoname_id=>1861060, :names=>{:en=>"Japan", :es=>"Japn", :fr=>"Japon", :ja=>"日本", :"pt-BR"=>"Japo", :ru=>"Япония", :"zh-CN"=>"日本", :de=>"Japan"}},
:subdivisions=>[{:iso_code=>"13", :geoname_id=>1850144, :names=>{:ja=>"東京都", :en=>"Tokyo", :fr=>"Prfecture de Tokyo"}}]}

今回は、都市名の東京都を取得したいのでget_to_geoip(remote_ip)&.dig(:subdivisions)した値をsubdivisions変数の中に格納しています。
変数の中身は以下の通りになっているかと思います。

subdivisions = [{:iso_code=>"13", :geoname_id=>1850144, :names=>{:en=>"Tokyo", :fr=>"Prfecture de Tokyo", :ja=>"東京都"}}]

次にprefecture_name変数にsubdivisions.present?がtrueの場合は都市名を
falseの場合にはnil返す処理をしています。

 # trueの場合
prefecture_name = subdivisions.present? ? subdivisions[0].dig(:names, :ja) : nil
→ "東京都"

 # falseの場合
 prefecture_name = subdivisions.present? ? subdivisions[0].dig(:names, :ja) : nil
→ nil

都市名を取得できたらprefecture.rbで作成したスコープget_prefecture_nameの引数にprefecture_nameを渡せば...

 # 今回はidが欲しいのでidを取得
Prefecture.get_prefecture_by_name(prefecture_name)&.id
→ 13

あとは@articles.saveで無事保存されていることが確認できましたら完成です!

request.env["HTTP_X_FORWARDED_FOR"]について

  • Ruby on Railsではwebサーバーの種類によりますが以下のコードIPの取得ができます
request.env["HTTP_X_FORWARDED_FOR"] || request.remote_ip
  • しかしrequest.env["HTTP_X_FORWARDED_FOR"]ではカンマ区切りで複数のIPが返ってきてしまうので以下の形に書き換えることを推奨します
- request.env["HTTP_X_FORWARDED_FOR"] || request.remote_ip
+ request.env["HTTP_X_FORWARDED_FOR"]&.to_s&.split(', ')&.first || request.remote_ip

また今回のコードではnilが返ってくる可能性が高いのでぼっち演算子を使用しています
メソッド名が存在しない場合はNoMethodErrorが出力されてしまうので注意が必要です

最後に

今回が初めての記事投稿ということもあり説明不足なところも多数あると思いますが参考程度に見ていただけたら嬉しいです
不明な点などありましたらお気軽にコメントよろしくお願いします🤲
Twitterのフォローもよろしくお願いします! → https://twitter.com/teru_pm

Discussion

ログインするとコメントできます