hostrich 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 637748f6b1009f5874056cee3fb09f09e9a3acf8
4
+ data.tar.gz: d68d76d257965d8ff2da09a76124ab8561c194a2
5
+ SHA512:
6
+ metadata.gz: 2b61b34927dbfad8b7c8c01d1c7c290a7bf3250dfb24be333ccfba741bf2a679f3cc11596f00560d1366a603d13ae05c482ea30efb62b5d7fa7e350cef939dfe
7
+ data.tar.gz: 7204c9c21f1b6cef6ec948833a20834f1825fb5057157aeae305bf3520dd487aed345600ee8772a75a79bf065021d8a5d8a619008bae37d632249768a34facd7
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Rafaël Blais Masson
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,83 @@
1
+ # Hostrich
2
+
3
+ Hostrich is a Rack middleware that eases multi-domain web app development.
4
+
5
+ ## Usage
6
+
7
+ Add the middleware at the top of your development stack, passing it the domain(s) your app is using:
8
+
9
+ ```ruby
10
+ # Gemfile
11
+ gem 'hostrich', group: :development
12
+ ```
13
+
14
+ ```ruby
15
+ # config.ru
16
+
17
+ # if using Rack::Deflater, put it here
18
+ if ENV['RACK_ENV'] == 'development'
19
+ use Hostrich, ['somedomain.com', 'otherdoma.in']
20
+ end
21
+ # ... any other middleware ...
22
+
23
+ run Your::RackApp
24
+ ```
25
+
26
+ ## Database-dynamic hosts
27
+
28
+ If your app serves pages on hosts that depend on models, say a `Website` model with a `custom_domain` column, you can append hosts to the `Hostrich.hosts` array after your app intialization:
29
+
30
+ ```ruby
31
+ # config/environments/development.rb
32
+
33
+ config.after_initialize do
34
+ Hostrich.hosts += Website.pluck(:custom_domain).compact
35
+ end
36
+ ```
37
+
38
+ ## Rationale
39
+
40
+ Hostrich tricks your development environment into thinking it is serving your application from your production host (`example.com`) instead of your usual development host (`example.dev`).
41
+
42
+ Thus, your application doesn’t have to know about any dev-prod hosts mapping. This makes your code simpler and less prone to errors.
43
+
44
+ To make this possible, you must access your local app at `http://example.com.dev` or the like, where a suffix is added to the full production domain. This way Hostrich can extract `.dev` from the host and append it everywhere `example.com` is output in your response bodies and headers.
45
+
46
+ [xip.io](http://xip.io) is Hostrich’s best friend. For complex multi-domain web apps like ours at [Medalist](http://medali.st), it’s a must. Our app responds to `medali.st`, `manage.medali.st`, `*.mli.st` and even `anydomain.com` because some of our users have custom domains. xip.io allows us to access our local app at:
47
+
48
+ - **medali.st**.127.0.0.1.xip.io
49
+ - **manage.medali.st**.127.0.0.1.xip.io
50
+ - **mikaelkingsbury.mli.st**.127.0.0.1.xip.io
51
+ - **mikaelkingsbury.ca**.127.0.0.1.xip.io
52
+
53
+ And our application code only knows about production hosts:
54
+
55
+ ```ruby
56
+ # config/routes.rb
57
+
58
+ Medalist::Application.routes.draw do
59
+ # http://medali.st/
60
+ namespace :public, host: 'medali.st' do
61
+ # ...
62
+ end
63
+
64
+ # http://manage.medali.st/
65
+ namespace :manage, host: 'manage.medali.st' do
66
+ # ...
67
+ end
68
+
69
+ # http://usersite.mli.st/
70
+ # http://usersite.com/
71
+ namespace :usersite do
72
+ # ...
73
+ end
74
+ end
75
+ ```
76
+
77
+ ## TODO
78
+
79
+ - This README isn’t that great.
80
+
81
+ ---
82
+
83
+ © 2014 [Rafaël Blais Masson](http://medali.st). Hostrich is released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/hostrich.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hostrich/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'hostrich'
8
+ spec.version = Hostrich::VERSION
9
+ spec.authors = ['Rafaël Blais Masson']
10
+ spec.email = ['rafbmasson@gmail.com']
11
+ spec.description = 'Hostrich is a Rack middleware that eases multi-domain web app development.'
12
+ spec.summary = 'Hostrich is a Rack middleware that eases multi-domain web app development.'
13
+ spec.homepage = 'http://github.com/rafBM/hostrich'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'rack'
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ end
@@ -0,0 +1,3 @@
1
+ class Hostrich
2
+ VERSION = '0.1.0'
3
+ end
data/lib/hostrich.rb ADDED
@@ -0,0 +1,67 @@
1
+ require 'hostrich/version'
2
+
3
+ require 'rack'
4
+
5
+ class Hostrich
6
+ @@hosts = []
7
+
8
+ def self.hosts
9
+ @@hosts
10
+ end
11
+
12
+ def self.hosts=(array)
13
+ @@hosts = array
14
+ end
15
+
16
+ def initialize(app, hosts = [])
17
+ @app = app
18
+ @@hosts += Array(hosts)
19
+ end
20
+
21
+ def call(env)
22
+ return @app.call(env) if @@hosts.empty?
23
+
24
+ # Extract suffix from current request host.
25
+ match = nil
26
+ @@hosts.detect { |host| match = env['HTTP_HOST'].match(/#{host}(\.[\.\w-]+)?/) }
27
+ return @app.call(env) if match.nil?
28
+
29
+ suffix = match[1]
30
+
31
+ # Fake request host.
32
+ # Eg. If request is made from http://example.com.dev or http://example.com.127.0.0.1.xip.io,
33
+ # the Rack app will see it just as a request to http://example.com.
34
+ env['HTTP_HOST'] = remove_suffix(env['HTTP_HOST'], suffix)
35
+ env['SERVER_NAME'] = remove_suffix(env['SERVER_NAME'], suffix)
36
+
37
+ # Get regular response from Rack app
38
+ status, headers, body = @app.call(env)
39
+ body.close if body.respond_to? :close
40
+
41
+ # Add current host suffix in all response bodies, so that occurences of http://example.com
42
+ # appear as http://example.com.dev or http://example.com.127.0.0.1.xip.io in the browser.
43
+ body = Array(body.each(&:to_s)).join
44
+ body = [add_suffix(body, suffix)]
45
+
46
+ # Do the same in response headers. This is important for cookies and redirects.
47
+ headers = Hash[headers.map { |k,v| [k, add_suffix(v, suffix)] }]
48
+
49
+ # Return hacked response
50
+ [status, headers, body]
51
+ end
52
+
53
+ private
54
+
55
+ def remove_suffix(input, suffix)
56
+ output = input.dup
57
+ @@hosts.each { |host| output.gsub! "#{host}#{suffix}", host }
58
+ output
59
+ end
60
+
61
+ def add_suffix(input, suffix)
62
+ output = input.dup
63
+ # Don’t add suffix when it’s already there. Prevents double-suffix redirects and stuff.
64
+ @@hosts.each { |host| output.gsub! /\b#{host}\b(?!#{suffix})/, "#{host}#{suffix}" }
65
+ output
66
+ end
67
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hostrich do
4
+ # Prevent spec mess-up after testing `Hostrich.hosts += ['...']`
5
+ before { Hostrich.hosts = [] }
6
+
7
+ let(:app) {
8
+ proc { |env|
9
+ case env['PATH_INFO']
10
+ when '/redirect'
11
+ [302, { 'Location' => 'http://foo.com/index.html' }, []]
12
+ when '/cookie'
13
+ [200, { 'Set-Cookie' => 'some_param=foo.com/index.html; Path=/; Domain=.foo.com' }, ['Cookie!']]
14
+ else
15
+ [200, {}, ['Welcome to foo.com or foo.com.dev, not foo.comzle, not ffoo.com and not bar.io']]
16
+ end
17
+ }
18
+ }
19
+ let(:request) { Rack::MockRequest.new(stack) }
20
+
21
+ shared_examples 'the whole deal' do
22
+ it 'adds host suffix in response body' do
23
+ response = request.get('/', 'HTTP_HOST' => 'foo.com.dev')
24
+ expect(response.body).to eq 'Welcome to foo.com.dev or foo.com.dev, not foo.comzle, not ffoo.com and not bar.io'
25
+ end
26
+
27
+ it 'adds host suffix in response headers' do
28
+ response = request.get('/redirect', 'HTTP_HOST' => 'foo.com.dev')
29
+ expect(response.headers['Location']).to eq 'http://foo.com.dev/index.html'
30
+
31
+ response = request.get('/cookie', 'HTTP_HOST' => 'foo.com.dev')
32
+ expect(response.headers['Set-Cookie']).to eq 'some_param=foo.com.dev/index.html; Path=/; Domain=.foo.com.dev'
33
+ end
34
+ end
35
+
36
+ context 'passing host as a string' do
37
+ let(:stack) { Hostrich.new(app, 'foo.com') }
38
+ it_behaves_like 'the whole deal'
39
+ end
40
+
41
+ context 'passing host as an array' do
42
+ let(:stack) { Hostrich.new(app, ['foo.com']) }
43
+ it_behaves_like 'the whole deal'
44
+ end
45
+
46
+ context 'passing multiple hosts as an array' do
47
+ let(:stack) { Hostrich.new(app, %w[foo.com foo.bar.io]) }
48
+ it_behaves_like 'the whole deal'
49
+
50
+ it 'adds host suffix for all hosts' do
51
+ response = request.get('/', 'HTTP_HOST' => 'foo.com.127.0.0.1.xip.io')
52
+ expect(response.body).to eq 'Welcome to foo.com.127.0.0.1.xip.io or foo.com.127.0.0.1.xip.io.dev, not foo.comzle, not ffoo.com and not bar.io'
53
+ end
54
+ end
55
+
56
+ context 'passing no host' do
57
+ let(:stack) { Hostrich.new(app) }
58
+
59
+ it 'does nothing' do
60
+ response = request.get('/', 'HTTP_HOST' => 'foo.com.dev')
61
+ expect(response.body).to eq 'Welcome to foo.com or foo.com.dev, not foo.comzle, not ffoo.com and not bar.io'
62
+ end
63
+
64
+ context 'adding hosts after initialization' do
65
+ before { Hostrich.hosts += ['foo.com'] }
66
+ it_behaves_like 'the whole deal'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,13 @@
1
+ require 'hostrich'
2
+
3
+ RSpec.configure do |config|
4
+ config.treat_symbols_as_metadata_keys_with_true_values = true
5
+ config.run_all_when_everything_filtered = true
6
+ config.filter_run :focus
7
+
8
+ # Run specs in random order to surface order dependencies. If you find an
9
+ # order dependency and want to debug it, you can fix the order by providing
10
+ # the seed, which is printed after each run.
11
+ # --seed 1234
12
+ config.order = 'random'
13
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hostrich
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rafaël Blais Masson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Hostrich is a Rack middleware that eases multi-domain web app development.
70
+ email:
71
+ - rafbmasson@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - hostrich.gemspec
83
+ - lib/hostrich.rb
84
+ - lib/hostrich/version.rb
85
+ - spec/hostrich_spec.rb
86
+ - spec/spec_helper.rb
87
+ homepage: http://github.com/rafBM/hostrich
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.2.0
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Hostrich is a Rack middleware that eases multi-domain web app development.
111
+ test_files:
112
+ - spec/hostrich_spec.rb
113
+ - spec/spec_helper.rb