├── .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 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 | 
271 |
--------------------------------------------------------------------------------