Skip to content

Commit 0db2ee8

Browse files
authored
Merge pull request #14 from ipinfo/silvano/eng-299-add-lite-api-support-to-ipinforails
Add support for Lite API
2 parents fbfde5a + 1626fc7 commit 0db2ee8

File tree

9 files changed

+465
-13
lines changed

9 files changed

+465
-13
lines changed

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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

.ruby-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.7.2
1+
3.2.2

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ group :development do
88
gem 'bundler'
99
gem 'minitest'
1010
gem 'minitest-reporters'
11+
gem 'rake'
1112
gem 'rubocop'
1213
end

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ You'll need an IPinfo API access token, which you can get by signing up for a fr
1515

1616
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)
1717

18-
⚠️ Note: This library does not currently support our newest free API https://ipinfo.io/lite. If you’d like to use IPinfo Lite, you can call the [endpoint directly](https://ipinfo.io/developers/lite-api) using your preferred HTTP client. Developers are also welcome to contribute support for Lite by submitting a pull request.
18+
The library also supports the Lite API, see the [Lite API section](#lite-api) for more info.
1919

2020
### Installation
2121

@@ -246,6 +246,19 @@ config.middleware.use(IPinfoMiddleware, {
246246
247247
This simple lambda function will filter out requests coming from your local computer.
248248
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: "<your_token>"})
260+
```
261+
249262
## Other Libraries
250263
251264
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.

Rakefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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

ipinfo-rails.gemspec

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ Gem::Specification.new do |s|
99
s.name = 'ipinfo-rails'
1010
s.version = IPinfoRails::VERSION
1111
s.required_ruby_version = '>= 2.5.0'
12-
s.date = '2018-12-10'
1312
s.summary = 'The official Rails gem for IPinfo. IPinfo prides itself on ' \
1413
'being the most reliable, accurate, and in-depth source of ' \
1514
'IP address data available anywhere. We process terabytes ' \
@@ -22,11 +21,16 @@ Gem::Specification.new do |s|
2221
s.homepage = 'https://ipinfo.io'
2322
s.license = 'Apache-2.0'
2423

25-
s.add_runtime_dependency 'IPinfo', '~> 1.0.1'
26-
s.add_runtime_dependency 'rack', '~> 2.0'
24+
s.add_dependency 'IPinfo', '~> 2.3'
25+
s.add_dependency 'rack', '~> 2.0'
26+
27+
s.add_development_dependency 'mocha', '~> 2.7'
2728

2829
s.files = `git ls-files -z`.split("\x0").reject do |f|
2930
f.match(%r{^(test|spec|features)/})
3031
end
3132
s.require_paths = ['lib']
33+
s.metadata = {
34+
'rubygems_mfa_required' => 'true'
35+
}
3236
end

lib/ipinfo-rails.rb

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,18 @@
22

33
require 'rack'
44
require 'ipinfo'
5+
require 'ipinfo_lite'
56
require 'ipinfo-rails/ip_selector/default_ip_selector'
67

8+
def is_bot(request)
9+
if request.user_agent
10+
user_agent = request.user_agent.downcase
11+
user_agent.include?('bot') || user_agent.include?('spider')
12+
else
13+
false
14+
end
15+
end
16+
717
class IPinfoMiddleware
818
def initialize(app, options = {})
919
@app = app
@@ -32,15 +42,34 @@ def call(env)
3242

3343
@app.call(env)
3444
end
45+
end
3546

36-
private
47+
class IPinfoLiteMiddleware
48+
def initialize(app, options = {})
49+
@app = app
50+
@token = options.fetch(:token, nil)
51+
@ipinfo = IPinfoLite.create(@token, options)
52+
@filter = options.fetch(:filter, nil)
53+
@ip_selector = options.fetch(:ip_selector, DefaultIPSelector)
54+
end
3755

38-
def is_bot(request)
39-
if request.user_agent
40-
user_agent = request.user_agent.downcase
41-
user_agent.include?('bot') || user_agent.include?('spider')
42-
else
43-
false
44-
end
56+
def call(env)
57+
env['called'] = 'yes'
58+
request = Rack::Request.new(env)
59+
ip_selector = @ip_selector.new(request)
60+
filtered = if @filter.nil?
61+
is_bot(request)
62+
else
63+
@filter.call(request)
64+
end
65+
66+
if filtered
67+
env['ipinfo'] = nil
68+
else
69+
ip = ip_selector.get_ip
70+
env['ipinfo'] = @ipinfo.details(ip)
4571
end
72+
73+
@app.call(env)
74+
end
4675
end
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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

0 commit comments

Comments
 (0)