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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +83 -0
- data/Rakefile +1 -0
- data/hostrich.gemspec +25 -0
- data/lib/hostrich/version.rb +3 -0
- data/lib/hostrich.rb +67 -0
- data/spec/hostrich_spec.rb +69 -0
- data/spec/spec_helper.rb +13 -0
- metadata +113 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|