├── .ruby-version ├── lib ├── ipinfo-rails │ ├── version.rb │ └── ip_selector │ │ ├── ip_selector_interface.rb │ │ ├── default_ip_selector.rb │ │ └── xforwarded_ip_selector.rb └── ipinfo-rails.rb ├── .gitignore ├── Gemfile ├── Rakefile ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── .rubocop.yml ├── ipinfo-rails.gemspec ├── test ├── ipinfo_middleware_test.rb ├── ipinfo_core_middleware_test.rb ├── ipinfo_lite_middleware_test.rb └── ipinfo_plus_middleware_test.rb └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /lib/ipinfo-rails/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IPinfoRails 4 | VERSION = '1.2.0' 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | ipinfo-rails-*.gem 11 | -------------------------------------------------------------------------------- /lib/ipinfo-rails/ip_selector/ip_selector_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IPSelectorInterface 4 | class InterfaceNotImplemented < StandardError; end 5 | def get_ip() 6 | raise InterfaceNotImplemented 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | group :development do 8 | gem 'bundler' 9 | gem 'minitest' 10 | gem 'minitest-reporters' 11 | gem 'rake' 12 | gem 'rubocop' 13 | end 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rake/testtask' 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << 'test' 8 | t.libs << 'lib' 9 | t.test_files = FileList['test/**/*_test.rb'] 10 | end 11 | 12 | task default: :test 13 | -------------------------------------------------------------------------------- /lib/ipinfo-rails/ip_selector/default_ip_selector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'ipinfo-rails/ip_selector/ip_selector_interface' 3 | 4 | class DefaultIPSelector 5 | include IPSelectorInterface 6 | 7 | def initialize(request) 8 | @request = request 9 | end 10 | 11 | def get_ip() 12 | return @request.ip 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/ipinfo-rails/ip_selector/xforwarded_ip_selector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'ipinfo-rails/ip_selector/ip_selector_interface' 3 | 4 | class XForwardedIPSelector 5 | include IPSelectorInterface 6 | 7 | def initialize(request) 8 | @request = request 9 | end 10 | 11 | def get_ip() 12 | x_forwarded = @request.env['HTTP_X_FORWARDED_FOR'] 13 | if !x_forwarded || x_forwarded.empty? 14 | return @request.ip 15 | else 16 | return x_forwarded.split(',' , -1)[0] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | ruby-version: ["3.2", "3.3", "3.4"] 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up Ruby ${{ matrix.ruby-version }} 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: ${{ matrix.ruby-version }} 26 | 27 | - name: Install dependencies 28 | run: bundle install 29 | 30 | - name: Run tests 31 | run: bundle exec rake 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release package to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | permissions: 13 | id-token: write 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Install apt dependencies 20 | run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev # needed by faraday-patron gem 21 | 22 | - name: Set up Ruby 23 | uses: ruby/setup-ruby@v1 24 | 25 | - name: Install dependencies 26 | run: bundle install 27 | 28 | - name: Run tests 29 | run: bundle exec rake 30 | env: 31 | IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }} 32 | 33 | - name: Build 34 | run: gem build *.gemspec 35 | 36 | - name: Publish 37 | uses: rubygems/release-gem@v1 38 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.5 3 | NewCops: enable 4 | 5 | Layout/IndentationWidth: 6 | Width: 4 7 | 8 | Layout/LineLength: 9 | Enabled: true 10 | Max: 80 11 | 12 | Lint/MissingSuper: 13 | Enabled: false 14 | 15 | Metrics/MethodLength: 16 | Enabled: false 17 | 18 | Metrics/AbcSize: 19 | Enabled: false 20 | 21 | Metrics/ClassLength: 22 | Enabled: false 23 | 24 | Lint/DuplicateMethods: 25 | Enabled: false 26 | 27 | Style/Documentation: 28 | Enabled: false 29 | 30 | Style/ClassAndModuleChildren: 31 | Enabled: false 32 | 33 | Style/MethodCallWithArgsParentheses: 34 | EnforcedStyle: require_parentheses 35 | IgnoreMacros: false 36 | IgnoredPatterns: [] 37 | AllowParenthesesInMultilineCall: true 38 | AllowParenthesesInChaining: true 39 | AllowParenthesesInCamelCaseMethod: true 40 | 41 | Style/MethodCallWithoutArgsParentheses: 42 | Enabled: false 43 | 44 | Naming/FileName: 45 | Enabled: false 46 | 47 | Naming/PredicateName: 48 | Enabled: false 49 | -------------------------------------------------------------------------------- /ipinfo-rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require 'ipinfo-rails/version' 7 | 8 | Gem::Specification.new do |s| 9 | s.name = 'ipinfo-rails' 10 | s.version = IPinfoRails::VERSION 11 | s.required_ruby_version = '>= 2.5.0' 12 | s.summary = 'The official Rails gem for IPinfo. IPinfo prides itself on ' \ 13 | 'being the most reliable, accurate, and in-depth source of ' \ 14 | 'IP address data available anywhere. We process terabytes ' \ 15 | 'of data to produce our custom IP geolocation, company, ' \ 16 | 'carrier and IP type data sets. You can visit our developer ' \ 17 | 'docs at https://ipinfo.io/developers.' 18 | s.description = s.summary 19 | s.authors = ['IPinfo releases'] 20 | s.email = ['releases@ipinfo.io'] 21 | s.homepage = 'https://ipinfo.io' 22 | s.license = 'Apache-2.0' 23 | 24 | s.add_dependency 'IPinfo', '~> 2.4' 25 | s.add_dependency 'rack', '~> 2.0' 26 | 27 | s.add_development_dependency 'mocha', '~> 2.7' 28 | 29 | s.files = `git ls-files -z`.split("\x0").reject do |f| 30 | f.match(%r{^(test|spec|features)/}) 31 | end 32 | s.require_paths = ['lib'] 33 | s.metadata = { 34 | 'rubygems_mfa_required' => 'true' 35 | } 36 | end 37 | -------------------------------------------------------------------------------- /lib/ipinfo-rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rack' 4 | require 'ipinfo' 5 | require 'ipinfo_lite' 6 | require 'ipinfo_core' 7 | require 'ipinfo_plus' 8 | require 'ipinfo-rails/ip_selector/default_ip_selector' 9 | 10 | def is_bot(request) 11 | if request.user_agent 12 | user_agent = request.user_agent.downcase 13 | user_agent.include?('bot') || user_agent.include?('spider') 14 | else 15 | false 16 | end 17 | end 18 | 19 | class IPinfoMiddleware 20 | def initialize(app, options = {}) 21 | @app = app 22 | @token = options.fetch(:token, nil) 23 | @ipinfo = IPinfo.create(@token, options) 24 | @filter = options.fetch(:filter, nil) 25 | @ip_selector = options.fetch(:ip_selector, DefaultIPSelector) 26 | end 27 | 28 | def call(env) 29 | env['called'] = 'yes' 30 | request = Rack::Request.new(env) 31 | ip_selector = @ip_selector.new(request) 32 | filtered = if @filter.nil? 33 | is_bot(request) 34 | else 35 | @filter.call(request) 36 | end 37 | 38 | if filtered 39 | env['ipinfo'] = nil 40 | else 41 | ip = ip_selector.get_ip() 42 | env['ipinfo'] = @ipinfo.details(ip) 43 | end 44 | 45 | @app.call(env) 46 | end 47 | end 48 | 49 | class IPinfoLiteMiddleware 50 | def initialize(app, options = {}) 51 | @app = app 52 | @token = options.fetch(:token, nil) 53 | @ipinfo = IPinfoLite.create(@token, options) 54 | @filter = options.fetch(:filter, nil) 55 | @ip_selector = options.fetch(:ip_selector, DefaultIPSelector) 56 | end 57 | 58 | def call(env) 59 | env['called'] = 'yes' 60 | request = Rack::Request.new(env) 61 | ip_selector = @ip_selector.new(request) 62 | filtered = if @filter.nil? 63 | is_bot(request) 64 | else 65 | @filter.call(request) 66 | end 67 | 68 | if filtered 69 | env['ipinfo'] = nil 70 | else 71 | ip = ip_selector.get_ip 72 | env['ipinfo'] = @ipinfo.details(ip) 73 | end 74 | 75 | @app.call(env) 76 | end 77 | end 78 | 79 | class IPinfoCoreMiddleware 80 | def initialize(app, options = {}) 81 | @app = app 82 | @token = options.fetch(:token, nil) 83 | @ipinfo = IPinfoCore.create(@token, options) 84 | @filter = options.fetch(:filter, nil) 85 | @ip_selector = options.fetch(:ip_selector, DefaultIPSelector) 86 | end 87 | 88 | def call(env) 89 | env['called'] = 'yes' 90 | request = Rack::Request.new(env) 91 | ip_selector = @ip_selector.new(request) 92 | filtered = if @filter.nil? 93 | is_bot(request) 94 | else 95 | @filter.call(request) 96 | end 97 | 98 | if filtered 99 | env['ipinfo'] = nil 100 | else 101 | ip = ip_selector.get_ip 102 | env['ipinfo'] = @ipinfo.details(ip) 103 | end 104 | 105 | @app.call(env) 106 | end 107 | end 108 | 109 | class IPinfoPlusMiddleware 110 | def initialize(app, options = {}) 111 | @app = app 112 | @token = options.fetch(:token, nil) 113 | @ipinfo = IPinfoPlus.create(@token, options) 114 | @filter = options.fetch(:filter, nil) 115 | @ip_selector = options.fetch(:ip_selector, DefaultIPSelector) 116 | end 117 | 118 | def call(env) 119 | env['called'] = 'yes' 120 | request = Rack::Request.new(env) 121 | ip_selector = @ip_selector.new(request) 122 | filtered = if @filter.nil? 123 | is_bot(request) 124 | else 125 | @filter.call(request) 126 | end 127 | 128 | if filtered 129 | env['ipinfo'] = nil 130 | else 131 | ip = ip_selector.get_ip 132 | env['ipinfo'] = @ipinfo.details(ip) 133 | end 134 | 135 | @app.call(env) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /test/ipinfo_middleware_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/mock' 5 | require 'mocha/minitest' 6 | require 'rack/mock' 7 | require 'ostruct' 8 | require 'ipinfo' 9 | require 'ipinfo/errors' 10 | require_relative '../lib/ipinfo-rails' 11 | 12 | 13 | # Simple Rack app 14 | class TestApp 15 | attr_reader :last_env 16 | 17 | def call(env) 18 | @last_env = env 19 | [200, { 'Content-Type' => 'text/plain' }, ['Hello from TestApp!']] 20 | end 21 | end 22 | 23 | class IPinfoMiddlewareTest < Minitest::Test 24 | def setup 25 | @app = TestApp.new 26 | @middleware = nil 27 | @mock_ipinfo_client = mock('IPinfoClient') 28 | IPinfo.stubs(:create).returns(@mock_ipinfo_client) 29 | 30 | @mock_details = OpenStruct.new( 31 | ip: '1.2.3.4', 32 | city: 'New York', 33 | country: 'US', 34 | hostname: 'example.com', 35 | org: 'Example Org' 36 | ) 37 | end 38 | 39 | # Custom IP Selector 40 | class CustomIPSelector 41 | def initialize(request) 42 | @request = request 43 | end 44 | 45 | def get_ip 46 | '9.10.11.12' 47 | end 48 | end 49 | 50 | def test_should_use_default_ip_selector_when_no_custom_selector_is_provided 51 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 52 | 53 | @middleware = IPinfoMiddleware.new(@app, token: 'test_token') 54 | request = Rack::MockRequest.new(@middleware) 55 | 56 | # Simulate a request with REMOTE_ADDR 57 | env = { 'REMOTE_ADDR' => '1.2.3.4' } 58 | response = request.get('/', env) 59 | 60 | assert_equal 200, response.status 61 | assert_equal 'yes', @app.last_env['called'] 62 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 63 | assert_equal 'New York', @app.last_env['ipinfo'].city 64 | end 65 | 66 | def test_should_use_custom_ip_selector_when_provided 67 | @mock_ipinfo_client.expects(:details).with('9.10.11.12') 68 | .returns(@mock_details.dup.tap { |d| d.ip = '9.10.11.12' }) 69 | 70 | @middleware = IPinfoMiddleware.new(@app, 71 | token: 'test_token', 72 | ip_selector: CustomIPSelector) 73 | request = Rack::MockRequest.new(@middleware) 74 | 75 | response = request.get('/', {}) 76 | 77 | assert_equal 200, response.status 78 | assert_equal 'yes', @app.last_env['called'] 79 | assert_equal '9.10.11.12', @app.last_env['ipinfo'].ip 80 | end 81 | 82 | def test_middleware_skips_processing_if_filter_returns_true 83 | always_filter = ->(_request) { true } 84 | 85 | @middleware = IPinfoMiddleware.new(@app, 86 | token: 'test_token', 87 | filter: always_filter) 88 | request = Rack::MockRequest.new(@middleware) 89 | 90 | @mock_ipinfo_client.expects(:details).never 91 | 92 | response = request.get('/', { 'REMOTE_ADDR' => '8.8.8.8' }) 93 | 94 | assert_equal 200, response.status 95 | assert_equal 'yes', @app.last_env['called'] 96 | assert_nil @app.last_env['ipinfo'], 97 | 'ipinfo should be nil when filtered' 98 | end 99 | 100 | def test_middleware_processes_if_filter_returns_false 101 | never_filter = ->(_request) { false } 102 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 103 | 104 | @middleware = IPinfoMiddleware.new(@app, 105 | token: 'test_token', 106 | filter: never_filter) 107 | request = Rack::MockRequest.new(@middleware) 108 | 109 | response = request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 110 | 111 | assert_equal 200, response.status 112 | assert_equal 'yes', @app.last_env['called'] 113 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 114 | end 115 | 116 | def test_middleware_filters_bots_by_default 117 | @mock_ipinfo_client.expects(:details).never # Should not call if bot 118 | 119 | @middleware = IPinfoMiddleware.new(@app, token: 'test_token') 120 | request = Rack::MockRequest.new(@middleware) 121 | 122 | # Test with common bot user agents 123 | bot_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } 124 | response = request.get('/', bot_env) 125 | 126 | assert_equal 200, response.status 127 | assert_equal 'yes', @app.last_env['called'] 128 | assert_nil @app.last_env['ipinfo'], 129 | 'ipinfo should be nil for bot user agent' 130 | 131 | spider_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)' } 132 | response = request.get('/', spider_env) 133 | 134 | assert_equal 200, response.status 135 | assert_equal 'yes', @app.last_env['called'] 136 | assert_nil @app.last_env['ipinfo'], 137 | 'ipinfo should be nil for spider user agent' 138 | end 139 | 140 | def test_middleware_does_not_filter_non_bots_by_default 141 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 142 | 143 | @middleware = IPinfoMiddleware.new(@app, token: 'test_token') 144 | request = Rack::MockRequest.new(@middleware) 145 | 146 | # Test with a regular user agent 147 | user_env = { 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } 148 | response = request.get('/', user_env) 149 | 150 | assert_equal 200, response.status 151 | assert_equal 'yes', @app.last_env['called'] 152 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 153 | end 154 | 155 | def test_middleware_handles_missing_user_agent 156 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 157 | 158 | @middleware = IPinfoMiddleware.new(@app, token: 'test_token') 159 | request = Rack::MockRequest.new(@middleware) 160 | 161 | # Test with no user agent provided 162 | no_ua_env = { 'REMOTE_ADDR' => '1.2.3.4' } 163 | response = request.get('/', no_ua_env) 164 | 165 | assert_equal 200, response.status 166 | assert_equal 'yes', @app.last_env['called'] 167 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 168 | end 169 | 170 | def test_middleware_handles_ipinfo_api_errors 171 | @mock_ipinfo_client.expects(:details).raises(StandardError, 172 | 'API rate limit exceeded') 173 | 174 | @middleware = IPinfoMiddleware.new(@app, token: 'test_token') 175 | request = Rack::MockRequest.new(@middleware) 176 | 177 | assert_raises StandardError do 178 | request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /test/ipinfo_core_middleware_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/mock' 5 | require 'mocha/minitest' 6 | require 'rack/mock' 7 | require 'ostruct' 8 | require 'ipinfo_core' 9 | require 'ipinfo/errors' 10 | require_relative '../lib/ipinfo-rails' 11 | 12 | 13 | # Simple Rack app 14 | class TestApp 15 | attr_reader :last_env 16 | 17 | def call(env) 18 | @last_env = env 19 | [200, { 'Content-Type' => 'text/plain' }, ['Hello from TestApp!']] 20 | end 21 | end 22 | 23 | class IPinfoCoreMiddlewareTest < Minitest::Test 24 | def setup 25 | @app = TestApp.new 26 | @middleware = nil 27 | @mock_ipinfo_client = mock('IPinfoClient') 28 | IPinfoCore.stubs(:create).returns(@mock_ipinfo_client) 29 | 30 | @mock_details = OpenStruct.new( 31 | ip: '1.2.3.4', 32 | city: 'New York', 33 | country: 'US', 34 | hostname: 'example.com', 35 | org: 'Example Org' 36 | ) 37 | end 38 | 39 | # Custom IP Selector 40 | class CustomIPSelector 41 | def initialize(request) 42 | @request = request 43 | end 44 | 45 | def get_ip 46 | '9.10.11.12' 47 | end 48 | end 49 | 50 | def test_should_use_default_ip_selector_when_no_custom_selector_is_provided 51 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 52 | 53 | @middleware = IPinfoCoreMiddleware.new(@app, token: 'test_token') 54 | request = Rack::MockRequest.new(@middleware) 55 | 56 | # Simulate a request with REMOTE_ADDR 57 | env = { 'REMOTE_ADDR' => '1.2.3.4' } 58 | response = request.get('/', env) 59 | 60 | assert_equal 200, response.status 61 | assert_equal 'yes', @app.last_env['called'] 62 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 63 | assert_equal 'New York', @app.last_env['ipinfo'].city 64 | end 65 | 66 | def test_should_use_custom_ip_selector_when_provided 67 | @mock_ipinfo_client.expects(:details).with('9.10.11.12') 68 | .returns(@mock_details.dup.tap { |d| d.ip = '9.10.11.12' }) 69 | 70 | @middleware = IPinfoCoreMiddleware.new(@app, 71 | token: 'test_token', 72 | ip_selector: CustomIPSelector) 73 | request = Rack::MockRequest.new(@middleware) 74 | 75 | response = request.get('/', {}) 76 | 77 | assert_equal 200, response.status 78 | assert_equal 'yes', @app.last_env['called'] 79 | assert_equal '9.10.11.12', @app.last_env['ipinfo'].ip 80 | end 81 | 82 | def test_middleware_skips_processing_if_filter_returns_true 83 | always_filter = ->(_request) { true } 84 | 85 | @middleware = IPinfoCoreMiddleware.new(@app, 86 | token: 'test_token', 87 | filter: always_filter) 88 | request = Rack::MockRequest.new(@middleware) 89 | 90 | @mock_ipinfo_client.expects(:details).never 91 | 92 | response = request.get('/', { 'REMOTE_ADDR' => '8.8.8.8' }) 93 | 94 | assert_equal 200, response.status 95 | assert_equal 'yes', @app.last_env['called'] 96 | assert_nil @app.last_env['ipinfo'], 97 | 'ipinfo should be nil when filtered' 98 | end 99 | 100 | def test_middleware_processes_if_filter_returns_false 101 | never_filter = ->(_request) { false } 102 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 103 | 104 | @middleware = IPinfoCoreMiddleware.new(@app, 105 | token: 'test_token', 106 | filter: never_filter) 107 | request = Rack::MockRequest.new(@middleware) 108 | 109 | response = request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 110 | 111 | assert_equal 200, response.status 112 | assert_equal 'yes', @app.last_env['called'] 113 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 114 | end 115 | 116 | def test_middleware_filters_bots_by_default 117 | @mock_ipinfo_client.expects(:details).never # Should not call if bot 118 | 119 | @middleware = IPinfoCoreMiddleware.new(@app, token: 'test_token') 120 | request = Rack::MockRequest.new(@middleware) 121 | 122 | # Test with common bot user agents 123 | bot_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } 124 | response = request.get('/', bot_env) 125 | 126 | assert_equal 200, response.status 127 | assert_equal 'yes', @app.last_env['called'] 128 | assert_nil @app.last_env['ipinfo'], 129 | 'ipinfo should be nil for bot user agent' 130 | 131 | spider_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)' } 132 | response = request.get('/', spider_env) 133 | 134 | assert_equal 200, response.status 135 | assert_equal 'yes', @app.last_env['called'] 136 | assert_nil @app.last_env['ipinfo'], 137 | 'ipinfo should be nil for spider user agent' 138 | end 139 | 140 | def test_middleware_does_not_filter_non_bots_by_default 141 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 142 | 143 | @middleware = IPinfoCoreMiddleware.new(@app, token: 'test_token') 144 | request = Rack::MockRequest.new(@middleware) 145 | 146 | # Test with a regular user agent 147 | user_env = { 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } 148 | response = request.get('/', user_env) 149 | 150 | assert_equal 200, response.status 151 | assert_equal 'yes', @app.last_env['called'] 152 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 153 | end 154 | 155 | def test_middleware_handles_missing_user_agent 156 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 157 | 158 | @middleware = IPinfoCoreMiddleware.new(@app, token: 'test_token') 159 | request = Rack::MockRequest.new(@middleware) 160 | 161 | # Test with no user agent provided 162 | no_ua_env = { 'REMOTE_ADDR' => '1.2.3.4' } 163 | response = request.get('/', no_ua_env) 164 | 165 | assert_equal 200, response.status 166 | assert_equal 'yes', @app.last_env['called'] 167 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 168 | end 169 | 170 | def test_middleware_handles_ipinfo_api_errors 171 | @mock_ipinfo_client.expects(:details).raises(StandardError, 172 | 'API rate limit exceeded') 173 | 174 | @middleware = IPinfoCoreMiddleware.new(@app, token: 'test_token') 175 | request = Rack::MockRequest.new(@middleware) 176 | 177 | assert_raises StandardError do 178 | request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /test/ipinfo_lite_middleware_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/mock' 5 | require 'mocha/minitest' 6 | require 'rack/mock' 7 | require 'ostruct' 8 | require 'ipinfo_lite' 9 | require 'ipinfo/errors' 10 | require_relative '../lib/ipinfo-rails' 11 | 12 | 13 | # Simple Rack app 14 | class TestApp 15 | attr_reader :last_env 16 | 17 | def call(env) 18 | @last_env = env 19 | [200, { 'Content-Type' => 'text/plain' }, ['Hello from TestApp!']] 20 | end 21 | end 22 | 23 | class IPinfoLiteMiddlewareTest < Minitest::Test 24 | def setup 25 | @app = TestApp.new 26 | @middleware = nil 27 | @mock_ipinfo_client = mock('IPinfoClient') 28 | IPinfoLite.stubs(:create).returns(@mock_ipinfo_client) 29 | 30 | @mock_details = OpenStruct.new( 31 | ip: '1.2.3.4', 32 | city: 'New York', 33 | country: 'US', 34 | hostname: 'example.com', 35 | org: 'Example Org' 36 | ) 37 | end 38 | 39 | # Custom IP Selector 40 | class CustomIPSelector 41 | def initialize(request) 42 | @request = request 43 | end 44 | 45 | def get_ip 46 | '9.10.11.12' 47 | end 48 | end 49 | 50 | def test_should_use_default_ip_selector_when_no_custom_selector_is_provided 51 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 52 | 53 | @middleware = IPinfoLiteMiddleware.new(@app, token: 'test_token') 54 | request = Rack::MockRequest.new(@middleware) 55 | 56 | # Simulate a request with REMOTE_ADDR 57 | env = { 'REMOTE_ADDR' => '1.2.3.4' } 58 | response = request.get('/', env) 59 | 60 | assert_equal 200, response.status 61 | assert_equal 'yes', @app.last_env['called'] 62 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 63 | assert_equal 'New York', @app.last_env['ipinfo'].city 64 | end 65 | 66 | def test_should_use_custom_ip_selector_when_provided 67 | @mock_ipinfo_client.expects(:details).with('9.10.11.12') 68 | .returns(@mock_details.dup.tap { |d| d.ip = '9.10.11.12' }) 69 | 70 | @middleware = IPinfoLiteMiddleware.new(@app, 71 | token: 'test_token', 72 | ip_selector: CustomIPSelector) 73 | request = Rack::MockRequest.new(@middleware) 74 | 75 | response = request.get('/', {}) 76 | 77 | assert_equal 200, response.status 78 | assert_equal 'yes', @app.last_env['called'] 79 | assert_equal '9.10.11.12', @app.last_env['ipinfo'].ip 80 | end 81 | 82 | def test_middleware_skips_processing_if_filter_returns_true 83 | always_filter = ->(_request) { true } 84 | 85 | @middleware = IPinfoLiteMiddleware.new(@app, 86 | token: 'test_token', 87 | filter: always_filter) 88 | request = Rack::MockRequest.new(@middleware) 89 | 90 | @mock_ipinfo_client.expects(:details).never 91 | 92 | response = request.get('/', { 'REMOTE_ADDR' => '8.8.8.8' }) 93 | 94 | assert_equal 200, response.status 95 | assert_equal 'yes', @app.last_env['called'] 96 | assert_nil @app.last_env['ipinfo'], 97 | 'ipinfo should be nil when filtered' 98 | end 99 | 100 | def test_middleware_processes_if_filter_returns_false 101 | never_filter = ->(_request) { false } 102 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 103 | 104 | @middleware = IPinfoLiteMiddleware.new(@app, 105 | token: 'test_token', 106 | filter: never_filter) 107 | request = Rack::MockRequest.new(@middleware) 108 | 109 | response = request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 110 | 111 | assert_equal 200, response.status 112 | assert_equal 'yes', @app.last_env['called'] 113 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 114 | end 115 | 116 | def test_middleware_filters_bots_by_default 117 | @mock_ipinfo_client.expects(:details).never # Should not call if bot 118 | 119 | @middleware = IPinfoLiteMiddleware.new(@app, token: 'test_token') 120 | request = Rack::MockRequest.new(@middleware) 121 | 122 | # Test with common bot user agents 123 | bot_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } 124 | response = request.get('/', bot_env) 125 | 126 | assert_equal 200, response.status 127 | assert_equal 'yes', @app.last_env['called'] 128 | assert_nil @app.last_env['ipinfo'], 129 | 'ipinfo should be nil for bot user agent' 130 | 131 | spider_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)' } 132 | response = request.get('/', spider_env) 133 | 134 | assert_equal 200, response.status 135 | assert_equal 'yes', @app.last_env['called'] 136 | assert_nil @app.last_env['ipinfo'], 137 | 'ipinfo should be nil for spider user agent' 138 | end 139 | 140 | def test_middleware_does_not_filter_non_bots_by_default 141 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 142 | 143 | @middleware = IPinfoLiteMiddleware.new(@app, token: 'test_token') 144 | request = Rack::MockRequest.new(@middleware) 145 | 146 | # Test with a regular user agent 147 | user_env = { 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } 148 | response = request.get('/', user_env) 149 | 150 | assert_equal 200, response.status 151 | assert_equal 'yes', @app.last_env['called'] 152 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 153 | end 154 | 155 | def test_middleware_handles_missing_user_agent 156 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 157 | 158 | @middleware = IPinfoLiteMiddleware.new(@app, token: 'test_token') 159 | request = Rack::MockRequest.new(@middleware) 160 | 161 | # Test with no user agent provided 162 | no_ua_env = { 'REMOTE_ADDR' => '1.2.3.4' } 163 | response = request.get('/', no_ua_env) 164 | 165 | assert_equal 200, response.status 166 | assert_equal 'yes', @app.last_env['called'] 167 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 168 | end 169 | 170 | def test_middleware_handles_ipinfo_api_errors 171 | @mock_ipinfo_client.expects(:details).raises(StandardError, 172 | 'API rate limit exceeded') 173 | 174 | @middleware = IPinfoLiteMiddleware.new(@app, token: 'test_token') 175 | request = Rack::MockRequest.new(@middleware) 176 | 177 | assert_raises StandardError do 178 | request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /test/ipinfo_plus_middleware_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/mock' 5 | require 'mocha/minitest' 6 | require 'rack/mock' 7 | require 'ostruct' 8 | require 'ipinfo_plus' 9 | require 'ipinfo/errors' 10 | require_relative '../lib/ipinfo-rails' 11 | 12 | 13 | # Simple Rack app 14 | class TestApp 15 | attr_reader :last_env 16 | 17 | def call(env) 18 | @last_env = env 19 | [200, { 'Content-Type' => 'text/plain' }, ['Hello from TestApp!']] 20 | end 21 | end 22 | 23 | class IPinfoPlusMiddlewareTest < Minitest::Test 24 | def setup 25 | @app = TestApp.new 26 | @middleware = nil 27 | @mock_ipinfo_client = mock('IPinfoClient') 28 | IPinfoPlus.stubs(:create).returns(@mock_ipinfo_client) 29 | 30 | @mock_details = OpenStruct.new( 31 | ip: '1.2.3.4', 32 | city: 'New York', 33 | country: 'US', 34 | hostname: 'example.com', 35 | org: 'Example Org' 36 | ) 37 | end 38 | 39 | # Custom IP Selector 40 | class CustomIPSelector 41 | def initialize(request) 42 | @request = request 43 | end 44 | 45 | def get_ip 46 | '9.10.11.12' 47 | end 48 | end 49 | 50 | def test_should_use_default_ip_selector_when_no_custom_selector_is_provided 51 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 52 | 53 | @middleware = IPinfoPlusMiddleware.new(@app, token: 'test_token') 54 | request = Rack::MockRequest.new(@middleware) 55 | 56 | # Simulate a request with REMOTE_ADDR 57 | env = { 'REMOTE_ADDR' => '1.2.3.4' } 58 | response = request.get('/', env) 59 | 60 | assert_equal 200, response.status 61 | assert_equal 'yes', @app.last_env['called'] 62 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 63 | assert_equal 'New York', @app.last_env['ipinfo'].city 64 | end 65 | 66 | def test_should_use_custom_ip_selector_when_provided 67 | @mock_ipinfo_client.expects(:details).with('9.10.11.12') 68 | .returns(@mock_details.dup.tap { |d| d.ip = '9.10.11.12' }) 69 | 70 | @middleware = IPinfoPlusMiddleware.new(@app, 71 | token: 'test_token', 72 | ip_selector: CustomIPSelector) 73 | request = Rack::MockRequest.new(@middleware) 74 | 75 | response = request.get('/', {}) 76 | 77 | assert_equal 200, response.status 78 | assert_equal 'yes', @app.last_env['called'] 79 | assert_equal '9.10.11.12', @app.last_env['ipinfo'].ip 80 | end 81 | 82 | def test_middleware_skips_processing_if_filter_returns_true 83 | always_filter = ->(_request) { true } 84 | 85 | @middleware = IPinfoPlusMiddleware.new(@app, 86 | token: 'test_token', 87 | filter: always_filter) 88 | request = Rack::MockRequest.new(@middleware) 89 | 90 | @mock_ipinfo_client.expects(:details).never 91 | 92 | response = request.get('/', { 'REMOTE_ADDR' => '8.8.8.8' }) 93 | 94 | assert_equal 200, response.status 95 | assert_equal 'yes', @app.last_env['called'] 96 | assert_nil @app.last_env['ipinfo'], 97 | 'ipinfo should be nil when filtered' 98 | end 99 | 100 | def test_middleware_processes_if_filter_returns_false 101 | never_filter = ->(_request) { false } 102 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 103 | 104 | @middleware = IPinfoPlusMiddleware.new(@app, 105 | token: 'test_token', 106 | filter: never_filter) 107 | request = Rack::MockRequest.new(@middleware) 108 | 109 | response = request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 110 | 111 | assert_equal 200, response.status 112 | assert_equal 'yes', @app.last_env['called'] 113 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 114 | end 115 | 116 | def test_middleware_filters_bots_by_default 117 | @mock_ipinfo_client.expects(:details).never # Should not call if bot 118 | 119 | @middleware = IPinfoPlusMiddleware.new(@app, token: 'test_token') 120 | request = Rack::MockRequest.new(@middleware) 121 | 122 | # Test with common bot user agents 123 | bot_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } 124 | response = request.get('/', bot_env) 125 | 126 | assert_equal 200, response.status 127 | assert_equal 'yes', @app.last_env['called'] 128 | assert_nil @app.last_env['ipinfo'], 129 | 'ipinfo should be nil for bot user agent' 130 | 131 | spider_env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)' } 132 | response = request.get('/', spider_env) 133 | 134 | assert_equal 200, response.status 135 | assert_equal 'yes', @app.last_env['called'] 136 | assert_nil @app.last_env['ipinfo'], 137 | 'ipinfo should be nil for spider user agent' 138 | end 139 | 140 | def test_middleware_does_not_filter_non_bots_by_default 141 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 142 | 143 | @middleware = IPinfoPlusMiddleware.new(@app, token: 'test_token') 144 | request = Rack::MockRequest.new(@middleware) 145 | 146 | # Test with a regular user agent 147 | user_env = { 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } 148 | response = request.get('/', user_env) 149 | 150 | assert_equal 200, response.status 151 | assert_equal 'yes', @app.last_env['called'] 152 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 153 | end 154 | 155 | def test_middleware_handles_missing_user_agent 156 | @mock_ipinfo_client.expects(:details).with('1.2.3.4').returns(@mock_details) 157 | 158 | @middleware = IPinfoPlusMiddleware.new(@app, token: 'test_token') 159 | request = Rack::MockRequest.new(@middleware) 160 | 161 | # Test with no user agent provided 162 | no_ua_env = { 'REMOTE_ADDR' => '1.2.3.4' } 163 | response = request.get('/', no_ua_env) 164 | 165 | assert_equal 200, response.status 166 | assert_equal 'yes', @app.last_env['called'] 167 | assert_equal '1.2.3.4', @app.last_env['ipinfo'].ip 168 | end 169 | 170 | def test_middleware_handles_ipinfo_api_errors 171 | @mock_ipinfo_client.expects(:details).raises(StandardError, 172 | 'API rate limit exceeded') 173 | 174 | @middleware = IPinfoPlusMiddleware.new(@app, token: 'test_token') 175 | request = Rack::MockRequest.new(@middleware) 176 | 177 | assert_raises StandardError do 178 | request.get('/', { 'REMOTE_ADDR' => '1.2.3.4' }) 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPinfo IPinfo Rails Client Library 2 | 3 | This is the official Rails client library for the IPinfo.io IP address API, allowing you to look up your own IP address, or get any of the following details for an IP: 4 | 5 | - [Geolocation](https://ipinfo.io/ip-geolocation-api) (city, region, country, postal code, latitude, and longitude) 6 | - [ASN](https://ipinfo.io/asn-api) (ISP or network operator, associated domain name, and type, such as business, hosting, or company) 7 | - [Company](https://ipinfo.io/ip-company-api) (the name and domain of the business that uses the IP address) 8 | - [Carrier](https://ipinfo.io/ip-carrier-api) (the name of the mobile carrier and MNC and MCC for that carrier if the IP is used exclusively for mobile traffic) 9 | 10 | Check all the data we have for your IP address [here](https://ipinfo.io/what-is-my-ip). 11 | 12 | ## Getting Started 13 | 14 | You'll need an IPinfo API access token, which you can get by signing up for a free account at [https://ipinfo.io/signup](https://ipinfo.io/signup). 15 | 16 | The free plan is limited to 50,000 requests per month, and doesn't include some of the data fields such as IP type and company data. To enable all the data fields and additional request volumes see [https://ipinfo.io/pricing](https://ipinfo.io/pricing) 17 | 18 | The library also supports the Lite API, see the [Lite API section](#lite-api) for more info. 19 | 20 | ### Installation 21 | 22 | 1. Option 1) Add this line to your application's Gemfile: 23 | 24 | ```ruby 25 | gem 'ipinfo-rails' 26 | ``` 27 | 28 | Then execute: 29 | 30 | ```bash 31 | $ bundle install 32 | ``` 33 | 34 | Option 2) Install it yourself by running the following command: 35 | 36 | ```bash 37 | $ gem install ipinfo-rails 38 | ``` 39 | 40 | 1. Open your `config/environment.rb` file or your preferred file in the `config/environment` directory. Add the following code to your chosen configuration file. 41 | 42 | ```ruby 43 | require 'ipinfo-rails' 44 | config.middleware.use(IPinfoMiddleware, {token: ""}) 45 | ``` 46 | 47 | Note: if editing `config/environment.rb`, this needs to come before `Rails.application.initialize!` and with `Rails.application.` prepended to `config`, otherwise you'll get runtime errors. 48 | 49 | 1. Restart your development server. 50 | 51 | ### Quickstart 52 | 53 | Once configured, `ipinfo-rails` will make IP address data accessible within Rail's `request` object. These values can be accessed at `request.env['ipinfo']`. 54 | 55 | ## Details Data 56 | 57 | `request.env['ipinfo']` is `Response` object that contains all fields listed [IPinfo developer docs](https://ipinfo.io/developers/responses#full-response) with a few minor additions. Properties can be accessed through methods of the same name. 58 | 59 | ```ruby 60 | request.env['ipinfo'].hostname == 'cpe-104-175-221-247.socal.res.rr.com' 61 | ``` 62 | 63 | ### Country Name 64 | 65 | `request.env['ipinfo'].country_name` will return the country name, as supplied by the `countries.json` file. See below for instructions on changing that file for use with non-English languages. `request.env['ipinfo'].country` will still return country code. 66 | 67 | ```ruby 68 | request.env['ipinfo'].country == 'US' 69 | request.env['ipinfo'].country_name == 'United States' 70 | ``` 71 | 72 | ### IP Address 73 | 74 | `request.env['ipinfo'].ip_address` will return the an `IPAddr` object from the [Ruby Standard Library](https://ruby-doc.org/stdlib-2.5.1/libdoc/ipaddr/rdoc/IPAddr.html). `request.env['ipinfo'].ip` will still return a string. 75 | 76 | ```ruby 77 | request.env['ipinfo'].ip == '104.175.221.247' 78 | request.env['ipinfo'].ip_address == 79 | ``` 80 | 81 | ### Longitude and Latitude 82 | 83 | `request.env['ipinfo'].latitude` and `request.env['ipinfo'].longitude` will return latitude and longitude, respectively, as strings. `request.env['ipinfo'].loc` will still return a composite string of both values. 84 | 85 | ```ruby 86 | request.env['ipinfo'].loc == '34.0293,-118.3570' 87 | request.env['ipinfo'].latitude == '34.0293' 88 | request.env['ipinfo'].longitude == '-118.3570' 89 | ``` 90 | 91 | ### Accessing all properties 92 | 93 | `request.env['ipinfo'].all` will return all details data as a hash. 94 | 95 | ```ruby 96 | request.env['ipinfo'].all == 97 | { 98 | :asn => { :asn => 'AS20001', 99 | :domain => 'twcable.com', 100 | :name => 'Time Warner Cable Internet LLC', 101 | :route => '104.172.0.0/14', 102 | :type => 'isp'}, 103 | :city => 'Los Angeles', 104 | :company => { :domain => 'twcable.com', 105 | :name => 'Time Warner Cable Internet LLC', 106 | :type => 'isp'}, 107 | :country => 'US', 108 | :country_name => 'United States', 109 | :hostname => 'cpe-104-175-221-247.socal.res.rr.com', 110 | :ip => '104.175.221.247', 111 | :ip_address => , 112 | :loc => '34.0293,-118.3570', 113 | :latitude => '34.0293', 114 | :longitude => '-118.3570', 115 | :phone => '323', 116 | :postal => '90016', 117 | :region => 'California' 118 | } 119 | ``` 120 | 121 | ## Configuration 122 | 123 | In addition to the steps listed in the Installation section, it is possible to configure the library with more detail. The following arguments are allowed and are described in detail below. 124 | 125 | ```ruby 126 | require 'ipinfo-rails/ip_selector/xforwarded_ip_selector' 127 | 128 | config.middleware.use(IPinfoMiddleware, { 129 | token: "", 130 | ttl: "", 131 | maxsize: "", 132 | cache: "", 133 | http_client: "", 134 | countries: "", 135 | filter: "", 136 | ip_selector: XForwardedIPSelector, 137 | }) 138 | ``` 139 | 140 | ### IP Selection Mechanism 141 | 142 | By default, the source IP on the request is used as the input to IP geolocation. 143 | 144 | Since the actual desired IP may be something else, the IP selection mechanism is configurable. 145 | 146 | Here are some built-in mechanisms: 147 | 148 | - [DefaultIPSelector](./lib/ipinfo-rails/ip_selector/default_ip_selector.rb) 149 | - [XForwardedIPSelector](./lib/ipinfo-rails/ip_selector/xforwarded_ip_selector.rb) 150 | 151 | #### Using a custom IP selector 152 | 153 | In case a custom IP selector is required, you may implement the `IPSelectorInterface` and pass the class to `ip_selector` in config. 154 | 155 | ```ruby 156 | require 'custom-package/custom_ip_selector' 157 | 158 | config.middleware.use(IPinfoMiddleware, { 159 | token: "", 160 | ip_selector: CustomIPSelector, 161 | }) 162 | ``` 163 | 164 | ### Authentication 165 | 166 | The IPinfo library can be authenticated with your IPinfo API token, which is set in the environment file. It also works without an authentication token, but in a more limited capacity. 167 | 168 | ```ruby 169 | config.middleware.use(IPinfoMiddleware, {token: '123456789abc'}) 170 | ``` 171 | 172 | ### Caching 173 | 174 | In-memory caching of `details` data is provided by default via the [lrucache](https://www.rubydoc.info/gems/lrucache/0.1.4/LRUCache) gem. This uses an LRU (least recently used) cache with a TTL (time to live) by default. This means that values will be cached for the specified duration; if the cache's max size is reached, cache values will be invalidated as necessary, starting with the oldest cached value. 175 | 176 | #### Modifying cache options 177 | 178 | Cache behavior can be modified by setting the `ttl` and `maxsize` options. 179 | 180 | - Default maximum cache size: 4096 (multiples of 2 are recommended to increase efficiency) 181 | - Default TTL: 24 hours (in seconds) 182 | 183 | ```ruby 184 | config.middleware.use(IPinfoMiddleware, { 185 | ttl: 30, 186 | maxsize: 40 187 | }) 188 | ``` 189 | 190 | #### Using a different cache 191 | 192 | It's possible to use a custom cache by creating a child class of the [CacheInterface](https://github.com/ipinfo/ruby/blob/master/lib/ipinfo/cache/cache_interface.rb) class and passing this into the handler object with the `cache` keyword argument. FYI this is known as [the Strategy Pattern](https://sourcemaking.com/design_patterns/strategy). 193 | 194 | ```ruby 195 | config.middleware.use(IPinfoMiddleware, {:cache => my_fancy_custom_class}) 196 | ``` 197 | 198 | If a custom cache is used the `maxsize` and `ttl` settings will not be used. 199 | 200 | ### Using a different HTTP library 201 | 202 | Ruby is notorious for having lots of HTTP libraries. While `Net::HTTP` is a reasonable default, you can set any other that [Faraday supports](https://github.com/lostisland/faraday/tree/29feeb92e3413d38ffc1fd3a3479bb48a0915730#faraday) if you prefer. 203 | 204 | ```ruby 205 | config.middleware.use(IPinfoMiddleware, {:http_client => my_client}) 206 | ``` 207 | 208 | Don't forget to bundle the custom HTTP library as well. 209 | 210 | ### Internationalization 211 | 212 | When looking up an IP address, the response object includes a `Details.country_name` method which includes the country name based on American English. It is possible to return the country name in other languages by setting the countries setting when creating the IPinfo object. 213 | 214 | The file must be a `.json` file with the following structure: 215 | 216 | ```ruby 217 | { 218 | "BD": "Bangladesh", 219 | "BE": "Belgium", 220 | "BF": "Burkina Faso", 221 | "BG": "Bulgaria" 222 | ... 223 | } 224 | ``` 225 | 226 | ```ruby 227 | config.middleware.use(IPinfoMiddleware, {:countries => }) 228 | ``` 229 | 230 | ### Filtering 231 | 232 | By default, `ipinfo-rails` filters out requests that have `bot` or `spider` in the user-agent. Instead of looking up IP address data for these requests, the `request.env['ipinfo']` attribute is set to `nil`. This is to prevent you from unnecessarily using up requests on non-user traffic. 233 | 234 | To set your own filtering rules, *thereby replacing the default filter*, you can set `:filter` to your own, custom callable function which satisfies the following rules: 235 | 236 | - Accepts one request. 237 | - Returns *True to filter out, False to allow lookup* 238 | 239 | To use your own filter rules: 240 | 241 | ```ruby 242 | config.middleware.use(IPinfoMiddleware, { 243 | filter: ->(request) {request.ip == '127.0.0.1'} 244 | }) 245 | ``` 246 | 247 | This simple lambda function will filter out requests coming from your local computer. 248 | 249 | ## Lite API 250 | 251 | The library gives the possibility to use the [Lite API](https://ipinfo.io/developers/lite-api) too, authentication with your token is still required. 252 | 253 | The returned details are slightly different from the Core API, but it has the same configurations options. 254 | 255 | You can use it like so: 256 | 257 | ```ruby 258 | require 'ipinfo-rails' 259 | config.middleware.use(IPinfoLiteMiddleware, {token: ""}) 260 | ``` 261 | 262 | ## Other Libraries 263 | 264 | There are official IPinfo client libraries available for many languages including PHP, Go, Java, Ruby, and many popular frameworks such as Django, Rails, and Laravel. There are also many third-party libraries and integrations available for our API. 265 | 266 | ## About IPinfo 267 | 268 | Founded in 2013, IPinfo prides itself on being the most reliable, accurate, and in-depth source of IP address data available anywhere. We process terabytes of data to produce our custom IP geolocation, company, carrier, privacy detection (VPN, proxy, Tor), hosted domains, and IP type data sets. Our API handles over 40 billion requests a month for 100,000 businesses and developers. 269 | 270 | ![image](https://avatars3.githubusercontent.com/u/15721521?s=128&u=7bb7dde5c4991335fb234e68a30971944abc6bf3&v=4) 271 | --------------------------------------------------------------------------------