geo_redirect 0.2.4 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 TODO: Write your name
1
+ Copyright (c) 2012 Sagie Maoz
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
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.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Waze GeoRedirect
2
+ [![Build Status](https://secure.travis-ci.org/wazeHQ/geo_redirect.png)](http://travis-ci.org/wazeHQ/geo_redirect) [![Code Climate](https://codeclimate.com/github/wazeHQ/geo_redirect.png)](https://codeclimate.com/github/wazeHQ/geo_redirect) [![Gem Version](https://badge.fury.io/rb/geo_redirect.png)](http://badge.fury.io/rb/geo_redirect)
2
3
 
3
4
  `GeoRedirect` is a Rack middleware that can be configured to
4
5
  redirect incoming clients to different hosts based on their
@@ -71,7 +72,7 @@ Note that:
71
72
 
72
73
  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
 
74
- $ rake georedirect:fetch_db
75
+ $ rake geo_redirect:fetch_db
75
76
 
76
77
  It'd be a good idea to use this task on your (Capistrano or whatever) deployment scripts.
77
78
 
@@ -98,6 +99,11 @@ This is useful when working on your configuration YAML.
98
99
 
99
100
  `GeoRedirect`'s log messages will always be prefixed with `[GeoRedirect]`.
100
101
 
102
+ ### Accessing discovered country
103
+
104
+ The country code discovered for the current user is available for your convenience, under `session['geo_redirect.country']`.
105
+ You can use it to make content decisions, or whatever.
106
+
101
107
  ## Known Issues
102
108
 
103
109
  A couple issues I know about but haven't had the time to fix:
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => [:spec]
data/geo_redirect.gemspec CHANGED
@@ -12,8 +12,15 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{Rack middleware to redirect clients to hostnames based on geo-location}
13
13
  gem.homepage = ""
14
14
 
15
+ gem.add_dependency "rake"
15
16
  gem.add_dependency "geoip"
16
17
 
18
+ gem.add_development_dependency "rspec", "~> 2.13.0"
19
+ gem.add_development_dependency "rack", "~> 1.5.2"
20
+ gem.add_development_dependency "rack-test", "~> 0.6.2"
21
+ gem.add_development_dependency "debugger", "~> 1.5.0"
22
+ gem.add_development_dependency "simplecov", "~> 0.7.1"
23
+
17
24
  gem.files = `git ls-files`.split($/)
18
25
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
26
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
@@ -0,0 +1,197 @@
1
+ require 'yaml'
2
+ require 'geoip'
3
+
4
+ module GeoRedirect
5
+ class Middleware
6
+ attr_accessor :db, :config
7
+
8
+ def initialize(app, options = {})
9
+ # Some defaults
10
+ options[:db] ||= DEFAULT_DB_PATH
11
+ options[:config] ||= DEFAULT_CONFIG_PATH
12
+
13
+ @app = app
14
+
15
+ @logger = init_logger(options[:logfile]) if options[:logfile]
16
+ @db = init_db(options[:db])
17
+ @config = init_config(options[:config])
18
+
19
+ self.log "Initialized middleware"
20
+ end
21
+
22
+ def call(env)
23
+ @request = Rack::Request.new(env)
24
+
25
+ if force_redirect?
26
+ handle_force
27
+
28
+ elsif session_exists?
29
+ handle_session
30
+
31
+ else
32
+ handle_geoip
33
+ end
34
+ end
35
+
36
+ def session_exists?
37
+ host = @request.session['geo_redirect']
38
+ if host && @config[host].nil? # Invalid var, remove it
39
+ self.log "Invalid session var, forgetting"
40
+ forget_host(host)
41
+ host = nil
42
+ end
43
+
44
+ !host.nil?
45
+ end
46
+
47
+ def handle_session
48
+ host = @request.session['geo_redirect']
49
+ self.log "Handling session var: #{host}"
50
+ redirect_request(host)
51
+ end
52
+
53
+ def force_redirect?
54
+ url = URI.parse(@request.url)
55
+ Rack::Utils.parse_query(url.query).key? 'redirect'
56
+ end
57
+
58
+ def handle_force
59
+ url = URI.parse(@request.url)
60
+ host = host_by_hostname(url.host)
61
+ self.log "Handling force flag: #{host}"
62
+ remember_host(host)
63
+ redirect_request(url.host, true)
64
+ end
65
+
66
+ def handle_geoip
67
+ country = country_from_request rescue nil
68
+ @request.session['geo_redirect.country'] = country
69
+ self.log "GeoIP match: country code #{country}"
70
+
71
+ unless country.nil?
72
+ host = host_by_country(country) # desired host
73
+ self.log "GeoIP host match: #{host}"
74
+ remember_host(host)
75
+
76
+ redirect_request(host)
77
+ else
78
+ @app.call(@request.env)
79
+ end
80
+ end
81
+
82
+ def redirect_request(host=nil, same_host=false)
83
+ hostname = hostname_by_host(host)
84
+
85
+ if should_redirect?(hostname, same_host)
86
+ url = redirect_url(hostname)
87
+
88
+ self.log "Redirecting to #{url}"
89
+ [301,
90
+ {'Location' => url.to_s, 'Content-Type' => 'text/plain'},
91
+ ['Moved Permanently\n']]
92
+ else
93
+ @app.call(@request.env)
94
+ end
95
+ end
96
+
97
+ def host_by_country(country)
98
+ hosts = @config.select { |k, v| Array(v[:countries]).include?(country) }
99
+ hosts.keys.first || :default
100
+ end
101
+
102
+ def host_by_hostname(hostname)
103
+ hosts = @config.select { |k, v| v[:host] == hostname }
104
+ hosts.keys.first || :default
105
+ end
106
+
107
+ def hostname_by_host(host)
108
+ host.is_a?(Symbol) ? @config[host][:host] : host
109
+ end
110
+
111
+ def remember_host(host)
112
+ self.log "Remembering: #{host}"
113
+ @request.session['geo_redirect'] = host
114
+ end
115
+
116
+ def forget_host(host)
117
+ self.log "Forgetting: #{host}"
118
+ remember_host(nil)
119
+ end
120
+
121
+ protected
122
+ def log(message, level=:debug)
123
+ @logger.send(level, "[GeoRedirect] #{message}") unless @logger.nil?
124
+ end
125
+
126
+ def init_logger(path)
127
+ Logger.new(path)
128
+ rescue Errno::EINVAL, Errno::EACCES
129
+ nil
130
+ end
131
+
132
+ def init_db(path)
133
+ GeoIP.new(path)
134
+ rescue Errno::EINVAL, Errno::ENOENT
135
+ message = <<-ERROR
136
+ Could not load GeoIP database file.
137
+ Please make sure you have a valid one and add its name to the GeoRedirect middleware.
138
+ Alternatively, use `rake georedirect:fetch_db` to fetch it to the default location (under db/).
139
+ ERROR
140
+ self.log(message, :error)
141
+ end
142
+
143
+ def init_config(path)
144
+ YAML.load_file(path) || raise(Errno::EINVAL)
145
+ rescue Errno::EINVAL, Errno::ENOENT, Psych::SyntaxError, SyntaxError
146
+ message = <<-ERROR
147
+ Could not load GeoRedirect config YML file.
148
+ Please make sure you have a valid YML file and pass its name when adding the GeoRedirect middlware.
149
+ ERROR
150
+ self.log(message, :error)
151
+ end
152
+
153
+ def request_ip
154
+ ip_address = @request.env['HTTP_X_FORWARDED_FOR'] || @request.env['REMOTE_ADDR']
155
+ # take only the first given ip
156
+ ip_address.split(',').first.strip
157
+ end
158
+
159
+
160
+ def country_from_request
161
+ ip = request_ip
162
+ self.log "Handling GeoIP lookup: IP #{ip}"
163
+
164
+ res = @db.country(ip)
165
+ code = res[:country_code]
166
+
167
+ res[:country_code2] unless code.nil? || code.zero?
168
+ end
169
+
170
+ def redirect_url(hostname)
171
+ url = URI.parse(@request.url)
172
+ url.port = nil
173
+ url.host = hostname if hostname
174
+
175
+ # Remove force flag from GET arguments
176
+ query_hash = Rack::Utils.parse_query(url.query).tap{ |u|
177
+ u.delete('redirect')
178
+ }
179
+
180
+ # Copy query
181
+ url.query = URI.encode_www_form(query_hash)
182
+ url.query = nil if url.query.empty?
183
+
184
+ url
185
+ end
186
+
187
+ def should_redirect?(hostname, same_host)
188
+ unless hostname.nil? || same_host
189
+ hostname_ends_with = %r{#{hostname.gsub(".", "\.")}$}
190
+ (@request.host =~ hostname_ends_with).nil?
191
+ else
192
+ true
193
+ end
194
+ end
195
+
196
+ end
197
+ end
@@ -4,7 +4,7 @@ require 'rails'
4
4
  module GeoRedirect
5
5
  class Railtie < Rails::Railtie
6
6
  rake_tasks do
7
- require 'tasks/geo_redirect.rb'
7
+ load 'tasks/geo_redirect.rake'
8
8
  end
9
9
  end
10
10
  end
@@ -1,3 +1,3 @@
1
1
  module GeoRedirect
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3"
3
3
  end
data/lib/geo_redirect.rb CHANGED
@@ -1,171 +1,13 @@
1
- require 'yaml'
2
- require 'geoip'
1
+ require 'geo_redirect/middleware'
3
2
  require 'geo_redirect/version'
4
3
 
5
4
  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
- @logfile = options[:logfile] || nil
16
-
17
- @app = app
18
-
19
- # Load GeoIP database
20
- begin
21
- @db = GeoIP.new(options[:db])
22
- rescue Errno::EINVAL, Errno::ENOENT => e
23
- puts "Could not load GeoIP database file."
24
- puts "Please make sure you have a valid one and"
25
- puts "add its name to the GeoRedirect middleware."
26
- puts "Alternatively, use `rake georedirect:fetch_db`"
27
- puts "to fetch it to the default location (under db/)."
28
- raise e
29
- end
30
-
31
- # Load config object
32
- begin
33
- @config = YAML.load_file('config/geo_redirect.yml')
34
- raise Errno::EINVAL unless @config
35
- rescue Errno::EINVAL, Errno::ENOENT => e
36
- puts "Could not load GeoRedirect config YML file."
37
- puts "Please make sure you have a valid YML file"
38
- puts "and pass its name when adding the"
39
- puts "GeoRedirect middlware."
40
- raise e
41
- end
42
-
43
- self.log "Initialized middleware"
44
- end
45
-
46
- def call(env)
47
- @request = Rack::Request.new(env)
48
-
49
- if force_redirect?
50
- handle_force
51
-
52
- elsif session_exists?
53
- handle_session
54
-
55
- else
56
- handle_geoip
57
- end
58
- end
59
-
60
- def session_exists?
61
- host = @request.session['geo_redirect']
62
- if host.present? && @config[host].nil? # Invalid var, remove it
63
- self.log "Invalid session var, forgetting"
64
- forget_host
65
- host = nil
66
- end
67
-
68
- host.present?
69
- end
70
-
71
- def handle_session
72
- host = @request.session['geo_redirect']
73
- self.log "Handling session var: #{host}"
74
- redirect_request(host)
75
- end
76
-
77
- def force_redirect?
78
- url = URI.parse(@request.url)
79
- Rack::Utils.parse_query(url.query).key? 'redirect'
80
- end
5
+ DEFAULT_DB_PATH = 'db/GeoIP.dat'
6
+ DEFAULT_CONFIG_PATH = 'config/geo_redirect.yml'
81
7
 
82
- def handle_force
83
- url = URI.parse(@request.url)
84
- host = host_by_hostname(url.host)
85
- self.log "Handling force flag: #{host}"
86
- remember_host(host)
87
- redirect_request(url.host, true)
88
- end
89
-
90
- def handle_geoip
91
- # Fetch country code
92
- begin
93
- ip_address = @request.env['HTTP_X_FORWARDED_FOR'] || @request.env['REMOTE_ADDR']
94
- ip_address = ip_address.split(',').first.chop # take only the first given ip
95
- self.log "Handling GeoIP lookup: IP #{ip_address}"
96
- res = @db.country(ip_address)
97
- code = res.try(:country_code)
98
- country = res.try(:country_code2) unless code.nil? || code.zero?
99
- rescue
100
- country = nil
101
- end
102
-
103
- self.log "GeoIP match: country code #{country}"
104
-
105
- unless country.nil?
106
- host = host_by_country(country) # desired host
107
- self.log "GeoIP host match: #{host}"
108
- remember_host(host)
109
-
110
- redirect_request(host)
111
- else
112
- @app.call(@request.env)
113
- end
114
- end
115
-
116
- def redirect_request(host=nil, same_host=false)
117
- redirect = true
118
- unless host.nil?
119
- hostname = host.is_a?(Symbol) ? @config[host][:host] : host
120
- redirect = hostname.present?
121
- redirect &&= !@request.host.ends_with?(hostname) unless same_host
122
- end
123
-
124
- if redirect
125
- url = URI.parse(@request.url)
126
- url.port = nil
127
- url.host = hostname if host
128
- # Remove 'redirect' GET arg
129
- url.query = Rack::Utils.parse_query(url.query).tap{ |u|
130
- u.delete('redirect')
131
- }.to_param
132
- url.query = nil if url.query.empty?
133
-
134
- self.log "Redirecting to #{url}"
135
- [301,
136
- {'Location' => url.to_s, 'Content-Type' => 'text/plain'},
137
- ['Moved Permanently\n']]
138
- else
139
- @app.call(@request.env)
140
- end
141
- end
142
-
143
- def host_by_country(country)
144
- hosts = @config.select { |k, v| Array(v[:countries]).include?(country) }
145
- hosts.keys.first || :default
146
- end
147
-
148
- def host_by_hostname(hostname)
149
- hosts = @config.select { |k, v| v[:host] == hostname }
150
- hosts.keys.first || :default
151
- end
152
-
153
- def remember_host(host)
154
- self.log "Remembering: #{host}"
155
- @request.session['geo_redirect'] = host
156
- end
157
-
158
- def forget_host
159
- self.log "Forgetting: #{host}"
160
- remember_host(nil)
161
- end
162
-
163
- protected
164
- def log(message)
165
- unless @logfile.nil?
166
- @logger ||= Logger.new(@logfile)
167
- @logger.debug("[GeoRedirect] #{message}")
168
- end
169
- end
8
+ # Load rake tasks
9
+ if defined? Rails
10
+ require 'geo_redirect/railtie'
170
11
  end
12
+
171
13
  end
@@ -1,11 +1,14 @@
1
- require 'zlib'
1
+ require 'geo_redirect'
2
2
  require 'open-uri'
3
+ require 'zlib'
3
4
 
4
- namespace :georedirect do
5
+ namespace :geo_redirect do
5
6
  DB_URI = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz'
6
7
 
7
8
  desc "Fetches an updated copy of the GeoIP countries DB from MaxMind"
8
- task :fetch_db do
9
+ task :fetch_db, :db_path do |t, args|
10
+ args.with_defaults(:db_path => GeoRedirect::DEFAULT_DB_PATH)
11
+
9
12
  # Fetches DB copy and gunzips it
10
13
  # Thx http://stackoverflow.com/a/2014317/107085
11
14
  source = open(DB_URI)
@@ -13,7 +16,6 @@ namespace :georedirect do
13
16
  result = gz.read
14
17
 
15
18
  # Write to file
16
- filename = Rails.root.join('db', 'GeoIP.dat')
17
- File.open(filename, 'w') { |f| f.write(result) }
19
+ File.open(args[:db_path], 'w') { |f| f.write(result) }
18
20
  end
19
21
  end
Binary file
@@ -0,0 +1,7 @@
1
+ :::T:HIS
2
+ is:
3
+ not :
4
+ really : @$#%& &
5
+ : a
6
+ : v a l i d
7
+ YAML file!!!
@@ -0,0 +1,12 @@
1
+ :us:
2
+ :host: 'biz.waze.com'
3
+ :countries: ['US', 'CA']
4
+
5
+ :il:
6
+ :host: 'biz.waze.co.il'
7
+ :countries: ['IL']
8
+
9
+ :world: &default
10
+ :host: 'biz.world.waze.com'
11
+
12
+ :default: *default
@@ -0,0 +1,320 @@
1
+ require "spec_helper"
2
+ require "tempfile"
3
+ require "logger"
4
+
5
+ describe "geo_redirect" do
6
+ include GeoRedirect::Support
7
+ include Rack::Test::Methods
8
+
9
+ def session
10
+ last_request.env['rack.session']
11
+ end
12
+
13
+ def url_scheme
14
+ last_request.env['rack.url_scheme']
15
+ end
16
+
17
+ before :each do
18
+ @config = YAML.load_file(fixture_path("config.yml"))
19
+ end
20
+
21
+ describe "#load_config" do
22
+ it "reads a config file" do
23
+ mock_app
24
+
25
+ @app.config.should_not be_nil
26
+ @app.config.should eq(@config)
27
+ end
28
+
29
+ it "errors on not-found config file" do
30
+ mock_app :config => nonexisting_file_path
31
+ log_should_include("ERROR")
32
+ log_should_include("Could not load GeoRedirect config YML file")
33
+ end
34
+
35
+ it "errors on a mal-formatted config file" do
36
+ mock_app :config => fixture_path("config.bad.yml")
37
+ log_should_include("ERROR")
38
+ log_should_include("Could not load GeoRedirect config YML file")
39
+ end
40
+ end
41
+
42
+ describe "#load_db" do
43
+ it "reads a db file" do
44
+ mock_app
45
+
46
+ @app.db.should_not be_nil
47
+ @app.db.should be_a_kind_of GeoIP
48
+ end
49
+
50
+ it "errors on not-found db file" do
51
+ mock_app :db => nonexisting_file_path
52
+ log_should_include("ERROR")
53
+ log_should_include("Could not load GeoIP database file.")
54
+ end
55
+
56
+ # this example is disabled, as it seems that
57
+ # GeoIP does not let me know if a db file is proper.
58
+ =begin
59
+ it "errors on mal-formatted db file" do
60
+ pending "GeoIP does not raise on bad files"
61
+ mock_app :db => fixture_path("config.yml")
62
+ log_should_include("ERROR")
63
+ log_should_include("Could not load GeoIP database file.")
64
+ end
65
+ =end
66
+ end
67
+
68
+ describe "#log" do
69
+ describe "with valid logfile path" do
70
+ before :each do
71
+ mock_app
72
+ end
73
+
74
+ it "initiates a log file" do
75
+ @app.instance_variable_get(:"@logger").should be_kind_of Logger
76
+ end
77
+
78
+ it "prints to log file" do
79
+ message = "Testing GeoRedirect logger"
80
+ @app.send(:log, [message])
81
+ log_should_include(message)
82
+ end
83
+ end
84
+
85
+ it "ignores invalid logfile path" do
86
+ mock_app :logfile => '/no_such_file'
87
+ @app.instance_variable_get(:"@logger").should be_nil
88
+ end
89
+ end
90
+
91
+ describe "#host_by_country" do
92
+ before :each do
93
+ mock_app
94
+ end
95
+
96
+ it "fetches host by country" do
97
+ @app.host_by_country("US").should eq(:us)
98
+ @app.host_by_country("IL").should eq(:il)
99
+ end
100
+
101
+ it "falls back to default" do
102
+ @app.host_by_country(:foo).should eq(:default)
103
+ end
104
+ end
105
+
106
+ describe "host_by_hostname" do
107
+ before :each do
108
+ mock_app
109
+ end
110
+
111
+ it "fetches host by hostname" do
112
+ @app.host_by_hostname("biz.waze.com").should eq(:us)
113
+ @app.host_by_hostname("biz.waze.co.il").should eq(:il)
114
+ @app.host_by_hostname("biz.world.waze.com").should eq(:world)
115
+ end
116
+
117
+ it "falls back to default" do
118
+ @app.host_by_hostname("foo").should eq(:default)
119
+ end
120
+ end
121
+
122
+ describe "redirect logic" do
123
+ before :each do
124
+ mock_app
125
+ end
126
+
127
+ def mock_request_from(code, options={})
128
+ ip = "5.5.5.5"
129
+
130
+ if code.nil?
131
+ country = nil
132
+ else
133
+ country = GeoIP::Country.stub({ :country_code2 => code,
134
+ :country_code => 5 })
135
+ end
136
+ @app.db.stub(:country).with(ip).and_return(country)
137
+
138
+ env = { "REMOTE_ADDR" => ip, "HTTP_HOST" => "biz.waze.co.il" }
139
+
140
+ if options[:session]
141
+ env['rack.session'] ||= {}
142
+ env['rack.session']['geo_redirect'] = options[:session]
143
+ env['rack.session']['geo_redirect.country'] = code
144
+ end
145
+
146
+ args = {}
147
+ if options[:force]
148
+ args[:redirect] = 1
149
+ end
150
+
151
+ get "/", args, env
152
+ end
153
+
154
+ def should_redirect_to(host)
155
+ last_response.body.should include("Moved Permanently")
156
+ last_response.status.should eq(301)
157
+ last_response.headers.should have_key("Location")
158
+ url = "#{url_scheme}://#{@config[host][:host]}"
159
+ last_response.headers["Location"].should start_with(url)
160
+ end
161
+
162
+ def should_not_redirect
163
+ last_response.body.should include("Hello world!")
164
+ last_response.should be_ok
165
+ end
166
+
167
+ def should_remember(host)
168
+ session['geo_redirect'].should eq(host)
169
+ end
170
+
171
+ def should_remember_country(country)
172
+ session['geo_redirect.country'].should eq(country)
173
+ end
174
+
175
+ describe "without session memory" do
176
+ describe "for a foreign source" do
177
+ before :each do
178
+ mock_request_from "US"
179
+ end
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
192
+ end
193
+
194
+ describe "for a local source" do
195
+ before :each do
196
+ mock_request_from "IL"
197
+ end
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
210
+ end
211
+
212
+ describe "for an unknown source" do
213
+ before :each do
214
+ mock_request_from "SOMEWHERE OVER THE RAINBOW"
215
+ end
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
228
+ end
229
+ end
230
+
231
+ describe "with valid session memory" do
232
+ before :each do
233
+ mock_request_from "US", :session => :default
234
+ end
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
247
+ end
248
+
249
+ describe "with invalid session memory" do
250
+ before :each do
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
261
+
262
+ it "stores decision in session" do
263
+ should_remember :us
264
+ end
265
+
266
+ it "stores discovered country in session" do
267
+ should_remember_country "US"
268
+ end
269
+ end
270
+
271
+ describe "with forced redirect flag" do
272
+ before :each do
273
+ mock_request_from "US", :force => true
274
+ end
275
+
276
+ it "rewrites the flag out" do
277
+ should_redirect_to :il
278
+ last_response.headers["Location"].should_not include("redirect=1")
279
+ end
280
+
281
+ it "stores decision in session" do
282
+ should_remember :il
283
+ end
284
+
285
+ it "does not store discovered country in session" do
286
+ should_remember_country nil
287
+ end
288
+ end
289
+
290
+ describe "with no recognizable IP" do
291
+ before :each do
292
+ mock_request_from nil
293
+ end
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
306
+ end
307
+
308
+ end
309
+ end
310
+
311
+ describe "geo_redirect:fetch_db" do
312
+ include_context "rake"
313
+
314
+ it "downloads a GeoIP db to a location" do
315
+ @dbfile = Tempfile.new("db")
316
+ subject.invoke(@dbfile.path)
317
+ @dbfile.size.should_not be_nil
318
+ @dbfile.size.should be > 0
319
+ end
320
+ end
@@ -0,0 +1,22 @@
1
+ require "simplecov"
2
+ SimpleCov.start
3
+
4
+ require "rspec"
5
+ require "rack"
6
+ require "rack/test"
7
+
8
+ current_dir = File.dirname(__FILE__)
9
+ $LOAD_PATH.unshift(File.join(current_dir, '..', 'lib'))
10
+ $LOAD_PATH.unshift(current_dir)
11
+ require "geo_redirect"
12
+
13
+ Dir[File.join(current_dir, "support/**/*.rb")].each { |f| require f }
14
+
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+ RSpec.configure do |config|
17
+ config.treat_symbols_as_metadata_keys_with_true_values = true
18
+ config.run_all_when_everything_filtered = true
19
+ config.filter_run :focus
20
+
21
+ config.order = "random"
22
+ end
@@ -0,0 +1,41 @@
1
+ module GeoRedirect
2
+ module Support
3
+ def fixture_path(file)
4
+ "spec/fixtures/#{file}"
5
+ end
6
+
7
+ def nonexisting_file_path
8
+ "/no_such_file"
9
+ end
10
+
11
+ def app
12
+ Rack::Lint.new(@app)
13
+ end
14
+
15
+ def mock_app(options = {})
16
+ # Simple HTTP server that always returns 'Hello world!'
17
+ main_app = lambda { |env|
18
+ Rack::Request.new(env)
19
+ headers = {"Content-Type" => "text/html"}
20
+ [200, headers, ["Hello world!"]]
21
+ }
22
+
23
+ @logfile = Tempfile.new("log")
24
+ options = { :config => fixture_path("config.yml"),
25
+ :db => fixture_path("GeoIP.dat"),
26
+ :logfile => @logfile.path
27
+ }.merge(options)
28
+
29
+ builder = Rack::Builder.new
30
+ builder.use GeoRedirect::Middleware, options
31
+ builder.run main_app
32
+ @app = builder.to_app
33
+ end
34
+
35
+ def log_should_include(message)
36
+ @logfile.rewind
37
+ @logfile.read.should include(message)
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,18 @@
1
+ # thanks http://robots.thoughtbot.com/post/11957424161/test-rake-tasks-like-a-boss
2
+
3
+ require "rake"
4
+
5
+ shared_context "rake" do
6
+ let(:rake) { Rake::Application.new }
7
+ let(:task_name) { self.class.top_level_description }
8
+ let(:task_path) { "lib/tasks/#{task_name.split(":").first}" }
9
+ subject { rake[task_name] }
10
+
11
+ before do
12
+ Rake.application = rake
13
+ Rake.application.rake_require(task_path,
14
+ [File.join(File.dirname(__FILE__), "..", "..")])
15
+
16
+ Rake::Task.define_task(:environment)
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geo_redirect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: '0.3'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-07 00:00:00.000000000 Z
12
+ date: 2013-07-18 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
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'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: geoip
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -27,6 +43,86 @@ dependencies:
27
43
  - - ! '>='
28
44
  - !ruby/object:Gem::Version
29
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.13.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.13.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: rack
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.5.2
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.5.2
78
+ - !ruby/object:Gem::Dependency
79
+ name: rack-test
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.6.2
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.6.2
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
110
+ - !ruby/object:Gem::Dependency
111
+ name: simplecov
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.7.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.7.1
30
126
  description: Geo-location based redirector
31
127
  email:
32
128
  - sagie@waze.com
@@ -35,15 +131,25 @@ extensions: []
35
131
  extra_rdoc_files: []
36
132
  files:
37
133
  - .gitignore
134
+ - .rspec
135
+ - .travis.yml
38
136
  - Gemfile
39
137
  - LICENSE.txt
40
138
  - README.md
41
139
  - Rakefile
42
140
  - geo_redirect.gemspec
43
141
  - lib/geo_redirect.rb
142
+ - lib/geo_redirect/middleware.rb
44
143
  - lib/geo_redirect/railtie.rb
45
144
  - lib/geo_redirect/version.rb
46
- - lib/tasks/geo_redirect.rb
145
+ - lib/tasks/geo_redirect.rake
146
+ - spec/fixtures/GeoIP.dat
147
+ - spec/fixtures/config.bad.yml
148
+ - spec/fixtures/config.yml
149
+ - spec/geo_redirect_spec.rb
150
+ - spec/spec_helper.rb
151
+ - spec/support/geo_redirect.rb
152
+ - spec/support/rake.rb
47
153
  homepage: ''
48
154
  licenses: []
49
155
  post_install_message:
@@ -56,16 +162,29 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
162
  - - ! '>='
57
163
  - !ruby/object:Gem::Version
58
164
  version: '0'
165
+ segments:
166
+ - 0
167
+ hash: 3031125964627560618
59
168
  required_rubygems_version: !ruby/object:Gem::Requirement
60
169
  none: false
61
170
  requirements:
62
171
  - - ! '>='
63
172
  - !ruby/object:Gem::Version
64
173
  version: '0'
174
+ segments:
175
+ - 0
176
+ hash: 3031125964627560618
65
177
  requirements: []
66
178
  rubyforge_project:
67
179
  rubygems_version: 1.8.24
68
180
  signing_key:
69
181
  specification_version: 3
70
182
  summary: Rack middleware to redirect clients to hostnames based on geo-location
71
- test_files: []
183
+ test_files:
184
+ - spec/fixtures/GeoIP.dat
185
+ - spec/fixtures/config.bad.yml
186
+ - spec/fixtures/config.yml
187
+ - spec/geo_redirect_spec.rb
188
+ - spec/spec_helper.rb
189
+ - spec/support/geo_redirect.rb
190
+ - spec/support/rake.rb