geo_redirect 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/geo_redirect.gemspec +21 -0
- data/lib/geo_redirect.rb +146 -0
- data/lib/geo_redirect/railtie.rb +10 -0
- data/lib/geo_redirect/version.rb +3 -0
- data/lib/tasks/geo_redirect.rb +19 -0
- metadata +71 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Waze GeoRedirect
|
2
|
+
|
3
|
+
`GeoRedirect` is a Rack middleware that can be configured to
|
4
|
+
redirect incoming clients to different hosts based on their
|
5
|
+
geo-location data.
|
6
|
+
|
7
|
+
For instance, we use it for [our advertisers site](http://biz.waze.com/)
|
8
|
+
to redirect users to:
|
9
|
+
|
10
|
+
* [biz.waze.co.il](http://biz.waze.co.il) for Israel-incoming traffic.
|
11
|
+
* [biz.waze.com](http://biz.waze.com) for US and Canada incoming traffic.
|
12
|
+
* [biz-world.waze.com](http://biz-world.waze.com/) for any other sources.
|
13
|
+
|
14
|
+
The server stores a session variable with the server it decided on, for future traffic from the same client.
|
15
|
+
|
16
|
+
In addition, you can override these redirects by adding `?redirect=1` to any URL, and by that forcing the server to host from the current domain (and saving that domain to the user's session variable).
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
gem 'geo_redirect'
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle
|
27
|
+
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
These usage instructions were written for Rails products, although I'm pretty sure you could use the gem with any other Rack-based solution.
|
32
|
+
|
33
|
+
You'll need to add this to your `production.rb`:
|
34
|
+
|
35
|
+
Rails.application.middleware.use GeoRedirect::Middleware
|
36
|
+
|
37
|
+
This will make sure `GeoRedirect` runs before your application gets rolling.
|
38
|
+
|
39
|
+
The middleware requires two additional files to be present:
|
40
|
+
|
41
|
+
### 1. Configuration YML
|
42
|
+
|
43
|
+
This should be a YML file representing your redirection rules.
|
44
|
+
|
45
|
+
Here's a template that we use for the setup described above:
|
46
|
+
|
47
|
+
```
|
48
|
+
:us:
|
49
|
+
:host: 'biz.waze.com'
|
50
|
+
:countries: ['US', 'CA']
|
51
|
+
|
52
|
+
:il:
|
53
|
+
:host: 'biz.waze.co.il'
|
54
|
+
:countries: ['IL']
|
55
|
+
|
56
|
+
:world: &default
|
57
|
+
:host: 'biz-world.waze.com'
|
58
|
+
|
59
|
+
:default: *default
|
60
|
+
```
|
61
|
+
|
62
|
+
Note that:
|
63
|
+
|
64
|
+
1. Every main item is a location, and must have a `host` configured.
|
65
|
+
2. A location can have a `countries` array. This will cause a redirect to this location for users from that country code. For available country codes, see [ISO 3166 Country Codes list](http://www.maxmind.com/en/iso3166) from MaxMind (the Geo IP provider `GeoRedirect` uses).
|
66
|
+
3. There must be a `default` location that would be used in case the client can't be geo-located.
|
67
|
+
|
68
|
+
### 2. GeoIP Countries database
|
69
|
+
|
70
|
+
`GeoRedirect` uses the [`geoip`](http://geoip.rubyforge.org/) gem for its geo-location functionality. In particular, it requires the `GeoLite country` free database from [MaxMind](http://www.maxmind.com/).
|
71
|
+
|
72
|
+
You can download the database file [directly from MaxMind](http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz) and unzip it into `db/` in your project, **or** you could use the following `rake` task designed just for that:
|
73
|
+
|
74
|
+
$ rake georedirect:fetch_db
|
75
|
+
|
76
|
+
### Custom paths
|
77
|
+
|
78
|
+
The default paths for these files are:
|
79
|
+
|
80
|
+
1. `config/georedirect.yml`
|
81
|
+
2. `db/GeoIP.dat`
|
82
|
+
|
83
|
+
If that doesn't suit you, you can customize these when adding `GeoRedirect` to your project:
|
84
|
+
|
85
|
+
Rails.application.middleware.use GeoRedirect::Middleware {
|
86
|
+
:db => 'db/geo_database.dat',
|
87
|
+
:config => 'geo_cfg.yml'
|
88
|
+
}
|
89
|
+
|
90
|
+
## Known Issues
|
91
|
+
|
92
|
+
A couple issues I know about but haven't had the time to fix:
|
93
|
+
|
94
|
+
1. Cross-domain session var is required. In particular, if your stubborn user goes to more than 1 server with `?redirect=1`, all of these servers will never redirect them again (until the session is expired).
|
95
|
+
2. When a client accesses your site from an unknown hostname (one that was not configured in the `yml` file) with `?redirect=1`, they will stay in that hostname for the current session, but in the future would be redirected to the configured default hostname (because it was saved on their session var).
|
96
|
+
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
1. Fork it!
|
101
|
+
2. Create your feature branch! (`git checkout -b my-new-feature`)
|
102
|
+
3. Commit your changes! (`git commit -am 'Add some feature'`)
|
103
|
+
4. Push to the branch! (`git push origin my-new-feature`)
|
104
|
+
5. Create new Pull Request!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'geo_redirect/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "geo_redirect"
|
8
|
+
gem.version = GeoRedirect::VERSION
|
9
|
+
gem.authors = ["Sagie Maoz"]
|
10
|
+
gem.email = ["sagie@waze.com"]
|
11
|
+
gem.description = %q{Geo-location based redirector}
|
12
|
+
gem.summary = %q{Rack middleware to redirect clients to hostnames based on geo-location}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.add_dependency "geoip"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
end
|
data/lib/geo_redirect.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'geoip'
|
3
|
+
require 'geo_redirect/version'
|
4
|
+
|
5
|
+
module GeoRedirect
|
6
|
+
# Load rake tasks
|
7
|
+
require 'geo_redirect/railtie' if defined?(Rails)
|
8
|
+
|
9
|
+
# Rack middleware
|
10
|
+
class Middleware
|
11
|
+
def initialize(app, options = {})
|
12
|
+
# Some defaults
|
13
|
+
options[:db] ||= 'db/GeoIP.dat'
|
14
|
+
options[:config] ||= 'config/geo_redirect.yml'
|
15
|
+
|
16
|
+
@app = app
|
17
|
+
|
18
|
+
# Load GeoIP database
|
19
|
+
begin
|
20
|
+
@db = GeoIP.new(options[:db])
|
21
|
+
rescue Errno::EINVAL, Errno::ENOENT => e
|
22
|
+
puts "Could not load GeoIP database file."
|
23
|
+
puts "Please make sure you have a valid one and"
|
24
|
+
puts "add its name to the GeoRedirect middleware."
|
25
|
+
puts "Alternatively, use `rake georedirect:fetch_db`"
|
26
|
+
puts "to fetch it to the default location (under db/)."
|
27
|
+
raise e
|
28
|
+
end
|
29
|
+
|
30
|
+
# Load config object
|
31
|
+
begin
|
32
|
+
@config = YAML.load_file('config/geo_redirect.yml')
|
33
|
+
raise Errno::EINVAL unless @config
|
34
|
+
rescue Errno::EINVAL, Errno::ENOENT => e
|
35
|
+
puts "Could not load GeoRedirect config YML file."
|
36
|
+
puts "Please make sure you have a valid YML file"
|
37
|
+
puts "and pass its name when adding the"
|
38
|
+
puts "GeoRedirect middlware."
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
@request = Rack::Request.new(env)
|
45
|
+
|
46
|
+
if force_redirect?
|
47
|
+
handle_force
|
48
|
+
|
49
|
+
elsif session_exists?
|
50
|
+
handle_session
|
51
|
+
|
52
|
+
else
|
53
|
+
handle_geoip
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def session_exists?
|
58
|
+
host = @request.session['geo_redirect']
|
59
|
+
if host.present? && @config[host].nil? # Invalid var, remove it
|
60
|
+
forget_host
|
61
|
+
host = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
host.present?
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_session
|
68
|
+
host = @request.session['geo_redirect']
|
69
|
+
redirect_request(host)
|
70
|
+
end
|
71
|
+
|
72
|
+
def force_redirect?
|
73
|
+
url = URI.parse(@request.url)
|
74
|
+
Rack::Utils.parse_query(url.query).key? 'redirect'
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_force
|
78
|
+
url = URI.parse(@request.url)
|
79
|
+
host = host_by_hostname(url.host)
|
80
|
+
remember_host(host)
|
81
|
+
redirect_request(url.host, true)
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_geoip
|
85
|
+
# Fetch country code
|
86
|
+
begin
|
87
|
+
res = @db.country(@request.env['REMOTE_ADDR'])
|
88
|
+
code = res.try(:country_code)
|
89
|
+
country = res.try(:country_code2) unless code.nil? || code.zero?
|
90
|
+
rescue
|
91
|
+
country = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
unless country.nil?
|
95
|
+
host = host_by_country(country) # desired host
|
96
|
+
remember_host(host)
|
97
|
+
|
98
|
+
redirect_request(host)
|
99
|
+
else
|
100
|
+
@app.call(@request.env)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def redirect_request(host=nil, same_host=false)
|
105
|
+
redirect = true
|
106
|
+
unless host.nil?
|
107
|
+
hostname = host.is_a?(Symbol) ? @config[host][:host] : host
|
108
|
+
redirect = hostname.present?
|
109
|
+
redirect &&= !@request.host.ends_with?(hostname) unless same_host
|
110
|
+
end
|
111
|
+
|
112
|
+
if redirect
|
113
|
+
url = URI.parse(@request.url)
|
114
|
+
url.port = nil
|
115
|
+
url.host = hostname if host
|
116
|
+
# Remove 'redirect' GET arg
|
117
|
+
url.query = Rack::Utils.parse_query(url.query).tap{ |u|
|
118
|
+
u.delete('redirect')
|
119
|
+
}.to_param
|
120
|
+
url.query = nil if url.query.empty?
|
121
|
+
|
122
|
+
[301, {'Location' => url.to_s}, ['Moved Permanently\n']]
|
123
|
+
else
|
124
|
+
@app.call(@request.env)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def host_by_country(country)
|
129
|
+
hosts = @config.select { |k, v| Array(v[:countries]).include?(country) }
|
130
|
+
hosts.keys.first || :default
|
131
|
+
end
|
132
|
+
|
133
|
+
def host_by_hostname(hostname)
|
134
|
+
hosts = @config.select { |k, v| v[:host] == hostname }
|
135
|
+
hosts.keys.first || :default
|
136
|
+
end
|
137
|
+
|
138
|
+
def remember_host(host)
|
139
|
+
@request.session['geo_redirect'] = host
|
140
|
+
end
|
141
|
+
|
142
|
+
def forget_host
|
143
|
+
remember_host(nil)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
namespace :georedirect do
|
5
|
+
DB_URI = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz'
|
6
|
+
|
7
|
+
desc "Fetches an updated copy of the GeoIP countries DB from MaxMind"
|
8
|
+
task :fetch_db do
|
9
|
+
# Fetches DB copy and gunzips it
|
10
|
+
# Thx http://stackoverflow.com/a/2014317/107085
|
11
|
+
source = open(DB_URI)
|
12
|
+
gz = Zlib::GzipReader.new(source)
|
13
|
+
result = gz.read
|
14
|
+
|
15
|
+
# Write to file
|
16
|
+
filename = Rails.root.join('db', 'GeoIP.dat')
|
17
|
+
File.open(filename, 'w') { |f| f.write(result) }
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: geo_redirect
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sagie Maoz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: geoip
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Geo-location based redirector
|
31
|
+
email:
|
32
|
+
- sagie@waze.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- geo_redirect.gemspec
|
43
|
+
- lib/geo_redirect.rb
|
44
|
+
- lib/geo_redirect/railtie.rb
|
45
|
+
- lib/geo_redirect/version.rb
|
46
|
+
- lib/tasks/geo_redirect.rb
|
47
|
+
homepage: ''
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.24
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Rack middleware to redirect clients to hostnames based on geo-location
|
71
|
+
test_files: []
|