diff --git a/Gemfile b/Gemfile index e64517f55..2ff6837f8 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ group :development, :test do gem 'rubyzip' gem 'rails', '~>5.1.0' gem 'test-unit' # needed for Ruby >=2.2.0 + gem 'ip2location_ruby' platforms :jruby do gem 'jruby-openssl' diff --git a/README_API_GUIDE.md b/README_API_GUIDE.md index 9080a62ea..5763a0fd4 100644 --- a/README_API_GUIDE.md +++ b/README_API_GUIDE.md @@ -815,4 +815,26 @@ You can generate ActiveRecord migrations and download and import data via provid You can replace `city` with `country` in any of the above tasks, generators, and configurations. +### IP2Location LITE (`:ip2location_lite`) + +This lookup provides methods for geocoding IP addresses without making a call to a remote API (improves speed and availability). + +* **API key**: none (requires a IP2Location or FREE IP2Location LITE binary database which can be downloaded from [IP2Location LITE](https://lite.ip2location.com/)) +* **Quota**: none +* **Region**: world +* **SSL support**: N/A +* **Languages**: English +* **Documentation**: https://lite.ip2location.com/ +* **Terms of Service**: https://lite.ip2location.com/ +* **Notes**: **You must download a binary database (BIN) file from IP2Location LITE and set the `:file` configuration option.** Set the path to the database file in your configuration: + + Geocoder.configure( + ip_lookup: :ip2location_lite, + ip2location_lite: { + file: File.join('folder', 'IP2LOCATION-LITE-DB11.BIN') + } + ) + +You must add the *[ip2location_ruby](https://rubygems.org/gems/ip2location_ruby)* gem (pure Ruby implementation) to your Gemfile or have it installed in your system. + Copyright (c) 2009-2021 Alex Reisner, released under the MIT license. diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb index 56b36319f..c2054b847 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookup.rb @@ -23,7 +23,7 @@ def all_services_except_test # For example, Amazon Location Service uses the AWS gem, not HTTP REST requests, to fetch data. # def all_services_with_http_requests - all_services_except_test - [:amazon_location_service] + all_services_except_test - [:amazon_location_service, :ip2location_lite] end ## @@ -95,7 +95,8 @@ def ip_services :ipgeolocation, :ipqualityscore, :ipbase, - :ip2location_io + :ip2location_io, + :ip2location_lite ] end diff --git a/lib/geocoder/lookups/ip2location_lite.rb b/lib/geocoder/lookups/ip2location_lite.rb new file mode 100644 index 000000000..396ff0af0 --- /dev/null +++ b/lib/geocoder/lookups/ip2location_lite.rb @@ -0,0 +1,40 @@ +require 'geocoder/lookups/base' +require 'geocoder/results/ip2location_lite' + +module Geocoder + module Lookup + class Ip2locationLite < Base + attr_reader :gem_name + + def initialize + unless configuration[:file].nil? + begin + @gem_name = 'ip2location_ruby' + require @gem_name + rescue LoadError + raise "Could not load IP2Location DB dependency. To use the IP2LocationLite lookup you must add the #{@gem_name} gem to your Gemfile or have it installed in your system." + end + end + super + end + + def name + 'IP2LocationLite' + end + + def required_api_key_parts + [] + end + + private + + def results(query) + return [] unless configuration[:file] + + i2l = Ip2location.new.open(configuration[:file].to_s) + result = i2l.get_all(query.to_s) + result.nil? ? [] : [result] + end + end + end +end \ No newline at end of file diff --git a/lib/geocoder/results/ip2location_lite.rb b/lib/geocoder/results/ip2location_lite.rb new file mode 100644 index 000000000..82d8b0376 --- /dev/null +++ b/lib/geocoder/results/ip2location_lite.rb @@ -0,0 +1,47 @@ +require 'geocoder/results/base' + +module Geocoder::Result + class Ip2locationLite < Base + + def coordinates + [@data[:latitude], @data[:longitude]] + end + + def city + @data[:city] + end + + def state + @data[:region] + end + + def state_code + "" # Not available in Maxmind's database + end + + def country + @data[:country_long] + end + + def country_code + @data[:country_short] + end + + def postal_code + @data[:zipcode] + end + + def self.response_attributes + %w[country_short country_long region latitude longitude isp + domain netspeed areacode iddcode timezone zipcode weatherstationname + weatherstationcode mcc mnc mobilebrand elevation usagetype addresstype + category district asn as] + end + + response_attributes.each do |a| + define_method a do + @data[a] || "" + end + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index c0fb78382..e5a020f67 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -260,6 +260,23 @@ def default_fixture_filename end end + require 'geocoder/lookups/ip2location_lite' + class Ip2locationLite + private + + remove_method(:results) + + def results query + return [] if query.to_s == "no results" + + if query.to_s == '127.0.0.1' + [{:country_short=>"-", :country_long=>"-", :region=>"-", :city=>"-", :latitude=>0.0, :longitude=>0.0, :zipcode=>"-", :timezone=>"-", :isp=>"Loopback", :domain=>"-", :netspeed=>"-", :iddcode=>"-", :areacode=>"-", :weatherstationcode=>"-", :weatherstationname=>"-", :mcc=>"-", :mnc=>"-", :mobilebrand=>"-", :elevation=>0, :usagetype=>"RSV", :addresstype=>"U", :category=>"IAB24", :district=>"-", :asn=>"-", :as=>"-"}] + elsif query.to_s == '8.8.8.8' + [{:country_short=>"US", :country_long=>"United States of America", :region=>"California", :city=>"Mountain View", :latitude=>37.40599060058594, :longitude=>-122.0785140991211, :zipcode=>"94043", :timezone=>"-07:00", :isp=>"Google LLC", :domain=>"google.com", :netspeed=>"T1", :iddcode=>"1", :areacode=>"650", :weatherstationcode=>"USCA0746", :weatherstationname=>"Mountain View", :mcc=>"-", :mnc=>"-", :mobilebrand=>"-", :elevation=>32, :usagetype=>"DCH", :addresstype=>"A", :category=>"IAB19-11", :district=>"San Diego County", :asn=>"15169", :as=>"Google LLC"}] + end + end + end + require 'geocoder/lookups/ipgeolocation' class Ipgeolocation private diff --git a/test/unit/cache_test.rb b/test/unit/cache_test.rb index f58bc4f66..6b41895df 100644 --- a/test/unit/cache_test.rb +++ b/test/unit/cache_test.rb @@ -21,6 +21,7 @@ def test_second_occurrence_of_request_is_cache_hit # local, does not use cache l == :maxmind_local || l == :geoip2 || + l == :ip2location_lite || # uses the AWS gem, not HTTP requests with caching l == :amazon_location_service Geocoder.configure(:lookup => l) diff --git a/test/unit/error_handling_test.rb b/test/unit/error_handling_test.rb index c63ef2fcd..dbdfbe67b 100644 --- a/test/unit/error_handling_test.rb +++ b/test/unit/error_handling_test.rb @@ -43,7 +43,7 @@ def test_never_raise_response_parse_error def test_always_raise_timeout_error Geocoder.configure(:always_raise => [Timeout::Error]) Geocoder::Lookup.all_services_with_http_requests.each do |l| - next if l == :maxmind_local || l == :geoip2 # local, does not use cache + next if l == :maxmind_local || l == :geoip2 || l == :ip2location_lite # local, does not use cache lookup = Geocoder::Lookup.get(l) set_api_key!(l) assert_raises Timeout::Error do @@ -55,7 +55,7 @@ def test_always_raise_timeout_error def test_always_raise_socket_error Geocoder.configure(:always_raise => [SocketError]) Geocoder::Lookup.all_services_with_http_requests.each do |l| - next if l == :maxmind_local || l == :geoip2 # local, does not use cache + next if l == :maxmind_local || l == :geoip2 || l == :ip2location_lite # local, does not use cache lookup = Geocoder::Lookup.get(l) set_api_key!(l) assert_raises SocketError do @@ -67,7 +67,7 @@ def test_always_raise_socket_error def test_always_raise_connection_refused_error Geocoder.configure(:always_raise => [Errno::ECONNREFUSED]) Geocoder::Lookup.all_services_with_http_requests.each do |l| - next if l == :maxmind_local || l == :geoip2 # local, does not use cache + next if l == :maxmind_local || l == :geoip2 || l == :ip2location_lite # local, does not use cache lookup = Geocoder::Lookup.get(l) set_api_key!(l) assert_raises Errno::ECONNREFUSED do @@ -79,7 +79,7 @@ def test_always_raise_connection_refused_error def test_always_raise_host_unreachable_error Geocoder.configure(:always_raise => [Errno::EHOSTUNREACH]) Geocoder::Lookup.all_services_with_http_requests.each do |l| - next if l == :maxmind_local || l == :geoip2 # local, does not use cache + next if l == :maxmind_local || l == :geoip2 || l == :ip2location_lite # local, does not use cache lookup = Geocoder::Lookup.get(l) set_api_key!(l) assert_raises Errno::EHOSTUNREACH do diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb index cb60ddf83..ab4dc0b5c 100644 --- a/test/unit/lookup_test.rb +++ b/test/unit/lookup_test.rb @@ -32,7 +32,7 @@ def test_search_returns_empty_array_when_no_results def test_query_url_contains_values_in_params_hash Geocoder::Lookup.all_services_except_test.each do |l| - next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipdata_co, :ipinfo_io, :ipapi_com, :ipregistry, :ipstack, :postcodes_io, :uk_ordnance_survey_names, :amazon_location_service, :ipbase].include? l # does not use query string + next if [:freegeoip, :maxmind_local, :telize, :pointpin, :geoip2, :maxmind_geoip2, :mapbox, :ipdata_co, :ipinfo_io, :ipapi_com, :ipregistry, :ipstack, :postcodes_io, :uk_ordnance_survey_names, :amazon_location_service, :ipbase, :ip2location_lite].include? l # does not use query string set_api_key!(l) url = Geocoder::Lookup.get(l).query_url(Geocoder::Query.new( "test", :params => {:one_in_the_hand => "two in the bush"} diff --git a/test/unit/lookups/ip2location_lite_test.rb b/test/unit/lookups/ip2location_lite_test.rb new file mode 100644 index 000000000..8ff9f695a --- /dev/null +++ b/test/unit/lookups/ip2location_lite_test.rb @@ -0,0 +1,14 @@ +# encoding: utf-8 +require 'test_helper' + +class Ip2locationLiteTest < GeocoderTestCase + def setup + super + Geocoder.configure(ip_lookup: :ip2location_lite, ip2location_lite: { file: File.join('folder', 'test_file') }) + end + + def test_loopback + result = Geocoder.search('127.0.0.1').first + assert_equal '', result.country_short + end +end \ No newline at end of file diff --git a/test/unit/result_test.rb b/test/unit/result_test.rb index c0b0c4cdc..a37411c55 100644 --- a/test/unit/result_test.rb +++ b/test/unit/result_test.rb @@ -8,6 +8,7 @@ def test_forward_geocoding_result_has_required_attributes next if [ :ip2location, # has pay-per-attribute pricing model :ip2location_io, # has pay-per-attribute pricing model + :ip2location_lite, # no forward geocoding :twogis, # cant find 'Madison Square Garden' ].include?(l) @@ -23,6 +24,7 @@ def test_reverse_geocoding_result_has_required_attributes next if [ :ip2location, # has pay-per-attribute pricing model :ip2location_io, # has pay-per-attribute pricing model + :ip2location_lite, # no reverse geocoding :nationaal_georegister_nl, # no reverse geocoding :melissa_street, # reverse geocoding not implemented :twogis, # cant find 'Madison Square Garden'