geo_redirect 0.3 → 0.4
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +16 -0
- data/README.md +48 -26
- data/Rakefile +2 -2
- data/geo_redirect.gemspec +15 -16
- data/lib/geo_redirect.rb +1 -4
- data/lib/geo_redirect/middleware.rb +47 -38
- data/lib/geo_redirect/version.rb +1 -1
- data/lib/tasks/geo_redirect.rake +3 -3
- data/spec/geo_redirect_spec.rb +123 -202
- data/spec/spec_helper.rb +7 -7
- data/spec/support/geo_redirect.rb +9 -10
- data/spec/support/rake.rb +5 -5
- metadata +20 -55
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cc53d6e770a29fb9a25ad601803bc220e9de5595
|
4
|
+
data.tar.gz: ce7e9473fb1a032807256e361c72811ef06856c8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4b82b53e72d1ac2f59562433e6f4f30033115692484049253991507aa3871c4030751f382020976fda8b945425c9df93d7957a58534fa1f065b5e7f13b9b998e
|
7
|
+
data.tar.gz: 467773cacba033e14b1f0ee811fc420095e9f650d7239db8caf10a6bc0ebf4948fb967a5fde8e6096b8d8223624d08d4c732047e2194efa5198ab85dbf8c5326
|
data/.rubocop.yml
ADDED
data/README.md
CHANGED
@@ -12,9 +12,15 @@ to redirect users to:
|
|
12
12
|
* [biz.waze.com](http://biz.waze.com) for US and Canada incoming traffic.
|
13
13
|
* [biz-world.waze.com](http://biz-world.waze.com/) for any other sources.
|
14
14
|
|
15
|
-
The server stores a session variable with the server it decided on, for future
|
15
|
+
The server stores a session variable with the server it decided on, for future
|
16
|
+
traffic from the same client.
|
16
17
|
|
17
|
-
In addition, you can override these redirects by adding `?redirect=1` to any
|
18
|
+
In addition, you can override these redirects by adding `?redirect=1` to any
|
19
|
+
URL, and by that forcing the server to host from the current domain (and saving
|
20
|
+
that domain to the user's session variable).
|
21
|
+
|
22
|
+
To skip geo-redirection completely, pass a `?skip_geo=true` argument (this would
|
23
|
+
avoid saving any session value and/or HTTP redirects).
|
18
24
|
|
19
25
|
## Installation
|
20
26
|
|
@@ -45,36 +51,44 @@ This should be a YML file representing your redirection rules.
|
|
45
51
|
|
46
52
|
Here's a template that we use for the setup described above:
|
47
53
|
|
48
|
-
|
49
|
-
:
|
50
|
-
|
51
|
-
:countries: ['US', 'CA']
|
54
|
+
:us:
|
55
|
+
:host: 'biz.waze.com'
|
56
|
+
:countries: ['US', 'CA']
|
52
57
|
|
53
|
-
:il:
|
54
|
-
|
55
|
-
|
58
|
+
:il:
|
59
|
+
:host: 'biz.waze.co.il'
|
60
|
+
:countries: ['IL']
|
56
61
|
|
57
|
-
:world: &default
|
58
|
-
|
62
|
+
:world: &default
|
63
|
+
:host: 'biz-world.waze.com'
|
59
64
|
|
60
|
-
:default: *default
|
61
|
-
```
|
65
|
+
:default: *default
|
62
66
|
|
63
67
|
Note that:
|
64
68
|
|
65
69
|
1. Every main item is a location, and must have a `host` configured.
|
66
|
-
2. A location can have a `countries` array. This will cause a redirect to this
|
67
|
-
|
70
|
+
2. A location can have a `countries` array. This will cause a redirect to this
|
71
|
+
location for users from that country code. For available country codes, see
|
72
|
+
[ISO 3166 Country Codes list](http://www.maxmind.com/en/iso3166) from MaxMind
|
73
|
+
(the Geo IP provider `GeoRedirect` uses).
|
74
|
+
3. There must be a `default` location that would be used in case the client
|
75
|
+
can't be geo-located.
|
68
76
|
|
69
77
|
### 2. GeoIP Countries database
|
70
78
|
|
71
|
-
`GeoRedirect` uses the [`geoip`](http://geoip.rubyforge.org/) gem for its
|
79
|
+
`GeoRedirect` uses the [`geoip`](http://geoip.rubyforge.org/) gem for its
|
80
|
+
geo-location functionality. In particular, it requires the `GeoLite country`
|
81
|
+
free database from [MaxMind](http://www.maxmind.com/).
|
72
82
|
|
73
|
-
You can download the database file [directly from
|
83
|
+
You can download the database file [directly from
|
84
|
+
MaxMind](http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz)
|
85
|
+
and unzip it into `db/` in your project, **or** you could use the following
|
86
|
+
`rake` task designed just for that:
|
74
87
|
|
75
88
|
$ rake geo_redirect:fetch_db
|
76
89
|
|
77
|
-
It'd be a good idea to use this task on your (Capistrano or whatever) deployment
|
90
|
+
It'd be a good idea to use this task on your (Capistrano or whatever) deployment
|
91
|
+
scripts.
|
78
92
|
|
79
93
|
### Custom paths
|
80
94
|
|
@@ -83,33 +97,41 @@ The default paths for these files are:
|
|
83
97
|
1. `config/georedirect.yml`
|
84
98
|
2. `db/GeoIP.dat`
|
85
99
|
|
86
|
-
If that doesn't suit you, you can customize these when adding `GeoRedirect` to
|
100
|
+
If that doesn't suit you, you can customize these when adding `GeoRedirect` to
|
101
|
+
your project:
|
87
102
|
|
88
103
|
Rails.application.middleware.use GeoRedirect::Middleware, {
|
89
|
-
:
|
90
|
-
:
|
104
|
+
db: 'db/geo_database.dat',
|
105
|
+
config: 'geo_cfg.yml'
|
91
106
|
}
|
92
107
|
|
93
108
|
### Debugging
|
94
109
|
|
95
|
-
You can add a `logfile` path string when adding the middleware if you want it to
|
110
|
+
You can add a `logfile` path string when adding the middleware if you want it to
|
111
|
+
log some of its decision process into the file.
|
96
112
|
This is useful when working on your configuration YAML.
|
97
113
|
|
98
|
-
Rails.application.middleware.use GeoRedirect::Middleware, :
|
114
|
+
Rails.application.middleware.use GeoRedirect::Middleware, logfile: 'log/geo_redirect.log'
|
99
115
|
|
100
116
|
`GeoRedirect`'s log messages will always be prefixed with `[GeoRedirect]`.
|
101
117
|
|
102
118
|
### Accessing discovered country
|
103
119
|
|
104
|
-
The country code discovered for the current user is available for your
|
120
|
+
The country code discovered for the current user is available for your
|
121
|
+
convenience, under `session['geo_redirect.country']`.
|
105
122
|
You can use it to make content decisions, or whatever.
|
106
123
|
|
107
124
|
## Known Issues
|
108
125
|
|
109
126
|
A couple issues I know about but haven't had the time to fix:
|
110
127
|
|
111
|
-
1. Cross-domain session var is required. In particular, if your stubborn user
|
112
|
-
|
128
|
+
1. Cross-domain session var is required. In particular, if your stubborn user
|
129
|
+
goes to more than 1 server with `?redirect=1`, all of these servers will
|
130
|
+
never redirect them again (until the session is expired).
|
131
|
+
2. When a client accesses your site from an unknown hostname (one that was not
|
132
|
+
configured in the `yml` file) with `?redirect=1`, they will stay in that
|
133
|
+
hostname for the current session, but in the future would be redirected to
|
134
|
+
the configured default hostname (because it was saved on their session var).
|
113
135
|
|
114
136
|
|
115
137
|
## Contributing
|
data/Rakefile
CHANGED
data/geo_redirect.gemspec
CHANGED
@@ -4,25 +4,24 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'geo_redirect/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name =
|
7
|
+
gem.name = 'geo_redirect'
|
8
8
|
gem.version = GeoRedirect::VERSION
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
11
|
-
gem.description =
|
12
|
-
gem.summary =
|
13
|
-
gem.homepage =
|
9
|
+
gem.authors = ['Sagie Maoz']
|
10
|
+
gem.email = ['sagie@waze.com']
|
11
|
+
gem.description = 'Geo-location based redirector'
|
12
|
+
gem.summary = 'Rack middleware to redirect clients to hostnames based on geo-location'
|
13
|
+
gem.homepage = ''
|
14
14
|
|
15
|
-
gem.add_dependency
|
16
|
-
gem.add_dependency
|
15
|
+
gem.add_dependency 'rake'
|
16
|
+
gem.add_dependency 'geoip'
|
17
17
|
|
18
|
-
gem.add_development_dependency
|
19
|
-
gem.add_development_dependency
|
20
|
-
gem.add_development_dependency
|
21
|
-
gem.add_development_dependency
|
22
|
-
gem.add_development_dependency "simplecov", "~> 0.7.1"
|
18
|
+
gem.add_development_dependency 'rspec', '~> 3.1.0'
|
19
|
+
gem.add_development_dependency 'rack', '~> 1.6.0'
|
20
|
+
gem.add_development_dependency 'rack-test', '~> 0.6.3'
|
21
|
+
gem.add_development_dependency 'simplecov', '~> 0.9.1'
|
23
22
|
|
24
|
-
gem.files = `git ls-files`.split(
|
25
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
23
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
24
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
26
25
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
27
|
-
gem.require_paths = [
|
26
|
+
gem.require_paths = ['lib']
|
28
27
|
end
|
data/lib/geo_redirect.rb
CHANGED
@@ -16,13 +16,16 @@ module GeoRedirect
|
|
16
16
|
@db = init_db(options[:db])
|
17
17
|
@config = init_config(options[:config])
|
18
18
|
|
19
|
-
|
19
|
+
log 'Initialized middleware'
|
20
20
|
end
|
21
21
|
|
22
22
|
def call(env)
|
23
23
|
@request = Rack::Request.new(env)
|
24
24
|
|
25
|
-
if
|
25
|
+
if skip_redirect?
|
26
|
+
@app.call(env)
|
27
|
+
|
28
|
+
elsif force_redirect?
|
26
29
|
handle_force
|
27
30
|
|
28
31
|
elsif session_exists?
|
@@ -36,7 +39,7 @@ module GeoRedirect
|
|
36
39
|
def session_exists?
|
37
40
|
host = @request.session['geo_redirect']
|
38
41
|
if host && @config[host].nil? # Invalid var, remove it
|
39
|
-
|
42
|
+
log 'Invalid session var, forgetting'
|
40
43
|
forget_host(host)
|
41
44
|
host = nil
|
42
45
|
end
|
@@ -46,7 +49,7 @@ module GeoRedirect
|
|
46
49
|
|
47
50
|
def handle_session
|
48
51
|
host = @request.session['geo_redirect']
|
49
|
-
|
52
|
+
log "Handling session var: #{host}"
|
50
53
|
redirect_request(host)
|
51
54
|
end
|
52
55
|
|
@@ -55,10 +58,15 @@ module GeoRedirect
|
|
55
58
|
Rack::Utils.parse_query(url.query).key? 'redirect'
|
56
59
|
end
|
57
60
|
|
61
|
+
def skip_redirect?
|
62
|
+
url = URI.parse(@request.url)
|
63
|
+
Rack::Utils.parse_query(url.query).key? 'skip_geo'
|
64
|
+
end
|
65
|
+
|
58
66
|
def handle_force
|
59
67
|
url = URI.parse(@request.url)
|
60
68
|
host = host_by_hostname(url.host)
|
61
|
-
|
69
|
+
log "Handling force flag: #{host}"
|
62
70
|
remember_host(host)
|
63
71
|
redirect_request(url.host, true)
|
64
72
|
end
|
@@ -66,28 +74,28 @@ module GeoRedirect
|
|
66
74
|
def handle_geoip
|
67
75
|
country = country_from_request rescue nil
|
68
76
|
@request.session['geo_redirect.country'] = country
|
69
|
-
|
77
|
+
log "GeoIP match: country code #{country}"
|
70
78
|
|
71
|
-
|
79
|
+
if country.nil?
|
80
|
+
@app.call(@request.env)
|
81
|
+
else
|
72
82
|
host = host_by_country(country) # desired host
|
73
|
-
|
83
|
+
log "GeoIP host match: #{host}"
|
74
84
|
remember_host(host)
|
75
85
|
|
76
86
|
redirect_request(host)
|
77
|
-
else
|
78
|
-
@app.call(@request.env)
|
79
87
|
end
|
80
88
|
end
|
81
89
|
|
82
|
-
def redirect_request(host=nil, same_host=false)
|
90
|
+
def redirect_request(host = nil, same_host = false)
|
83
91
|
hostname = hostname_by_host(host)
|
84
92
|
|
85
93
|
if should_redirect?(hostname, same_host)
|
86
94
|
url = redirect_url(hostname)
|
87
95
|
|
88
|
-
|
96
|
+
log "Redirecting to #{url}"
|
89
97
|
[301,
|
90
|
-
{'Location' => url.to_s, 'Content-Type' => 'text/plain'},
|
98
|
+
{ 'Location' => url.to_s, 'Content-Type' => 'text/plain' },
|
91
99
|
['Moved Permanently\n']]
|
92
100
|
else
|
93
101
|
@app.call(@request.env)
|
@@ -95,12 +103,12 @@ module GeoRedirect
|
|
95
103
|
end
|
96
104
|
|
97
105
|
def host_by_country(country)
|
98
|
-
hosts = @config.select { |
|
106
|
+
hosts = @config.select { |_k, v| Array(v[:countries]).include?(country) }
|
99
107
|
hosts.keys.first || :default
|
100
108
|
end
|
101
109
|
|
102
110
|
def host_by_hostname(hostname)
|
103
|
-
hosts = @config.select { |
|
111
|
+
hosts = @config.select { |_k, v| v[:host] == hostname }
|
104
112
|
hosts.keys.first || :default
|
105
113
|
end
|
106
114
|
|
@@ -109,17 +117,18 @@ module GeoRedirect
|
|
109
117
|
end
|
110
118
|
|
111
119
|
def remember_host(host)
|
112
|
-
|
120
|
+
log "Remembering: #{host}"
|
113
121
|
@request.session['geo_redirect'] = host
|
114
122
|
end
|
115
123
|
|
116
124
|
def forget_host(host)
|
117
|
-
|
125
|
+
log "Forgetting: #{host}"
|
118
126
|
remember_host(nil)
|
119
127
|
end
|
120
128
|
|
121
129
|
protected
|
122
|
-
|
130
|
+
|
131
|
+
def log(message, level = :debug)
|
123
132
|
@logger.send(level, "[GeoRedirect] #{message}") unless @logger.nil?
|
124
133
|
end
|
125
134
|
|
@@ -134,35 +143,38 @@ module GeoRedirect
|
|
134
143
|
rescue Errno::EINVAL, Errno::ENOENT
|
135
144
|
message = <<-ERROR
|
136
145
|
Could not load GeoIP database file.
|
137
|
-
Please make sure you have a valid one and add its name to
|
138
|
-
|
146
|
+
Please make sure you have a valid one and add its name to
|
147
|
+
the GeoRedirect middleware.
|
148
|
+
Alternatively, use `rake georedirect:fetch_db` to fetch it
|
149
|
+
to the default location (under db/).
|
139
150
|
ERROR
|
140
|
-
|
151
|
+
log(message, :error)
|
141
152
|
end
|
142
153
|
|
143
154
|
def init_config(path)
|
144
|
-
YAML.load_file(path) ||
|
155
|
+
YAML.load_file(path) || fail(Errno::EINVAL)
|
145
156
|
rescue Errno::EINVAL, Errno::ENOENT, Psych::SyntaxError, SyntaxError
|
146
157
|
message = <<-ERROR
|
147
158
|
Could not load GeoRedirect config YML file.
|
148
|
-
Please make sure you have a valid YML file and pass its name
|
159
|
+
Please make sure you have a valid YML file and pass its name
|
160
|
+
when adding the GeoRedirect middlware.
|
149
161
|
ERROR
|
150
|
-
|
162
|
+
log(message, :error)
|
151
163
|
end
|
152
164
|
|
153
165
|
def request_ip
|
154
|
-
ip_address =
|
166
|
+
ip_address =
|
167
|
+
@request.env['HTTP_X_FORWARDED_FOR'] || @request.env['REMOTE_ADDR']
|
155
168
|
# take only the first given ip
|
156
169
|
ip_address.split(',').first.strip
|
157
170
|
end
|
158
171
|
|
159
|
-
|
160
172
|
def country_from_request
|
161
173
|
ip = request_ip
|
162
|
-
|
174
|
+
log "Handling GeoIP lookup: IP #{ip}"
|
163
175
|
|
164
|
-
res
|
165
|
-
code
|
176
|
+
res = @db.country(ip)
|
177
|
+
code = res[:country_code]
|
166
178
|
|
167
179
|
res[:country_code2] unless code.nil? || code.zero?
|
168
180
|
end
|
@@ -173,9 +185,9 @@ module GeoRedirect
|
|
173
185
|
url.host = hostname if hostname
|
174
186
|
|
175
187
|
# Remove force flag from GET arguments
|
176
|
-
query_hash = Rack::Utils.parse_query(url.query).tap
|
188
|
+
query_hash = Rack::Utils.parse_query(url.query).tap do |u|
|
177
189
|
u.delete('redirect')
|
178
|
-
|
190
|
+
end
|
179
191
|
|
180
192
|
# Copy query
|
181
193
|
url.query = URI.encode_www_form(query_hash)
|
@@ -185,13 +197,10 @@ module GeoRedirect
|
|
185
197
|
end
|
186
198
|
|
187
199
|
def should_redirect?(hostname, same_host)
|
188
|
-
|
189
|
-
hostname_ends_with = %r{#{hostname.gsub(".", "\.")}$}
|
190
|
-
(@request.host =~ hostname_ends_with).nil?
|
191
|
-
else
|
192
|
-
true
|
193
|
-
end
|
194
|
-
end
|
200
|
+
return true if hostname.nil? || same_host
|
195
201
|
|
202
|
+
hostname_ends_with = %r{#{hostname.gsub(".", "\.")}$}
|
203
|
+
(@request.host =~ hostname_ends_with).nil?
|
204
|
+
end
|
196
205
|
end
|
197
206
|
end
|
data/lib/geo_redirect/version.rb
CHANGED
data/lib/tasks/geo_redirect.rake
CHANGED
@@ -5,9 +5,9 @@ require 'zlib'
|
|
5
5
|
namespace :geo_redirect do
|
6
6
|
DB_URI = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz'
|
7
7
|
|
8
|
-
desc
|
9
|
-
task :fetch_db, :db_path do |
|
10
|
-
args.with_defaults(:
|
8
|
+
desc 'Fetches an updated copy of the GeoIP countries DB from MaxMind'
|
9
|
+
task :fetch_db, :db_path do |_t, args|
|
10
|
+
args.with_defaults(db_path: GeoRedirect::DEFAULT_DB_PATH)
|
11
11
|
|
12
12
|
# Fetches DB copy and gunzips it
|
13
13
|
# Thx http://stackoverflow.com/a/2014317/107085
|
data/spec/geo_redirect_spec.rb
CHANGED
@@ -1,141 +1,130 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'logger'
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe 'geo_redirect' do
|
6
6
|
include GeoRedirect::Support
|
7
7
|
include Rack::Test::Methods
|
8
8
|
|
9
9
|
def session
|
10
|
-
last_request.env['rack.session']
|
10
|
+
last_request.env['rack.session'] || {}
|
11
11
|
end
|
12
12
|
|
13
13
|
def url_scheme
|
14
14
|
last_request.env['rack.url_scheme']
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
@config = YAML.load_file(fixture_path("config.yml"))
|
19
|
-
end
|
17
|
+
let(:config) { YAML.load_file(fixture_path('config.yml')) }
|
20
18
|
|
21
|
-
describe
|
22
|
-
it
|
19
|
+
describe '#load_config' do
|
20
|
+
it 'reads a config file' do
|
23
21
|
mock_app
|
24
|
-
|
25
|
-
@app.config.should_not be_nil
|
26
|
-
@app.config.should eq(@config)
|
22
|
+
expect(@app.config).to eq(config)
|
27
23
|
end
|
28
24
|
|
29
|
-
it
|
30
|
-
mock_app :
|
31
|
-
log_should_include(
|
32
|
-
log_should_include(
|
25
|
+
it 'errors on not-found config file' do
|
26
|
+
mock_app config: nonexisting_file_path
|
27
|
+
log_should_include('ERROR')
|
28
|
+
log_should_include('Could not load GeoRedirect config YML file')
|
33
29
|
end
|
34
30
|
|
35
|
-
it
|
36
|
-
mock_app :
|
37
|
-
log_should_include(
|
38
|
-
log_should_include(
|
31
|
+
it 'errors on a mal-formatted config file' do
|
32
|
+
mock_app config: fixture_path('config.bad.yml')
|
33
|
+
log_should_include('ERROR')
|
34
|
+
log_should_include('Could not load GeoRedirect config YML file')
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
42
|
-
describe
|
43
|
-
it
|
38
|
+
describe '#load_db' do
|
39
|
+
it 'reads a db file' do
|
44
40
|
mock_app
|
45
|
-
|
46
|
-
@app.db.should_not be_nil
|
47
|
-
@app.db.should be_a_kind_of GeoIP
|
41
|
+
expect(@app.db).to be_a(GeoIP)
|
48
42
|
end
|
49
43
|
|
50
|
-
it
|
51
|
-
mock_app :
|
52
|
-
log_should_include(
|
53
|
-
log_should_include(
|
44
|
+
it 'errors on not-found db file' do
|
45
|
+
mock_app db: nonexisting_file_path
|
46
|
+
log_should_include('ERROR')
|
47
|
+
log_should_include('Could not load GeoIP database file.')
|
54
48
|
end
|
55
49
|
|
56
50
|
# this example is disabled, as it seems that
|
57
51
|
# GeoIP does not let me know if a db file is proper.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
=end
|
52
|
+
# it "errors on mal-formatted db file" do
|
53
|
+
# pending "GeoIP does not raise on bad files"
|
54
|
+
# mock_app :db => fixture_path("config.yml")
|
55
|
+
# log_should_include("ERROR")
|
56
|
+
# log_should_include("Could not load GeoIP database file.")
|
57
|
+
# end
|
66
58
|
end
|
67
59
|
|
68
|
-
describe
|
69
|
-
describe
|
70
|
-
before
|
71
|
-
mock_app
|
72
|
-
end
|
60
|
+
describe '#log' do
|
61
|
+
describe 'with valid logfile path' do
|
62
|
+
before { mock_app }
|
73
63
|
|
74
|
-
it
|
64
|
+
it 'initiates a log file' do
|
75
65
|
@app.instance_variable_get(:"@logger").should be_kind_of Logger
|
76
66
|
end
|
77
67
|
|
78
|
-
it
|
79
|
-
message =
|
68
|
+
it 'prints to log file' do
|
69
|
+
message = 'Testing GeoRedirect logger'
|
80
70
|
@app.send(:log, [message])
|
81
71
|
log_should_include(message)
|
82
72
|
end
|
83
73
|
end
|
84
74
|
|
85
|
-
it
|
86
|
-
mock_app :
|
87
|
-
@app.instance_variable_get(:"@logger").
|
75
|
+
it 'ignores invalid logfile path' do
|
76
|
+
mock_app logfile: '/no_such_file'
|
77
|
+
expect(@app.instance_variable_get(:"@logger")).to be_nil
|
88
78
|
end
|
89
79
|
end
|
90
80
|
|
91
|
-
describe
|
92
|
-
before
|
93
|
-
|
94
|
-
end
|
81
|
+
describe '#host_by_country' do
|
82
|
+
before { mock_app }
|
83
|
+
subject { @app.host_by_country(country) }
|
95
84
|
|
96
|
-
|
97
|
-
|
98
|
-
|
85
|
+
context 'when country is valid' do
|
86
|
+
let(:country) { 'US' }
|
87
|
+
it { is_expected.to eq(:us) }
|
99
88
|
end
|
100
89
|
|
101
|
-
|
102
|
-
|
90
|
+
context 'when country is invalid' do
|
91
|
+
let(:country) { 'WHATEVER' }
|
92
|
+
it { is_expected.to eq(:default) }
|
103
93
|
end
|
104
94
|
end
|
105
95
|
|
106
|
-
describe
|
107
|
-
before
|
108
|
-
|
109
|
-
end
|
96
|
+
describe 'host_by_hostname' do
|
97
|
+
before { mock_app }
|
98
|
+
subject { @app.host_by_hostname(hostname) }
|
110
99
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@app.host_by_hostname("biz.world.waze.com").should eq(:world)
|
100
|
+
context 'when hostname is valid' do
|
101
|
+
let(:hostname) { 'biz.waze.co.il' }
|
102
|
+
it { is_expected.to eq(:il) }
|
115
103
|
end
|
116
104
|
|
117
|
-
|
118
|
-
|
105
|
+
context 'when hostname is invalid' do
|
106
|
+
let(:hostname) { 'something.else.org' }
|
107
|
+
it { is_expected.to eq(:default) }
|
119
108
|
end
|
120
109
|
end
|
121
110
|
|
122
|
-
describe
|
111
|
+
describe 'redirect logic' do
|
123
112
|
before :each do
|
124
113
|
mock_app
|
125
114
|
end
|
126
115
|
|
127
|
-
def mock_request_from(code, options={})
|
128
|
-
ip =
|
116
|
+
def mock_request_from(code, options = {})
|
117
|
+
ip = '5.5.5.5'
|
129
118
|
|
130
119
|
if code.nil?
|
131
120
|
country = nil
|
132
121
|
else
|
133
|
-
country = GeoIP::Country.stub(
|
134
|
-
|
122
|
+
country = GeoIP::Country.stub(country_code2: code,
|
123
|
+
country_code: 5)
|
135
124
|
end
|
136
125
|
@app.db.stub(:country).with(ip).and_return(country)
|
137
126
|
|
138
|
-
env = {
|
127
|
+
env = { 'REMOTE_ADDR' => ip, 'HTTP_HOST' => 'biz.waze.co.il' }
|
139
128
|
|
140
129
|
if options[:session]
|
141
130
|
env['rack.session'] ||= {}
|
@@ -144,23 +133,22 @@ describe "geo_redirect" do
|
|
144
133
|
end
|
145
134
|
|
146
135
|
args = {}
|
147
|
-
if options[:force]
|
148
|
-
|
149
|
-
end
|
136
|
+
args[:redirect] = 1 if options[:force]
|
137
|
+
args[:skip_geo] = true if options[:skip]
|
150
138
|
|
151
|
-
get
|
139
|
+
get '/', args, env
|
152
140
|
end
|
153
141
|
|
154
142
|
def should_redirect_to(host)
|
155
|
-
last_response.body.should include(
|
143
|
+
last_response.body.should include('Moved Permanently')
|
156
144
|
last_response.status.should eq(301)
|
157
|
-
last_response.headers.should have_key(
|
158
|
-
url = "#{url_scheme}://#{
|
159
|
-
last_response.headers[
|
145
|
+
last_response.headers.should have_key('Location')
|
146
|
+
url = "#{url_scheme}://#{config[host][:host]}"
|
147
|
+
last_response.headers['Location'].should start_with(url)
|
160
148
|
end
|
161
149
|
|
162
150
|
def should_not_redirect
|
163
|
-
last_response.body.should include(
|
151
|
+
last_response.body.should include('Hello world!')
|
164
152
|
last_response.should be_ok
|
165
153
|
end
|
166
154
|
|
@@ -172,149 +160,82 @@ describe "geo_redirect" do
|
|
172
160
|
session['geo_redirect.country'].should eq(country)
|
173
161
|
end
|
174
162
|
|
175
|
-
describe
|
176
|
-
describe
|
177
|
-
before
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
it "redirects to destination" do
|
182
|
-
should_redirect_to :us
|
183
|
-
end
|
184
|
-
|
185
|
-
it "stores decision in session" do
|
186
|
-
should_remember :us
|
187
|
-
end
|
188
|
-
|
189
|
-
it "stores discovered country in session" do
|
190
|
-
should_remember_country "US"
|
191
|
-
end
|
163
|
+
describe 'without session memory' do
|
164
|
+
describe 'for a foreign source' do
|
165
|
+
before { mock_request_from 'US' }
|
166
|
+
it { should_redirect_to :us }
|
167
|
+
it { should_remember :us }
|
168
|
+
it { should_remember_country 'US' }
|
192
169
|
end
|
193
170
|
|
194
|
-
describe
|
195
|
-
before
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
it "does not redirect" do
|
200
|
-
should_not_redirect
|
201
|
-
end
|
202
|
-
|
203
|
-
it "stores decision in session" do
|
204
|
-
should_remember :il
|
205
|
-
end
|
206
|
-
|
207
|
-
it "stores discovered country in session" do
|
208
|
-
should_remember_country "IL"
|
209
|
-
end
|
171
|
+
describe 'for a local source' do
|
172
|
+
before { mock_request_from 'IL' }
|
173
|
+
it { should_not_redirect }
|
174
|
+
it { should_remember :il }
|
175
|
+
it { should_remember_country 'IL' }
|
210
176
|
end
|
211
177
|
|
212
|
-
describe
|
213
|
-
before
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
it "redirects to default" do
|
218
|
-
should_redirect_to :default
|
219
|
-
end
|
220
|
-
|
221
|
-
it "stores decision in session" do
|
222
|
-
should_remember :default
|
223
|
-
end
|
224
|
-
|
225
|
-
it "stores discovered country in session" do
|
226
|
-
should_remember_country "SOMEWHERE OVER THE RAINBOW"
|
227
|
-
end
|
178
|
+
describe 'for an unknown source' do
|
179
|
+
before { mock_request_from 'SOMEWHERE OVER THE RAINBOW' }
|
180
|
+
it { should_redirect_to :default }
|
181
|
+
it { should_remember :default }
|
182
|
+
it { should_remember_country 'SOMEWHERE OVER THE RAINBOW' }
|
228
183
|
end
|
229
184
|
end
|
230
185
|
|
231
|
-
describe
|
232
|
-
before :
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
it "redirects to remembered destination" do
|
237
|
-
should_redirect_to :default
|
238
|
-
end
|
239
|
-
|
240
|
-
it "leaves session as is" do
|
241
|
-
should_remember :default
|
242
|
-
end
|
243
|
-
|
244
|
-
it "remembers discovered country" do
|
245
|
-
should_remember_country "US"
|
246
|
-
end
|
186
|
+
describe 'with valid session memory' do
|
187
|
+
before { mock_request_from 'US', session: :default }
|
188
|
+
it { should_redirect_to :default }
|
189
|
+
it { should_remember :default }
|
190
|
+
it { should_remember_country 'US' }
|
247
191
|
end
|
248
192
|
|
249
|
-
describe
|
250
|
-
before :
|
251
|
-
mock_request_from "US", :session => "foo"
|
252
|
-
end
|
253
|
-
|
254
|
-
it "removes invalid session data" do
|
255
|
-
session['geo_redirect'].should_not eq("foo")
|
256
|
-
end
|
257
|
-
|
258
|
-
it "redirects to destination" do
|
259
|
-
should_redirect_to :us
|
260
|
-
end
|
193
|
+
describe 'with invalid session memory' do
|
194
|
+
before { mock_request_from 'US', session: 'foo' }
|
261
195
|
|
262
|
-
it
|
263
|
-
|
196
|
+
it 'removes invalid session data' do
|
197
|
+
expect(session['geo_redirect']).not_to eq('foo')
|
264
198
|
end
|
265
199
|
|
266
|
-
it
|
267
|
-
|
268
|
-
|
200
|
+
it { should_redirect_to :us }
|
201
|
+
it { should_remember :us }
|
202
|
+
it { should_remember_country 'US' }
|
269
203
|
end
|
270
204
|
|
271
|
-
describe
|
272
|
-
before :
|
273
|
-
mock_request_from "US", :force => true
|
274
|
-
end
|
205
|
+
describe 'with forced redirect flag' do
|
206
|
+
before { mock_request_from 'US', force: true }
|
275
207
|
|
276
|
-
it
|
277
|
-
|
278
|
-
last_response.headers[
|
208
|
+
it { should_redirect_to :il }
|
209
|
+
it 'rewrites the flag out' do
|
210
|
+
expect(last_response.headers['Location']).not_to include('redirect=1')
|
279
211
|
end
|
280
212
|
|
281
|
-
it
|
282
|
-
|
283
|
-
end
|
284
|
-
|
285
|
-
it "does not store discovered country in session" do
|
286
|
-
should_remember_country nil
|
287
|
-
end
|
213
|
+
it { should_remember :il }
|
214
|
+
it { should_remember_country nil }
|
288
215
|
end
|
289
216
|
|
290
|
-
describe
|
291
|
-
before :
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
it "does not redirect" do
|
296
|
-
should_not_redirect
|
297
|
-
end
|
298
|
-
|
299
|
-
it "does not store session" do
|
300
|
-
should_remember nil
|
301
|
-
end
|
302
|
-
|
303
|
-
it "does not store discovered country in session" do
|
304
|
-
should_remember_country nil
|
305
|
-
end
|
217
|
+
describe 'with skip flag' do
|
218
|
+
before { mock_request_from 'US', skip: true }
|
219
|
+
it { should_not_redirect }
|
220
|
+
it { should_remember nil }
|
221
|
+
it { should_remember_country nil }
|
306
222
|
end
|
307
223
|
|
224
|
+
describe 'with no recognizable IP' do
|
225
|
+
before { mock_request_from nil }
|
226
|
+
it { should_not_redirect }
|
227
|
+
it { should_remember nil }
|
228
|
+
it { should_remember_country nil }
|
229
|
+
end
|
308
230
|
end
|
309
231
|
end
|
310
232
|
|
311
|
-
describe
|
312
|
-
include_context
|
233
|
+
describe 'geo_redirect:fetch_db' do
|
234
|
+
include_context 'rake'
|
313
235
|
|
314
|
-
it
|
315
|
-
|
316
|
-
subject.invoke(
|
317
|
-
|
318
|
-
@dbfile.size.should be > 0
|
236
|
+
it 'downloads a GeoIP db to a location' do
|
237
|
+
dbfile = Tempfile.new('db')
|
238
|
+
subject.invoke(dbfile.path)
|
239
|
+
expect(dbfile.size).to be > 0
|
319
240
|
end
|
320
241
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
require
|
1
|
+
require 'simplecov'
|
2
2
|
SimpleCov.start
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
4
|
+
require 'rspec'
|
5
|
+
require 'rack'
|
6
|
+
require 'rack/test'
|
7
7
|
|
8
8
|
current_dir = File.dirname(__FILE__)
|
9
9
|
$LOAD_PATH.unshift(File.join(current_dir, '..', 'lib'))
|
10
10
|
$LOAD_PATH.unshift(current_dir)
|
11
|
-
require
|
11
|
+
require 'geo_redirect'
|
12
12
|
|
13
|
-
Dir[File.join(current_dir,
|
13
|
+
Dir[File.join(current_dir, 'support/**/*.rb')].each { |f| require f }
|
14
14
|
|
15
15
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
16
16
|
RSpec.configure do |config|
|
@@ -18,5 +18,5 @@ RSpec.configure do |config|
|
|
18
18
|
config.run_all_when_everything_filtered = true
|
19
19
|
config.filter_run :focus
|
20
20
|
|
21
|
-
config.order =
|
21
|
+
config.order = 'random'
|
22
22
|
end
|
@@ -5,7 +5,7 @@ module GeoRedirect
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def nonexisting_file_path
|
8
|
-
|
8
|
+
'/no_such_file'
|
9
9
|
end
|
10
10
|
|
11
11
|
def app
|
@@ -14,16 +14,16 @@ module GeoRedirect
|
|
14
14
|
|
15
15
|
def mock_app(options = {})
|
16
16
|
# Simple HTTP server that always returns 'Hello world!'
|
17
|
-
main_app = lambda
|
17
|
+
main_app = lambda do |env|
|
18
18
|
Rack::Request.new(env)
|
19
|
-
headers = {
|
20
|
-
[200, headers, [
|
21
|
-
|
19
|
+
headers = { 'Content-Type' => 'text/html' }
|
20
|
+
[200, headers, ['Hello world!']]
|
21
|
+
end
|
22
22
|
|
23
|
-
@logfile = Tempfile.new(
|
24
|
-
options = { :
|
25
|
-
:
|
26
|
-
:
|
23
|
+
@logfile = Tempfile.new('log')
|
24
|
+
options = { config: fixture_path('config.yml'),
|
25
|
+
db: fixture_path('GeoIP.dat'),
|
26
|
+
logfile: @logfile.path
|
27
27
|
}.merge(options)
|
28
28
|
|
29
29
|
builder = Rack::Builder.new
|
@@ -38,4 +38,3 @@ module GeoRedirect
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
data/spec/support/rake.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# thanks http://robots.thoughtbot.com/post/11957424161/test-rake-tasks-like-a-boss
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'rake'
|
4
4
|
|
5
|
-
shared_context
|
5
|
+
shared_context 'rake' do
|
6
6
|
let(:rake) { Rake::Application.new }
|
7
7
|
let(:task_name) { self.class.top_level_description }
|
8
|
-
let(:task_path) { "lib/tasks/#{task_name.split(
|
8
|
+
let(:task_path) { "lib/tasks/#{task_name.split(':').first}" }
|
9
9
|
subject { rake[task_name] }
|
10
10
|
|
11
11
|
before do
|
12
12
|
Rake.application = rake
|
13
|
-
Rake.application
|
14
|
-
[File.join(File.dirname(__FILE__),
|
13
|
+
Rake.application
|
14
|
+
.rake_require(task_path, [File.join(File.dirname(__FILE__), '..', '..')])
|
15
15
|
|
16
16
|
Rake::Task.define_task(:environment)
|
17
17
|
end
|
metadata
CHANGED
@@ -1,128 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geo_redirect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
5
|
-
prerelease:
|
4
|
+
version: '0.4'
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Sagie Maoz
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2015-01-12 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rake
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: geoip
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rspec
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
45
|
- - ~>
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
47
|
+
version: 3.1.0
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
52
|
- - ~>
|
60
53
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
54
|
+
version: 3.1.0
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: rack
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
59
|
- - ~>
|
68
60
|
- !ruby/object:Gem::Version
|
69
|
-
version: 1.
|
61
|
+
version: 1.6.0
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
66
|
- - ~>
|
76
67
|
- !ruby/object:Gem::Version
|
77
|
-
version: 1.
|
68
|
+
version: 1.6.0
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: rack-test
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
73
|
- - ~>
|
84
74
|
- !ruby/object:Gem::Version
|
85
|
-
version: 0.6.
|
75
|
+
version: 0.6.3
|
86
76
|
type: :development
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
80
|
- - ~>
|
92
81
|
- !ruby/object:Gem::Version
|
93
|
-
version: 0.6.
|
94
|
-
- !ruby/object:Gem::Dependency
|
95
|
-
name: debugger
|
96
|
-
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
|
-
requirements:
|
99
|
-
- - ~>
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
version: 1.5.0
|
102
|
-
type: :development
|
103
|
-
prerelease: false
|
104
|
-
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
|
-
requirements:
|
107
|
-
- - ~>
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: 1.5.0
|
82
|
+
version: 0.6.3
|
110
83
|
- !ruby/object:Gem::Dependency
|
111
84
|
name: simplecov
|
112
85
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
86
|
requirements:
|
115
87
|
- - ~>
|
116
88
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
89
|
+
version: 0.9.1
|
118
90
|
type: :development
|
119
91
|
prerelease: false
|
120
92
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
93
|
requirements:
|
123
94
|
- - ~>
|
124
95
|
- !ruby/object:Gem::Version
|
125
|
-
version: 0.
|
96
|
+
version: 0.9.1
|
126
97
|
description: Geo-location based redirector
|
127
98
|
email:
|
128
99
|
- sagie@waze.com
|
@@ -132,6 +103,7 @@ extra_rdoc_files: []
|
|
132
103
|
files:
|
133
104
|
- .gitignore
|
134
105
|
- .rspec
|
106
|
+
- .rubocop.yml
|
135
107
|
- .travis.yml
|
136
108
|
- Gemfile
|
137
109
|
- LICENSE.txt
|
@@ -152,33 +124,26 @@ files:
|
|
152
124
|
- spec/support/rake.rb
|
153
125
|
homepage: ''
|
154
126
|
licenses: []
|
127
|
+
metadata: {}
|
155
128
|
post_install_message:
|
156
129
|
rdoc_options: []
|
157
130
|
require_paths:
|
158
131
|
- lib
|
159
132
|
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
-
none: false
|
161
133
|
requirements:
|
162
|
-
- -
|
134
|
+
- - '>='
|
163
135
|
- !ruby/object:Gem::Version
|
164
136
|
version: '0'
|
165
|
-
segments:
|
166
|
-
- 0
|
167
|
-
hash: 3031125964627560618
|
168
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
-
none: false
|
170
138
|
requirements:
|
171
|
-
- -
|
139
|
+
- - '>='
|
172
140
|
- !ruby/object:Gem::Version
|
173
141
|
version: '0'
|
174
|
-
segments:
|
175
|
-
- 0
|
176
|
-
hash: 3031125964627560618
|
177
142
|
requirements: []
|
178
143
|
rubyforge_project:
|
179
|
-
rubygems_version:
|
144
|
+
rubygems_version: 2.4.5
|
180
145
|
signing_key:
|
181
|
-
specification_version:
|
146
|
+
specification_version: 4
|
182
147
|
summary: Rack middleware to redirect clients to hostnames based on geo-location
|
183
148
|
test_files:
|
184
149
|
- spec/fixtures/GeoIP.dat
|