Merge pull request #1528 from maxmeyer/feature/listener
Support Bind to address for middleman
This commit is contained in:
commit
d2f8dc9932
42 changed files with 2029 additions and 52 deletions
|
@ -14,6 +14,7 @@ AllCops:
|
|||
- 'middleman-core/fixtures/**/*'
|
||||
- 'middleman-core/features/**/*'
|
||||
- 'middleman-core/spec/**/*'
|
||||
DisplayCopNames: true
|
||||
LineLength:
|
||||
Enabled: false
|
||||
MethodLength:
|
||||
|
|
5
Gemfile
5
Gemfile
|
@ -9,7 +9,7 @@ gem 'pry', '~> 0.10', group: :development
|
|||
gem 'aruba', '~> 0.7.4'
|
||||
gem 'rspec', '~> 3.0'
|
||||
gem 'fivemat', '~> 1.3'
|
||||
gem 'cucumber', '~> 1.3'
|
||||
gem 'cucumber', '~> 2.0'
|
||||
|
||||
# Optional middleman dependencies, included for tests
|
||||
gem 'less', '2.3', require: false
|
||||
|
@ -20,6 +20,9 @@ gem 'sinatra', '>= 1.4', require: false
|
|||
gem 'redcarpet', '>= 3.1', require: false unless RUBY_ENGINE == 'jruby'
|
||||
gem 'asciidoctor', '~> 0.1', require: false
|
||||
|
||||
# Dns server to test preview server
|
||||
gem 'rubydns', '~> 1.0.1', require: false
|
||||
|
||||
# To test javascript
|
||||
gem 'poltergeist', '~> 1.6.0', require: false
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ Cucumber::Rake::Task.new do |t|
|
|||
exempt_tags << '--tags ~@encoding' unless Object.const_defined?(:Encoding)
|
||||
exempt_tags << '--tags ~@nowindows' if Gem.win_platform?
|
||||
exempt_tags << '--tags ~@travishatesme' if ENV['TRAVIS'] == 'true'
|
||||
t.cucumber_opts = "--color #{exempt_tags.join(' ')} --strict --format #{ENV['CUCUMBER_FORMAT'] || 'Fivemat'}"
|
||||
t.cucumber_opts = "--require features --color #{exempt_tags.join(' ')} --strict --format #{ENV['CUCUMBER_FORMAT'] || 'Fivemat'}"
|
||||
end
|
||||
|
||||
Cucumber::Rake::Task.new(:cucumber_wip) do |t|
|
||||
|
|
1
middleman-core/.rspec
Normal file
1
middleman-core/.rspec
Normal file
|
@ -0,0 +1 @@
|
|||
--color
|
2
middleman-core/cucumber.yml
Normal file
2
middleman-core/cucumber.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
default: --require features --tags ~@wip
|
||||
wip: --require features --tags @wip
|
532
middleman-core/features/cli/preview_server.feature
Normal file
532
middleman-core/features/cli/preview_server.feature
Normal file
|
@ -0,0 +1,532 @@
|
|||
Feature: Run the preview server
|
||||
|
||||
As a software developer
|
||||
I want to start the preview server
|
||||
In order to view my changes immediately in the browser
|
||||
|
||||
Background:
|
||||
Given a fixture app "preview-server-app"
|
||||
And the default aruba timeout is 30 seconds
|
||||
|
||||
Scenario: Start the server with defaults
|
||||
When I run `middleman server` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://
|
||||
"""
|
||||
|
||||
Scenario: Start the server with defaults in verbose mode
|
||||
When I run `middleman server --verbose` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to ":::4567", "0.0.0.0:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://
|
||||
"""
|
||||
|
||||
@ruby-2.1
|
||||
Scenario: Start the server with defaults in verbose mode, when a local mdns server resolves the local hostname
|
||||
Given I start a mdns server for the local hostname
|
||||
When I run `middleman server --verbose` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to ":::4567", "0.0.0.0:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address 127.0.0.1
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# <ip-address> <hostname.domain.org> <hostname>
|
||||
127.0.0.1 localhost.localdomain localhost
|
||||
"""
|
||||
When I run `middleman server --verbose --bind-address 127.0.0.1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address 127.0.0.1 configured via config.rb
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# <ip-address> <hostname.domain.org> <hostname>
|
||||
127.0.0.1 localhost.localdomain localhost
|
||||
"""
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
set :bind_address, '127.0.0.1'
|
||||
"""
|
||||
When I run `middleman server --verbose` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address 127.0.0.5
|
||||
|
||||
This will have no hostname attached because the hosts file, the DNS server
|
||||
and the MDNS-server do not know anything about 127.0.0.5
|
||||
|
||||
When I run `middleman server --verbose --bind-address 127.0.0.5` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.5:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://127.0.0.5:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://127.0.0.5:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address ::1
|
||||
Given a file named ".hosts" with:
|
||||
"""
|
||||
# <ip-address> <hostname.domain.org> <hostname>
|
||||
::1 localhost.localdomain localhost
|
||||
"""
|
||||
When I run `middleman server --verbose --bind-address ::1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "::1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://[::1]:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://[::1]:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address 0.0.0.0
|
||||
When I run `middleman server --verbose --bind-address 0.0.0.0` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "0.0.0.0:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address ::
|
||||
When I run `middleman server --verbose --bind-address ::` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to ":::4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "localhost"
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# <ip-address> <hostname.domain.org> <hostname>
|
||||
127.0.0.1 localhost.localdomain localhost
|
||||
"""
|
||||
When I run `middleman server --verbose --server-name localhost` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://localhost:4567", "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "localhost" configured via config.rb
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# <ip-address> <hostname.domain.org> <hostname>
|
||||
127.0.0.1 localhost.localdomain localhost
|
||||
"""
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
set :server_name, 'localhost'
|
||||
"""
|
||||
When I run `middleman server --verbose` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://localhost:4567", "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "localhost" and bind address "127.0.0.1"
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# <ip-address> <hostname.domain.org> <hostname>
|
||||
127.0.0.1 localhost.localdomain localhost
|
||||
"""
|
||||
When I run `middleman server --verbose --server-name localhost --bind-address 127.0.0.1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://localhost:4567", "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "127.0.0.1"
|
||||
When I run `middleman server --verbose --server-name 127.0.0.1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "::1"
|
||||
When I run `middleman server --verbose --server-name ::1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "::1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://[::1]:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://[::1]:4567/__middleman"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with https
|
||||
When I run `middleman server --verbose --https` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to ":::4567", "0.0.0.0:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "https://
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "https://
|
||||
"""
|
||||
|
||||
Scenario: Start the server with port 65432
|
||||
When I run `middleman server --verbose --port 65432` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to ":::65432", "0.0.0.0:65432"
|
||||
"""
|
||||
|
||||
Scenario: Start the server with port 65432 configured via config.rb
|
||||
Given a file named "config.rb" with:
|
||||
"""
|
||||
set :port, 65432
|
||||
"""
|
||||
When I run `middleman server --verbose` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to ":::65432", "0.0.0.0:65432"
|
||||
"""
|
||||
|
||||
Scenario: Start the server when port is blocked by other middleman instance
|
||||
Given `middleman server` is running in background
|
||||
When I run `middleman server --verbose` interactively
|
||||
And I stop all commands if the output of the last command contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman uses a different port
|
||||
"""
|
||||
|
||||
Scenario: Start the server with bind address 1.1.1.1
|
||||
|
||||
This should fail, because "1.1.1.1" is not an interface available on this computer.
|
||||
|
||||
Given a file named ".hosts" with:
|
||||
"""
|
||||
1.1.1.1 www.example.com www
|
||||
"""
|
||||
When I run `middleman server --verbose --bind-address 1.1.1.1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Running Middleman failed:
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
Bind address "1.1.1.1" is not available on your system
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name www.example.com and bind address 0.0.0.0
|
||||
|
||||
This should fail, because the user can just use `--server-name`. It does
|
||||
not make sense for `middleman` to only listen on `0.0.0.0` (IPv4 all
|
||||
interfaces), but not on `::` (IPv6 all interfaces). There are other tools
|
||||
like `iptables` (Linux-only) or better some `kernel`-configurations to make
|
||||
this possible.
|
||||
|
||||
When I run `middleman server --verbose --server-name www.example.com --bind-address 0.0.0.0` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Running Middleman failed:
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
Undefined combination of options "--server-name" and "--bind-address".
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "www.example.com" and bind address "127.0.0.1"
|
||||
|
||||
This should fail because the server name does not resolve to the ip address.
|
||||
|
||||
Given a file named ".hosts" with:
|
||||
"""
|
||||
1.1.1.1 www.example.com www
|
||||
"""
|
||||
When I run `middleman server --verbose --server-name www.example.com --bind-address 127.0.0.1` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Running Middleman failed:
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
Server name "www.example.com" does not resolve to bind address "127.0.0.1". Please fix that and try again.
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "garbage.example.com"
|
||||
When I run `middleman server --verbose --server-name garbage.example.com` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Running Middleman failed:
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
Server name "garbage.example.com" does not resolve to an ip address. Please fix that and try again.
|
||||
"""
|
||||
|
||||
Scenario: Start the server with server name "www.example.com" and the network name server is used to resolve the server name
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# empty
|
||||
"""
|
||||
And I start a mdns server with:
|
||||
"""
|
||||
# empty
|
||||
"""
|
||||
And I start a dns server with:
|
||||
"""
|
||||
www.example.com: 127.0.0.1
|
||||
"""
|
||||
When I run `middleman server --verbose --server-name www.example.com` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://www.example.com:4567", "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://www.example.com:4567/__middleman", "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
@ruby-2.1
|
||||
Scenario: Start the server with server name "host.local" and the link local name server is used to resolve the server name
|
||||
|
||||
To make the mdns resolver resolve a name, it needs to end with ".local".
|
||||
Otherwise the resolver returns [].
|
||||
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# empty
|
||||
"""
|
||||
And I start a mdns server with:
|
||||
"""
|
||||
host.local: 127.0.0.1
|
||||
"""
|
||||
When I run `middleman server --verbose --server-name host.local` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://host.local:4567", "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://host.local:4567/__middleman", "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
||||
|
||||
@ruby-2.1
|
||||
Scenario: Start the server with server name "host" and the link local name server is used to resolve the server name
|
||||
|
||||
To make the mdns resolver resolve a name, it needs to end with ".local". If
|
||||
a plain hostname is given `middleman` appends ".local" automatically.
|
||||
|
||||
Given I have a local hosts file with:
|
||||
"""
|
||||
# empty
|
||||
"""
|
||||
And I start a mdns server with:
|
||||
"""
|
||||
host.local: 127.0.0.1
|
||||
"""
|
||||
When I run `middleman server --verbose --server-name host` interactively
|
||||
And I stop middleman if the output contains:
|
||||
"""
|
||||
Inspect your site configuration
|
||||
"""
|
||||
Then the output should contain:
|
||||
"""
|
||||
The Middleman preview server is bind to "127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
View your site at "http://host.local:4567", "http://127.0.0.1:4567"
|
||||
"""
|
||||
And the output should contain:
|
||||
"""
|
||||
Inspect your site configuration at "http://host.local:4567/__middleman", "http://127.0.0.1:4567/__middleman"
|
||||
"""
|
33
middleman-core/fixtures/preview-server-app/bin/dns_server.rb
Executable file
33
middleman-core/fixtures/preview-server-app/bin/dns_server.rb
Executable file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubydns'
|
||||
require 'psych'
|
||||
|
||||
db_file = ARGV[0]
|
||||
port = ARGV[1] || 5300
|
||||
|
||||
db = if File.file? db_file
|
||||
$stderr.puts 'Found dns db'
|
||||
Psych.load_file(db_file)
|
||||
else
|
||||
$stderr.puts 'Found no dns db. Use default db.'
|
||||
|
||||
{
|
||||
/www\.example\.org/ => '1.1.1.1'
|
||||
}
|
||||
end
|
||||
|
||||
interfaces = [
|
||||
[:udp, "127.0.0.1", port],
|
||||
[:tcp, "127.0.0.1", port]
|
||||
]
|
||||
|
||||
|
||||
# Start the RubyDNS server
|
||||
RubyDNS::run_server(:listen => interfaces) do
|
||||
db.each do |matcher, result|
|
||||
match(matcher, Resolv::DNS::Resource::IN::A) do |transaction|
|
||||
transaction.respond!(result)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
page "/fake.html", :proxy => "/real.html", :layout => false
|
||||
|
||||
ignore "/should_be_ignored.html"
|
||||
page "/should_be_ignored2.html", :ignore => true
|
||||
page "/target_ignore.html", :proxy => "/should_be_ignored3.html", :ignore => true
|
||||
|
||||
%w(one two).each do |num|
|
||||
page "/fake/#{num}.html", :proxy => "/real/index.html" do
|
||||
@num = num
|
||||
end
|
||||
end
|
11
middleman-core/fixtures/preview-server-app/config.rb
Normal file
11
middleman-core/fixtures/preview-server-app/config.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
page "/fake.html", :proxy => "/real.html", :layout => false
|
||||
|
||||
ignore "/should_be_ignored.html"
|
||||
page "/should_be_ignored2.html", :ignore => true
|
||||
page "/target_ignore.html", :proxy => "/should_be_ignored3.html", :ignore => true
|
||||
|
||||
%w(one two).each do |num|
|
||||
page "/fake/#{num}.html", :proxy => "/real/index.html" do
|
||||
@num = num
|
||||
end
|
||||
end
|
1
middleman-core/fixtures/preview-server-app/source/index.html.erb
Executable file
1
middleman-core/fixtures/preview-server-app/source/index.html.erb
Executable file
|
@ -0,0 +1 @@
|
|||
<h1>Welcome</h1>
|
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>My Sample Site</title>
|
||||
<!-- Comment in layout -->
|
||||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
8
middleman-core/fixtures/preview-server-app/source/layouts/custom.erb
Executable file
8
middleman-core/fixtures/preview-server-app/source/layouts/custom.erb
Executable file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Custom Layout</title>
|
||||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
I am real
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
layout: false
|
||||
---
|
||||
|
||||
I am real: <%= @num %>
|
|
@ -0,0 +1 @@
|
|||
<h1>Ignore me!</h1>
|
|
@ -0,0 +1 @@
|
|||
<h1>Ignore me! 2</h1>
|
|
@ -0,0 +1 @@
|
|||
<h1>Ignore me! 3</h1>
|
1
middleman-core/fixtures/preview-server-app/source/static.html
Executable file
1
middleman-core/fixtures/preview-server-app/source/static.html
Executable file
|
@ -0,0 +1 @@
|
|||
Static, no code!
|
|
@ -71,7 +71,11 @@ module Middleman
|
|||
|
||||
# Which server name should be used
|
||||
# @return [NilClass, String]
|
||||
config.define_setting :host, nil, 'The preview host name'
|
||||
config.define_setting :server_name, nil, 'The server name of preview server'
|
||||
|
||||
# Which bind address the preview server should use
|
||||
# @return [NilClass, String]
|
||||
config.define_setting :bind_address, nil, 'The bind address of the preview server'
|
||||
|
||||
# Whether to serve the preview server over HTTPS.
|
||||
# @return [Boolean]
|
||||
|
|
|
@ -14,9 +14,12 @@ module Middleman::Cli
|
|||
method_option :port,
|
||||
aliases: '-p',
|
||||
desc: 'The port Middleman will listen on'
|
||||
method_option :host,
|
||||
aliases: '-h',
|
||||
desc: 'The host name Middleman will use'
|
||||
method_option :server_name,
|
||||
aliases: '-s',
|
||||
desc: 'The server name name Middleman will use'
|
||||
method_option :bind_address,
|
||||
aliases: '-b',
|
||||
desc: 'The bind address Middleman will listen on'
|
||||
method_option :https,
|
||||
type: :boolean,
|
||||
desc: 'Serve the preview server over SSL/TLS'
|
||||
|
@ -68,8 +71,9 @@ module Middleman::Cli
|
|||
|
||||
params = {
|
||||
port: options['port'],
|
||||
bind_address: options['bind_address'],
|
||||
https: options['https'],
|
||||
host: options['host'],
|
||||
server_name: options['server_name'],
|
||||
ssl_certificate: options['ssl_certificate'],
|
||||
ssl_private_key: options['ssl_private_key'],
|
||||
environment: options['environment'],
|
||||
|
|
73
middleman-core/lib/middleman-core/dns_resolver.rb
Normal file
73
middleman-core/lib/middleman-core/dns_resolver.rb
Normal file
|
@ -0,0 +1,73 @@
|
|||
require 'resolv'
|
||||
require 'middleman-core/dns_resolver/network_resolver'
|
||||
require 'middleman-core/dns_resolver/hosts_resolver'
|
||||
|
||||
module Middleman
|
||||
# This resolves IP address to names and vice versa
|
||||
class DnsResolver
|
||||
private
|
||||
|
||||
attr_reader :resolvers
|
||||
|
||||
public
|
||||
|
||||
# Create resolver
|
||||
#
|
||||
# First the local resolver is used. If environment variable HOSTSRC is
|
||||
# given this file is used for local name lookup.
|
||||
#
|
||||
# @param [#getnames, #getaddresses] network_resolver
|
||||
# The resolver which uses a network name server to resolve ip addresses
|
||||
# and names.
|
||||
#
|
||||
# @param [#getnames, #getaddresses] local_resolver
|
||||
# The resolver uses /etc/hosts on POSIX-systems and
|
||||
# C:\Windows\System32\drivers\etc\hosts on Windows-operating systems to
|
||||
# resolve ip addresses and names.
|
||||
#
|
||||
# First the local resolver is queried. If this raises an error or returns
|
||||
# nil or [] the network resolver is queried.
|
||||
def initialize(opts={})
|
||||
@resolvers = []
|
||||
@resolvers << opts.fetch(:hosts_resolver, HostsResolver.new)
|
||||
|
||||
if RUBY_VERSION >= '2.1'
|
||||
require 'middleman-core/dns_resolver/local_link_resolver'
|
||||
@resolvers << opts.fetch(:local_link_resolver, LocalLinkResolver.new)
|
||||
end
|
||||
|
||||
@resolvers << opts.fetch(:network_resolver, NetworkResolver.new)
|
||||
end
|
||||
|
||||
# Get names for given ip
|
||||
#
|
||||
# @param [String] ip
|
||||
# The ip which should be resolved.
|
||||
def names_for(ip)
|
||||
resolvers.each do |r|
|
||||
names = r.getnames(ip)
|
||||
|
||||
return names unless names.nil? || names.empty?
|
||||
end
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
# Get ips for given name
|
||||
#
|
||||
# First the local resolver is used. On POSIX-systems /etc/hosts is used. On
|
||||
# Windows C:\Windows\System32\drivers\etc\hosts is used.
|
||||
#
|
||||
# @param [String] name
|
||||
# The name which should be resolved.
|
||||
def ips_for(name)
|
||||
resolvers.each do |r|
|
||||
ips = r.getaddresses(name)
|
||||
|
||||
return ips unless ips.nil? || ips.empty?
|
||||
end
|
||||
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
module Middleman
|
||||
class DnsResolver
|
||||
# Use network name server to resolve ips and names
|
||||
class BasicNetworkResolver
|
||||
private
|
||||
|
||||
attr_reader :resolver, :timeouts
|
||||
|
||||
public
|
||||
|
||||
def initialize(opts={})
|
||||
@timeouts = opts.fetch(:timeouts, 2)
|
||||
end
|
||||
|
||||
# Get names for ip
|
||||
#
|
||||
# @param [#to_s] ip
|
||||
# The ip to resolve into names
|
||||
#
|
||||
# @return [Array]
|
||||
# Array of Names
|
||||
def getnames(ip)
|
||||
resolver.getnames(ip.to_s).map(&:to_s)
|
||||
rescue Resolv::ResolvError, Errno::EADDRNOTAVAIL
|
||||
[]
|
||||
end
|
||||
|
||||
# Get ips for name
|
||||
#
|
||||
# @param [#to_s] name
|
||||
# The name to resolve into ips
|
||||
#
|
||||
# @return [Array]
|
||||
# Array of ipaddresses
|
||||
def getaddresses(name)
|
||||
resolver.getaddresses(name.to_s).map(&:to_s)
|
||||
rescue Resolv::ResolvError, Errno::EADDRNOTAVAIL
|
||||
[]
|
||||
end
|
||||
|
||||
# Set timeout for lookup
|
||||
#
|
||||
# @param [Integer] value
|
||||
# The timeout value
|
||||
def timeouts=(timeouts)
|
||||
return if RUBY_VERSION < '2'
|
||||
|
||||
resolver.timeouts = timeouts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
module Middleman
|
||||
class DnsResolver
|
||||
# Use network name server to resolve ips and names
|
||||
class HostsResolver
|
||||
private
|
||||
|
||||
attr_reader :resolver
|
||||
|
||||
public
|
||||
|
||||
def initialize(opts={})
|
||||
# using the splat operator works around a non-existing HOSTSRC variable
|
||||
# using nil as input does not work, but `*[]` does and then Resolv::Hosts
|
||||
# uses its defaults
|
||||
@resolver = opts.fetch(:resolver, Resolv::Hosts.new(*hosts_file))
|
||||
end
|
||||
|
||||
# Get names for ip
|
||||
#
|
||||
# @param [#to_s] ip
|
||||
# The ip to resolve into names
|
||||
#
|
||||
# @return [Array]
|
||||
# Array of Names
|
||||
def getnames(ip)
|
||||
resolver.getnames(ip.to_s).map(&:to_s)
|
||||
rescue Resolv::ResolvError
|
||||
[]
|
||||
end
|
||||
|
||||
# Get ips for name
|
||||
#
|
||||
# @param [#to_s] name
|
||||
# The name to resolve into ips
|
||||
#
|
||||
# @return [Array]
|
||||
# Array of ipaddresses
|
||||
def getaddresses(name)
|
||||
resolver.getaddresses(name.to_s).map(&:to_s)
|
||||
rescue Resolv::ResolvError
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Path to hosts file
|
||||
#
|
||||
# This looks for MM_HOSTSRC in your environment
|
||||
#
|
||||
# @return [Array]
|
||||
# This needs to be an array, to make the splat operator work
|
||||
#
|
||||
# @example
|
||||
# # <ip> <hostname>
|
||||
# 127.0.0.1 localhost.localhost localhost
|
||||
def hosts_file
|
||||
return [ENV['MM_HOSTSRC']] if ENV.key?('MM_HOSTSRC') && File.file?(ENV['MM_HOSTSRC'])
|
||||
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
require 'middleman-core/dns_resolver/basic_network_resolver'
|
||||
|
||||
module Middleman
|
||||
class DnsResolver
|
||||
# Use network name server to resolve ips and names
|
||||
class LocalLinkResolver < BasicNetworkResolver
|
||||
def initialize(opts={})
|
||||
super
|
||||
|
||||
@timeouts = opts.fetch(:timeouts, 1)
|
||||
@resolver = opts.fetch(:resolver, Resolv::MDNS.new(nameserver_config))
|
||||
|
||||
self.timeouts = timeouts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Hosts + Ports for MDNS resolver
|
||||
#
|
||||
# This looks for MM_MDNSRC in your environment. If you are going to use
|
||||
# IPv6-addresses: Make sure you do not forget to add the port at the end.
|
||||
#
|
||||
# MM_MDNSRC=ip:port ip:port
|
||||
#
|
||||
# @return [Hash]
|
||||
# Returns the configuration for the nameserver
|
||||
#
|
||||
# @example
|
||||
# export MM_MDNSRC="224.0.0.251:5353 ff02::fb:5353"
|
||||
#
|
||||
def nameserver_config
|
||||
return unless ENV.key?('MM_MDNSRC') && ENV['MM_MDNSRC']
|
||||
|
||||
address, port = ENV['MM_MDNSRC'].split(/:/)
|
||||
|
||||
{
|
||||
nameserver_port: [[address, port.to_i]]
|
||||
}
|
||||
rescue StandardError
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
require 'middleman-core/dns_resolver/basic_network_resolver'
|
||||
|
||||
module Middleman
|
||||
class DnsResolver
|
||||
# Use network name server to resolve ips and names
|
||||
class NetworkResolver < BasicNetworkResolver
|
||||
def initialize(opts={})
|
||||
super
|
||||
|
||||
@resolver = opts.fetch(:resolver, Resolv::DNS.new(nameserver_config))
|
||||
self.timeouts = timeouts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Hosts + Ports for MDNS resolver
|
||||
#
|
||||
# This looks for MM_MDNSRC in your environment. If you are going to use
|
||||
# IPv6-addresses: Make sure you do not forget to add the port at the end.
|
||||
#
|
||||
# MM_MDNSRC=ip:port ip:port
|
||||
#
|
||||
# @return [Hash]
|
||||
# Returns the configuration for the nameserver
|
||||
#
|
||||
# @example
|
||||
# export MM_MDNSRC="224.0.0.251:5353 ff02::fb:5353"
|
||||
#
|
||||
def nameserver_config
|
||||
return unless ENV.key?('MM_DNSRC') && ENV['MM_DNSRC']
|
||||
|
||||
address, port = ENV['MM_DNSRC'].split(/:/)
|
||||
|
||||
{
|
||||
nameserver_port: [[address, port.to_i]]
|
||||
}
|
||||
rescue StandardError
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +1,16 @@
|
|||
require 'webrick'
|
||||
require 'webrick/https'
|
||||
require 'openssl'
|
||||
require 'socket'
|
||||
require 'middleman-core/meta_pages'
|
||||
require 'middleman-core/logger'
|
||||
require 'middleman-core/preview_server/server_information'
|
||||
require 'middleman-core/preview_server/server_url'
|
||||
|
||||
# rubocop:disable GlobalVars
|
||||
module Middleman
|
||||
module PreviewServer
|
||||
class PreviewServer
|
||||
class << self
|
||||
attr_reader :app, :host, :port, :ssl_certificate, :ssl_private_key, :environment
|
||||
attr_reader :app, :ssl_certificate, :ssl_private_key, :environment, :server_information
|
||||
delegate :logger, to: :app
|
||||
|
||||
def https?
|
||||
|
@ -19,13 +20,31 @@ module Middleman
|
|||
# Start an instance of Middleman::Application
|
||||
# @return [void]
|
||||
def start(opts={})
|
||||
# Do not buffer output, otherwise testing of output does not work
|
||||
$stdout.sync = true
|
||||
$stderr.sync = true
|
||||
|
||||
@options = opts
|
||||
@server_information = ServerInformation.new
|
||||
|
||||
mount_instance(new_app)
|
||||
# New app evaluates the middleman configuration. Since this can be
|
||||
# invalid as well, we need to evaluate the configuration BEFORE
|
||||
# checking for validity
|
||||
the_app = new_app
|
||||
|
||||
# And now comes the check
|
||||
unless server_information.valid?
|
||||
logger.fatal %(== Running Middleman failed: #{server_information.reason}. Please fix that and try again.)
|
||||
exit 1
|
||||
end
|
||||
|
||||
mount_instance(the_app)
|
||||
|
||||
logger.debug %(== Server information is provided by #{server_information.handler})
|
||||
logger.debug %(== The Middleman is running in "#{environment}" environment)
|
||||
logger.info "== The Middleman is standing watch at #{uri} (#{uri(public_ip)})"
|
||||
logger.info "== Inspect your site configuration at #{uri + '__middleman'}"
|
||||
logger.debug format('== The Middleman preview server is bind to %s', ServerUrl.new(hosts: server_information.listeners, port: server_information.port, https: https?).to_bind_addresses.join(', '))
|
||||
logger.info format('== View your site at %s', ServerUrl.new(hosts: server_information.site_addresses, port: server_information.port, https: https?).to_urls.join(', '))
|
||||
logger.info format('== Inspect your site configuration at %s', ServerUrl.new(hosts: server_information.site_addresses, port: server_information.port, https: https?).to_config_urls.join(', '))
|
||||
|
||||
@initialized ||= false
|
||||
return if @initialized
|
||||
|
@ -116,18 +135,26 @@ module Middleman
|
|||
opts[:instrumenting] || false
|
||||
)
|
||||
|
||||
config[:environment] = opts[:environment].to_sym if opts[:environment]
|
||||
config[:port] = opts[:port] if opts[:port]
|
||||
config[:host] = opts[:host].presence || Socket.gethostname.tr(' ', '+')
|
||||
config[:https] = opts[:https] unless opts[:https].nil?
|
||||
config[:environment] = opts[:environment].to_sym if opts[:environment]
|
||||
config[:port] = opts[:port] if opts[:port]
|
||||
config[:bind_address] = opts[:bind_address]
|
||||
config[:server_name] = opts[:server_name]
|
||||
config[:https] = opts[:https] unless opts[:https].nil?
|
||||
config[:ssl_certificate] = opts[:ssl_certificate] if opts[:ssl_certificate]
|
||||
config[:ssl_private_key] = opts[:ssl_private_key] if opts[:ssl_private_key]
|
||||
end
|
||||
|
||||
@host = @app.config[:host]
|
||||
@port = @app.config[:port]
|
||||
@https = @app.config[:https]
|
||||
@environment = @app.config[:environment]
|
||||
# store configured port to make a check later on possible
|
||||
configured_port = @app.config[:port]
|
||||
|
||||
# Use configuration values to set `bind_address` etc. in
|
||||
# `server_information`
|
||||
server_information.use @app.config
|
||||
|
||||
logger.warn format('== The Middleman uses a different port "%s" then the configured one "%s" because some other server is listening on that port.', server_information.port, configured_port) unless @app.config[:port] == configured_port
|
||||
|
||||
@https = @app.config[:https]
|
||||
@environment = @app.config[:environment]
|
||||
|
||||
@ssl_certificate = @app.config[:ssl_certificate]
|
||||
@ssl_private_key = @app.config[:ssl_private_key]
|
||||
|
@ -190,9 +217,10 @@ module Middleman
|
|||
# @return [void]
|
||||
def setup_webrick(is_logging)
|
||||
http_opts = {
|
||||
Port: port,
|
||||
Port: server_information.port,
|
||||
AccessLog: [],
|
||||
ServerName: host,
|
||||
ServerName: server_information.server_name,
|
||||
BindAddress: server_information.bind_address.to_s,
|
||||
DoNotReverseLookup: true
|
||||
}
|
||||
|
||||
|
@ -205,7 +233,7 @@ module Middleman
|
|||
http_opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new File.read ssl_private_key
|
||||
else
|
||||
# use a generated self-signed cert
|
||||
cert, key = create_self_signed_cert(1024, [['CN', host]], 'Middleman Preview Server')
|
||||
cert, key = create_self_signed_cert(1024, [['CN', server_information.server_name]], server_information.site_addresses, 'Middleman Preview Server')
|
||||
http_opts[:SSLCertificate] = cert
|
||||
http_opts[:SSLPrivateKey] = key
|
||||
end
|
||||
|
@ -217,20 +245,17 @@ module Middleman
|
|||
http_opts[:Logger] = ::WEBrick::Log.new(nil, 0)
|
||||
end
|
||||
|
||||
attempts_left = 4
|
||||
tried_ports = []
|
||||
begin
|
||||
::WEBrick::HTTPServer.new(http_opts)
|
||||
rescue Errno::EADDRINUSE
|
||||
logger.error "== Port #{port} is unavailable. Either close the instance of Middleman already running on #{port} or start this Middleman on a new port with: --port=#{unused_tcp_port}"
|
||||
exit(1)
|
||||
logger.error %(== Port "#{http_opts[:Port]}" is in use. This should not have happened. Please start "middleman server" again.)
|
||||
end
|
||||
end
|
||||
|
||||
# Copy of https://github.com/nahi/ruby/blob/webrick_trunk/lib/webrick/ssl.rb#L39
|
||||
# that uses a different serial number each time the cert is generated in order to
|
||||
# avoid errors in Firefox. Also doesn't print out stuff to $stderr unnecessarily.
|
||||
def create_self_signed_cert(bits, cn, comment)
|
||||
def create_self_signed_cert(bits, cn, aliases, comment)
|
||||
rsa = OpenSSL::PKey::RSA.new(bits)
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
cert.version = 2
|
||||
|
@ -254,6 +279,8 @@ module Middleman
|
|||
aki = ef.create_extension('authorityKeyIdentifier',
|
||||
'keyid:always,issuer:always')
|
||||
cert.add_extension(aki)
|
||||
cert.add_extension ef.create_extension('subjectAltName', aliases.map { |d| "DNS: #{d}" }.join(','))
|
||||
|
||||
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
|
||||
|
||||
[cert, rsa]
|
||||
|
@ -306,29 +333,6 @@ module Middleman
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the URI the preview server will run on
|
||||
# @return [URI]
|
||||
def uri(host=@host)
|
||||
scheme = https? ? 'https' : 'http'
|
||||
URI("#{scheme}://#{host}:#{@port}/")
|
||||
end
|
||||
|
||||
# An IPv4 address on this machine which should be externally addressable.
|
||||
# @return [String]
|
||||
def public_ip
|
||||
ip = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
|
||||
ip ? ip.ip_address : '127.0.0.1'
|
||||
end
|
||||
|
||||
# Returns unused TCP port
|
||||
# @return [Fixnum]
|
||||
def unused_tcp_port
|
||||
server = TCPServer.open(0)
|
||||
port = server.addr[1]
|
||||
server.close
|
||||
port
|
||||
end
|
||||
end
|
||||
|
||||
class FilteredWebrickLog < ::WEBrick::Log
|
||||
|
|
81
middleman-core/lib/middleman-core/preview_server/checks.rb
Normal file
81
middleman-core/lib/middleman-core/preview_server/checks.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
require 'ipaddr'
|
||||
|
||||
module Middleman
|
||||
class PreviewServer
|
||||
# Checks for input of preview server
|
||||
module Checks
|
||||
# This one will get all default setup
|
||||
class BasicCheck; end
|
||||
|
||||
# This checks if the server name resolves to the bind_address
|
||||
#
|
||||
# If the users enters:
|
||||
#
|
||||
# 1. server_name: www.example.com (10.0.0.1)
|
||||
# 2. bind_address: 127.0.0.01
|
||||
#
|
||||
# This validation will fail
|
||||
class ServerNameResolvesToBindAddress < BasicCheck
|
||||
private
|
||||
|
||||
attr_reader :resolver
|
||||
|
||||
public
|
||||
|
||||
def initialize
|
||||
@resolver = DnsResolver.new
|
||||
end
|
||||
|
||||
# Validate
|
||||
#
|
||||
# @param [Information] information
|
||||
# The information to be validated
|
||||
def validate(information)
|
||||
return if resolver.ips_for(information.server_name).include? information.bind_address
|
||||
|
||||
information.valid = false
|
||||
information.reason = format('Server name "%s" does not resolve to bind address "%s"', information.server_name, information.bind_address)
|
||||
end
|
||||
end
|
||||
|
||||
# This validation fails if the user chooses to use an ip address which is
|
||||
# not available on his/her system
|
||||
class InterfaceIsAvailableOnSystem < BasicCheck
|
||||
# Validate
|
||||
#
|
||||
# @param [Information] information
|
||||
# The information to be validated
|
||||
def validate(information)
|
||||
return if information.bind_address.blank? || information.local_network_interfaces.include?(information.bind_address.to_s) || %w(0.0.0.0 ::).any? { |b| information.bind_address == b } || IPAddr.new('127.0.0.0/8').include?(information.bind_address.to_s)
|
||||
|
||||
information.valid = false
|
||||
information.reason = format('Bind address "%s" is not available on your system. Please use one of %s', information.bind_address, information.local_network_interfaces.map { |i| %("#{i}") }.join(', '))
|
||||
end
|
||||
end
|
||||
|
||||
# This one requires a bind address if the user entered a server name
|
||||
#
|
||||
# If the `bind_address` is blank this check will fail
|
||||
class RequiresBindAddressIfServerNameIsGiven < BasicCheck
|
||||
def validate(information)
|
||||
return unless information.bind_address.blank?
|
||||
|
||||
information.valid = false
|
||||
information.reason = format('Server name "%s" does not resolve to an ip address', information.server_name)
|
||||
end
|
||||
end
|
||||
|
||||
# This validation always fails
|
||||
class DenyAnyAny < BasicCheck
|
||||
# Validate
|
||||
#
|
||||
# @param [Information] information
|
||||
# The information to be validated
|
||||
def validate(information)
|
||||
information.valid = false
|
||||
information.reason = 'Undefined combination of options "--server-name" and "--bind-address". If you think this is wrong, please file a bug at "https://github.com/middleman/middleman"'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
273
middleman-core/lib/middleman-core/preview_server/information.rb
Normal file
273
middleman-core/lib/middleman-core/preview_server/information.rb
Normal file
|
@ -0,0 +1,273 @@
|
|||
require 'ipaddr'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'middleman-core/preview_server/checks'
|
||||
require 'middleman-core/preview_server/server_hostname'
|
||||
require 'middleman-core/preview_server/server_ip_address'
|
||||
|
||||
module Middleman
|
||||
class PreviewServer
|
||||
# Basic information class to wrap common behaviour
|
||||
class BasicInformation
|
||||
private
|
||||
|
||||
attr_reader :checks, :network_interfaces_inventory
|
||||
|
||||
public
|
||||
|
||||
attr_accessor :bind_address, :server_name, :port, :reason, :valid
|
||||
attr_reader :listeners, :site_addresses
|
||||
|
||||
# Create instance
|
||||
#
|
||||
# @param [String] bind_address
|
||||
# The bind address of the server
|
||||
#
|
||||
# @param [String] server_name
|
||||
# The name of the server
|
||||
#
|
||||
# @param [Integer] port
|
||||
# The port to listen on
|
||||
def initialize(opts={})
|
||||
@bind_address = ServerIpAddress.new(opts[:bind_address])
|
||||
@server_name = ServerHostname.new(opts[:server_name])
|
||||
@port = opts[:port]
|
||||
@valid = true
|
||||
|
||||
@site_addresses = []
|
||||
@listeners = []
|
||||
@checks = []
|
||||
|
||||
# This needs to be check for each use case. Otherwise `Webrick` will
|
||||
# complain about that.
|
||||
@checks << Checks::InterfaceIsAvailableOnSystem.new
|
||||
end
|
||||
|
||||
# Is the given information valid?
|
||||
def valid?
|
||||
valid == true
|
||||
end
|
||||
|
||||
# Pass "self" to validator
|
||||
#
|
||||
# @param [#validate] validator
|
||||
# The validator
|
||||
def validate_me(validator)
|
||||
validator.validate self, checks
|
||||
end
|
||||
|
||||
def resolve_me(*)
|
||||
fail NoMethodError
|
||||
end
|
||||
|
||||
# Get network information
|
||||
#
|
||||
# @param [#network_interfaces] inventory
|
||||
# Get list of available network interfaces
|
||||
def show_me_network_interfaces(inventory)
|
||||
@network_interfaces_inventory = inventory
|
||||
end
|
||||
|
||||
# Default is to get all network interfaces
|
||||
def local_network_interfaces
|
||||
network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:all)
|
||||
end
|
||||
end
|
||||
|
||||
# This only is used if no other parser is available
|
||||
#
|
||||
# The "default" behaviour is to fail because of "Checks::DenyAnyAny"
|
||||
class DefaultInformation < BasicInformation
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
# Make this fail
|
||||
@checks << Checks::DenyAnyAny.new
|
||||
end
|
||||
|
||||
def resolve_me(*); end
|
||||
|
||||
# Always true
|
||||
def self.matches?(*)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# This one is used if no bind address and no server name is given
|
||||
class AllInterfaces < BasicInformation
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
after_init
|
||||
end
|
||||
|
||||
def self.matches?(opts={})
|
||||
opts[:bind_address].blank? && opts[:server_name].blank?
|
||||
end
|
||||
|
||||
# Resolve ips
|
||||
def resolve_me(resolver)
|
||||
hostname = ServerHostname.new(Socket.gethostname)
|
||||
hostname_ips = resolver.ips_for(hostname)
|
||||
network_interface = ServerIpAddress.new(Array(local_network_interfaces).first)
|
||||
resolved_name = ServerHostname.new(resolver.names_for(network_interface).first)
|
||||
|
||||
if includes_array? local_network_interfaces, hostname_ips
|
||||
@server_name = hostname
|
||||
@site_addresses << hostname
|
||||
|
||||
network_interface = ServerIpAddress.new((local_network_interfaces & hostname_ips).first)
|
||||
elsif RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
||||
@server_name = hostname
|
||||
@site_addresses << hostname
|
||||
elsif !resolved_name.blank?
|
||||
@server_name = resolved_name
|
||||
@site_addresses << resolved_name
|
||||
else
|
||||
@server_name = network_interface
|
||||
end
|
||||
|
||||
@site_addresses << network_interface
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def includes_array?(a, b)
|
||||
!(a & b).empty?
|
||||
end
|
||||
|
||||
def after_init
|
||||
@listeners << ServerIpAddress.new('::')
|
||||
@listeners << ServerIpAddress.new('0.0.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
# This is used if bind address is 0.0.0.0, the server name needs to be
|
||||
# blank
|
||||
class AllIpv4Interfaces < AllInterfaces
|
||||
def self.matches?(opts={})
|
||||
opts[:bind_address] == '0.0.0.0' && opts[:server_name].blank?
|
||||
end
|
||||
|
||||
# Use only ipv4 interfaces
|
||||
def local_network_interfaces
|
||||
network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:ipv4)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_init
|
||||
@listeners << ServerIpAddress.new('0.0.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
# This is used if bind address is ::, the server name needs to be blank
|
||||
class AllIpv6Interfaces < AllInterfaces
|
||||
def self.matches?(opts={})
|
||||
opts[:bind_address] == '::' && opts[:server_name].blank?
|
||||
end
|
||||
|
||||
# Use only ipv6 interfaces
|
||||
def local_network_interfaces
|
||||
network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:ipv6)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_init
|
||||
@listeners << ServerIpAddress.new('::')
|
||||
end
|
||||
end
|
||||
|
||||
# Used if a bind address is given and the server name is blank
|
||||
class BindAddressInformation < BasicInformation
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
@listeners << bind_address
|
||||
@site_addresses << bind_address
|
||||
end
|
||||
|
||||
def self.matches?(opts={})
|
||||
!opts[:bind_address].blank? && opts[:server_name].blank?
|
||||
end
|
||||
|
||||
# Resolv
|
||||
def resolve_me(resolver)
|
||||
@server_name = ServerHostname.new(resolver.names_for(bind_address).first)
|
||||
@site_addresses << @server_name unless @server_name.blank?
|
||||
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Use if server name is given and bind address is blank
|
||||
class ServerNameInformation < BasicInformation
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
@checks << Checks::RequiresBindAddressIfServerNameIsGiven.new
|
||||
@site_addresses << server_name
|
||||
end
|
||||
|
||||
def resolve_me(resolver)
|
||||
@bind_address = ServerIpAddress.new(resolver.ips_for(server_name).first)
|
||||
|
||||
unless bind_address.blank?
|
||||
@listeners << bind_address
|
||||
@site_addresses << bind_address
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def self.matches?(opts={})
|
||||
opts[:bind_address].blank? && !opts[:server_name].blank?
|
||||
end
|
||||
end
|
||||
|
||||
# Only used if bind address and server name are given and bind address is
|
||||
# not :: or 0.0.0.0
|
||||
class BindAddressAndServerNameInformation < BasicInformation
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
@listeners << bind_address
|
||||
@site_addresses << server_name
|
||||
@site_addresses << bind_address
|
||||
|
||||
@checks << Checks::ServerNameResolvesToBindAddress.new
|
||||
end
|
||||
|
||||
def self.matches?(opts={})
|
||||
!opts[:bind_address].blank? && !opts[:server_name].blank? && !%w(:: 0.0.0.0).include?(opts[:bind_address])
|
||||
end
|
||||
|
||||
def resolve_me(*); end
|
||||
end
|
||||
|
||||
# If the server name is either an ipv4 or ipv6 address, e.g. 127.0.0.1 or
|
||||
# ::1, use this one
|
||||
class ServerNameIsIpInformation < BasicInformation
|
||||
def initialize(opts={})
|
||||
super
|
||||
|
||||
ip = ServerIpAddress.new(server_name.to_s)
|
||||
|
||||
@listeners << ip
|
||||
@site_addresses << ip
|
||||
end
|
||||
|
||||
def resolve_me(*); end
|
||||
|
||||
def self.matches?(opts={})
|
||||
ip = IPAddr.new(opts[:server_name])
|
||||
|
||||
ip.ipv4? || ip.ipv6?
|
||||
rescue
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
require 'middleman-core/preview_server/server_ip_address'
|
||||
|
||||
module Middleman
|
||||
class PreviewServer
|
||||
# This holds information about local network interfaces on the user systemd
|
||||
class NetworkInterfaceInventory
|
||||
# Return all ip interfaces
|
||||
class All
|
||||
def network_interfaces
|
||||
ipv4_addresses = Socket.ip_address_list.select(&:ipv4?).map { |ai| ServerIpv4Address.new(ai.ip_address) }
|
||||
ipv6_addresses = Socket.ip_address_list.select(&:ipv6?).map { |ai| ServerIpv6Address.new(ai.ip_address) }
|
||||
|
||||
ipv4_addresses + ipv6_addresses
|
||||
end
|
||||
|
||||
def self.match?(*)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Return all ipv4 interfaces
|
||||
class Ipv4
|
||||
def network_interfaces
|
||||
Socket.ip_address_list.select { |ai| ai.ipv4? && !ai.ipv4_loopback? }.map { |ai| ServerIpv4Address.new(ai.ip_address) }
|
||||
end
|
||||
|
||||
def self.match?(type)
|
||||
:ipv4 == type
|
||||
end
|
||||
end
|
||||
|
||||
# Return all ipv6 interfaces
|
||||
class Ipv6
|
||||
def network_interfaces
|
||||
Socket.ip_address_list.select { |ai| ai.ipv6? && !ai.ipv6_loopback? }.map { |ai| ServerIpv6Address.new(ai.ip_address) }
|
||||
end
|
||||
|
||||
def self.match?(type)
|
||||
:ipv6 == type
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :types
|
||||
|
||||
public
|
||||
|
||||
def initialize
|
||||
@types = []
|
||||
@types << Ipv4
|
||||
@types << Ipv6
|
||||
@types << All
|
||||
end
|
||||
|
||||
# Return ip interfaces
|
||||
#
|
||||
# @param [Symbol] type
|
||||
# The type of interface which should be returned
|
||||
def network_interfaces(type=:all)
|
||||
types.find { |t| t.match? type.to_sym }.new.network_interfaces
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
module Middleman
|
||||
class PreviewServer
|
||||
class ServerHostname
|
||||
class ServerFullHostname < SimpleDelegator
|
||||
def to_s
|
||||
__getobj__
|
||||
end
|
||||
|
||||
def self.match?(*)
|
||||
true
|
||||
end
|
||||
|
||||
alias_method :to_browser, :to_s
|
||||
end
|
||||
|
||||
class ServerPlainHostname < SimpleDelegator
|
||||
def to_s
|
||||
__getobj__ + '.local'
|
||||
end
|
||||
|
||||
def self.match?(name)
|
||||
# rubocop:disable Style/CaseEquality
|
||||
name != 'localhost' && /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?\.?$/ === name
|
||||
# rubocop:enable Style/CaseEquality
|
||||
end
|
||||
|
||||
alias_method :to_browser, :to_s
|
||||
end
|
||||
|
||||
def self.new(string)
|
||||
@names = []
|
||||
@names << ServerPlainHostname
|
||||
@names << ServerFullHostname
|
||||
|
||||
@names.find { |n| n.match? string }.new(string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,144 @@
|
|||
require 'middleman-core/dns_resolver'
|
||||
require 'middleman-core/preview_server/information'
|
||||
require 'middleman-core/preview_server/network_interface_inventory'
|
||||
require 'middleman-core/preview_server/tcp_port_prober'
|
||||
require 'middleman-core/preview_server/server_information_validator'
|
||||
|
||||
module Middleman
|
||||
class PreviewServer
|
||||
# This class holds all information which the preview server needs to setup a listener
|
||||
#
|
||||
# * server name
|
||||
# * bind address
|
||||
# * port
|
||||
#
|
||||
# Furthermore it probes for a free tcp port, if the default one 4567 is not available.
|
||||
class ServerInformation
|
||||
private
|
||||
|
||||
attr_reader :resolver, :validator, :network_interface_inventory, :informations, :tcp_port_prober
|
||||
|
||||
public
|
||||
|
||||
def initialize(opts={})
|
||||
@resolver = opts.fetch(:resolver, DnsResolver.new)
|
||||
@validator = opts.fetch(:validator, ServerInformationValidator.new)
|
||||
@network_interface_inventory = opts.fetch(:network_interface_inventory, NetworkInterfaceInventory.new)
|
||||
@tcp_port_prober = opts.fetch(:tcp_port_prober, TcpPortProber.new)
|
||||
|
||||
@informations = []
|
||||
@informations << AllInterfaces
|
||||
@informations << AllIpv4Interfaces
|
||||
@informations << AllIpv6Interfaces
|
||||
@informations << ServerNameIsIpInformation
|
||||
@informations << ServerNameInformation
|
||||
@informations << BindAddressInformation
|
||||
@informations << BindAddressAndServerNameInformation
|
||||
@informations << DefaultInformation
|
||||
end
|
||||
|
||||
# The information
|
||||
#
|
||||
# Is cached
|
||||
def information
|
||||
return @information if @information
|
||||
|
||||
# The `DefaultInformation`-class always returns `true`, so there's
|
||||
# always a klass available and find will never return nil
|
||||
listener_klass = informations.find { |l| l.matches? bind_address: @bind_address, server_name: @server_name }
|
||||
@information = listener_klass.new(bind_address: @bind_address, server_name: @server_name)
|
||||
|
||||
@information.show_me_network_interfaces(network_interface_inventory)
|
||||
@information.resolve_me(resolver)
|
||||
@information.port = tcp_port_prober.port(@port)
|
||||
@information.validate_me(validator)
|
||||
|
||||
@information
|
||||
end
|
||||
|
||||
# Use a middleman configuration to get information
|
||||
#
|
||||
# @param [#[]] config
|
||||
# The middleman config
|
||||
def use(config)
|
||||
@bind_address = config[:bind_address]
|
||||
@port = config[:port]
|
||||
@server_name = config[:server_name]
|
||||
|
||||
config[:bind_address] = bind_address
|
||||
config[:port] = port
|
||||
config[:server_name] = server_name
|
||||
end
|
||||
|
||||
# Make information of internal server class avaible to make debugging
|
||||
# easier. This can be used to log the class which was used to determine
|
||||
# the preview server settings
|
||||
#
|
||||
# @return [String]
|
||||
# The name of the class
|
||||
def handler
|
||||
information.class.to_s
|
||||
end
|
||||
|
||||
# Is the server information valid?
|
||||
#
|
||||
# This is used to output a helpful error message, which can be stored in
|
||||
# `#reason`.
|
||||
#
|
||||
# @return [TrueClass, FalseClass]
|
||||
# The result
|
||||
def valid?
|
||||
information.valid?
|
||||
end
|
||||
|
||||
# The reason why the information is NOT valid
|
||||
#
|
||||
# @return [String]
|
||||
# The reason why the information is not valid
|
||||
def reason
|
||||
information.reason
|
||||
end
|
||||
|
||||
# The server name
|
||||
#
|
||||
# @return [String]
|
||||
# The name of the server
|
||||
def server_name
|
||||
information.server_name
|
||||
end
|
||||
|
||||
# The bind address of server
|
||||
#
|
||||
# @return [String]
|
||||
# The bind address of the server
|
||||
def bind_address
|
||||
information.bind_address
|
||||
end
|
||||
|
||||
# The port on which the server should listen
|
||||
#
|
||||
# @return [Integer]
|
||||
# The port number
|
||||
def port
|
||||
information.port
|
||||
end
|
||||
|
||||
# A list of site addresses
|
||||
#
|
||||
# @return [Array]
|
||||
# A list of addresses which can be used to access the middleman preview
|
||||
# server
|
||||
def site_addresses
|
||||
information.site_addresses
|
||||
end
|
||||
|
||||
# A list of listeners
|
||||
#
|
||||
# @return [Array]
|
||||
# A list of bind address where the
|
||||
def listeners
|
||||
information.listeners
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module Middleman
|
||||
class PreviewServer
|
||||
# Validate user input
|
||||
class ServerInformationValidator
|
||||
# Validate the input
|
||||
#
|
||||
# @param [ServerInformation] information
|
||||
# The information instance which holds information about the preview
|
||||
# server settings
|
||||
#
|
||||
# @param [Array] checks
|
||||
# A list of checks which should be evaluated
|
||||
def validate(information, checks)
|
||||
checks.each { |c| c.validate information }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
require 'ipaddr'
|
||||
require 'forwardable'
|
||||
|
||||
module Middleman
|
||||
class PreviewServer
|
||||
class ServerIpAddress
|
||||
def self.new(ip_address)
|
||||
@parser = []
|
||||
@parser << ServerIpv6Address
|
||||
@parser << ServerIpv4Address
|
||||
|
||||
@parser.find { |p| p.match? ip_address }.new(ip_address)
|
||||
end
|
||||
end
|
||||
|
||||
class BasicServerIpAddress < SimpleDelegator
|
||||
end
|
||||
|
||||
class ServerIpv4Address < BasicServerIpAddress
|
||||
def to_browser
|
||||
__getobj__.to_s
|
||||
end
|
||||
|
||||
def self.match?(*)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class ServerIpv6Address < BasicServerIpAddress
|
||||
def to_s
|
||||
__getobj__.sub(/%.*$/, '')
|
||||
end
|
||||
|
||||
def to_browser
|
||||
format('[%s]', to_s)
|
||||
end
|
||||
|
||||
if RUBY_VERSION < '2'
|
||||
def self.match?(str)
|
||||
str = str.to_s.sub(/%.*$/, '')
|
||||
IPAddr.new(str).ipv6?
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
else
|
||||
def self.match?(str)
|
||||
str = str.to_s.sub(/%.*$/, '')
|
||||
IPAddr.new(str).ipv6?
|
||||
rescue IPAddr::InvalidAddressError, IPAddr::AddressFamilyError
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
require 'ipaddr'
|
||||
|
||||
module Middleman
|
||||
class PreviewServer
|
||||
# This builds the server urls for the preview server
|
||||
class ServerUrl
|
||||
private
|
||||
|
||||
attr_reader :hosts, :port, :https
|
||||
|
||||
public
|
||||
|
||||
def initialize(opts={})
|
||||
@hosts = opts.fetch(:hosts)
|
||||
@port = opts.fetch(:port)
|
||||
@https = opts.fetch(:https, false)
|
||||
end
|
||||
|
||||
# Return bind addresses
|
||||
#
|
||||
# @return [Array]
|
||||
# List of bind addresses of format host:port
|
||||
def to_bind_addresses
|
||||
hosts.map { |l| format('"%s:%s"', l.to_s, port) }
|
||||
end
|
||||
|
||||
# Return server urls
|
||||
#
|
||||
# @return [Array]
|
||||
# List of urls of format http://host:port
|
||||
def to_urls
|
||||
hosts.map { |l| format('"%s://%s:%s"', https? ? 'https' : 'http', l.to_browser, port) }
|
||||
end
|
||||
|
||||
# Return server config urls
|
||||
#
|
||||
# @return [Array]
|
||||
# List of urls of format http://host:port/__middleman
|
||||
def to_config_urls
|
||||
hosts.map { |l| format('"%s://%s:%s/__middleman"', https? ? 'https' : 'http', l.to_browser, port) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def https?
|
||||
https == true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
module Middleman
|
||||
class PreviewServer
|
||||
# Probe for tcp ports
|
||||
#
|
||||
# This one first tries `try_port` if this is not available use the free
|
||||
# port returned by TCPServer.
|
||||
class TcpPortProber
|
||||
# Check for port
|
||||
#
|
||||
# @param [Integer] try_port
|
||||
# The port to be checked
|
||||
#
|
||||
# @return [Integer]
|
||||
# The port
|
||||
def port(try_port)
|
||||
server = TCPServer.open(try_port)
|
||||
server.close
|
||||
|
||||
try_port
|
||||
rescue
|
||||
server = TCPServer.open(0)
|
||||
port = server.addr[1]
|
||||
server.close
|
||||
|
||||
port
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@ require 'aruba/jruby'
|
|||
require 'middleman-core/step_definitions/middleman_steps'
|
||||
require 'middleman-core/step_definitions/builder_steps'
|
||||
require 'middleman-core/step_definitions/server_steps'
|
||||
require 'middleman-core/step_definitions/commandline_steps'
|
||||
|
||||
# Monkeypatch for windows support
|
||||
module ArubaMonkeypatch
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
When /^I stop (?:middleman|all commands) if the output( of the last command)? contains:$/ do |last_command, expected|
|
||||
begin
|
||||
Timeout.timeout(exit_timeout) do
|
||||
loop do
|
||||
fail "You need to start middleman interactively first." unless @interactive
|
||||
|
||||
if unescape(@interactive.output) =~ Regexp.new(unescape(expected))
|
||||
only_processes.each { |p| p.terminate }
|
||||
break
|
||||
end
|
||||
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
rescue ChildProcess::TimeoutError, TimeoutError
|
||||
@interactive.terminate
|
||||
ensure
|
||||
announcer.stdout @interactive.stdout
|
||||
announcer.stderr @interactive.stderr
|
||||
end
|
||||
end
|
||||
|
||||
# Make it just a long running process
|
||||
Given /`(.*?)` is running in background/ do |cmd|
|
||||
run(cmd, 120)
|
||||
end
|
||||
|
||||
Given /I have a local hosts file with:/ do |string|
|
||||
step 'I set the environment variables to:', table(
|
||||
%(
|
||||
| variable | value |
|
||||
| MM_HOSTSRC | .hosts |
|
||||
)
|
||||
)
|
||||
|
||||
step 'a file named ".hosts" with:', string
|
||||
end
|
||||
|
||||
Given /I start a dns server with:/ do |string|
|
||||
@dns_server.terminate if defined? @dns_server
|
||||
|
||||
port = 5300
|
||||
db_file = 'dns.db'
|
||||
|
||||
step 'I set the environment variables to:', table(
|
||||
%(
|
||||
| variable | value |
|
||||
| MM_DNSRC | 127.0.0.1:#{port}|
|
||||
)
|
||||
)
|
||||
|
||||
set_env 'PATH', File.expand_path(File.join(current_dir, 'bin')) + ':' + ENV['PATH']
|
||||
write_file db_file, string
|
||||
|
||||
@dns_server = run("dns_server.rb #{db_file} #{port}", 120)
|
||||
end
|
||||
|
||||
Given /I start a mdns server with:/ do |string|
|
||||
@mdns_server.terminate if defined? @mdns_server
|
||||
|
||||
port = 5301
|
||||
db_file = 'mdns.db'
|
||||
|
||||
step 'I set the environment variables to:', table(
|
||||
%(
|
||||
| variable | value |
|
||||
| MM_MDNSRC | 127.0.0.1:#{port}|
|
||||
)
|
||||
)
|
||||
|
||||
set_env 'PATH', File.expand_path(File.join(current_dir, 'bin')) + ':' + ENV['PATH']
|
||||
write_file db_file, string
|
||||
|
||||
@mdns_server = run("dns_server.rb #{db_file} #{port}", 120)
|
||||
end
|
||||
|
||||
Given /I start a mdns server for the local hostname/ do
|
||||
step %(I start a mdns server with:), "#{Socket.gethostname}: 127.0.0.1"
|
||||
end
|
||||
|
||||
# Make sure each and every process is really dead
|
||||
After do
|
||||
only_processes.each { |p| p.terminate }
|
||||
end
|
||||
|
||||
Before '@ruby-2.1' do
|
||||
skip_this_scenario if RUBY_VERSION < '2.1'
|
||||
end
|
118
middleman-core/spec/middleman-core/dns_resolver_spec.rb
Normal file
118
middleman-core/spec/middleman-core/dns_resolver_spec.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
require 'spec_helper'
|
||||
require 'middleman-core/dns_resolver'
|
||||
|
||||
RSpec.describe Middleman::DnsResolver do
|
||||
subject(:resolver) do
|
||||
described_class.new(
|
||||
hosts_resolver: hosts_resolver,
|
||||
local_link_resolver: local_link_resolver,
|
||||
network_resolver: network_resolver
|
||||
)
|
||||
end
|
||||
|
||||
let(:hosts_resolver) { instance_double('Middleman::DnsResolver::HostsResolver') }
|
||||
let(:local_link_resolver) { instance_double('Middleman::DnsResolver::LocalLinkResolver') }
|
||||
let(:network_resolver) { instance_double('Middleman::DnsResolver::NetworkResolver') }
|
||||
|
||||
before :each do
|
||||
allow(network_resolver).to receive(:timeouts=)
|
||||
end
|
||||
|
||||
describe '#names_for' do
|
||||
context 'when hosts resolver can resolve name' do
|
||||
before :each do
|
||||
expect(hosts_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
|
||||
if RUBY_VERSION >= '2.1'
|
||||
expect(local_link_resolver).not_to receive(:getnames)
|
||||
end
|
||||
expect(network_resolver).not_to receive(:getnames)
|
||||
end
|
||||
|
||||
let(:unresolved_ip) { '127.0.0.1' }
|
||||
let(:resolved_names) { %w(localhost) }
|
||||
|
||||
it { expect(resolver.names_for(unresolved_ip)).to eq resolved_names }
|
||||
end
|
||||
|
||||
context 'when local link resolver can resolve name' do
|
||||
before :each do
|
||||
expect(hosts_resolver).to receive(:getnames).with(unresolved_ip).and_return([])
|
||||
if RUBY_VERSION >= '2.1'
|
||||
expect(local_link_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
|
||||
expect(network_resolver).not_to receive(:getnames)
|
||||
else
|
||||
expect(network_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
|
||||
end
|
||||
end
|
||||
|
||||
let(:unresolved_ip) { '127.0.0.1' }
|
||||
let(:resolved_names) { %w(localhost) }
|
||||
|
||||
it { expect(resolver.names_for(unresolved_ip)).to eq resolved_names }
|
||||
end
|
||||
|
||||
context 'when network resolver can resolve name' do
|
||||
before :each do
|
||||
expect(hosts_resolver).to receive(:getnames).with(unresolved_ip).and_return([])
|
||||
if RUBY_VERSION >= '2.1'
|
||||
expect(local_link_resolver).to receive(:getnames).with(unresolved_ip).and_return([])
|
||||
end
|
||||
expect(network_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
|
||||
end
|
||||
|
||||
let(:unresolved_ip) { '127.0.0.1' }
|
||||
let(:resolved_names) { %w(localhost) }
|
||||
|
||||
it { expect(resolver.names_for(unresolved_ip)).to eq resolved_names }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ips_for' do
|
||||
context 'when hosts resolver can resolve name' do
|
||||
before :each do
|
||||
expect(hosts_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
|
||||
if RUBY_VERSION >= '2.1'
|
||||
expect(local_link_resolver).not_to receive(:getaddresses)
|
||||
end
|
||||
expect(network_resolver).not_to receive(:getaddresses)
|
||||
end
|
||||
|
||||
let(:unresolved_ips) { '127.0.0.1' }
|
||||
let(:resolved_name) { %w(localhost) }
|
||||
|
||||
it { expect(resolver.ips_for(unresolved_ips)).to eq resolved_name }
|
||||
end
|
||||
|
||||
context 'when local link resolver can resolve name' do
|
||||
before :each do
|
||||
expect(hosts_resolver).to receive(:getaddresses).with(unresolved_ips).and_return([])
|
||||
if RUBY_VERSION >= '2.1'
|
||||
expect(local_link_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
|
||||
expect(network_resolver).not_to receive(:getaddresses)
|
||||
else
|
||||
expect(network_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
|
||||
end
|
||||
end
|
||||
|
||||
let(:unresolved_ips) { '127.0.0.1' }
|
||||
let(:resolved_name) { %w(localhost) }
|
||||
|
||||
it { expect(resolver.ips_for(unresolved_ips)).to eq resolved_name }
|
||||
end
|
||||
|
||||
context 'when network resolver can resolve name' do
|
||||
before :each do
|
||||
expect(hosts_resolver).to receive(:getaddresses).with(unresolved_ips).and_return([])
|
||||
if RUBY_VERSION >= '2.1'
|
||||
expect(local_link_resolver).to receive(:getaddresses).with(unresolved_ips).and_return([])
|
||||
end
|
||||
expect(network_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
|
||||
end
|
||||
|
||||
let(:unresolved_ips) { '127.0.0.1' }
|
||||
let(:resolved_name) { %w(localhost) }
|
||||
|
||||
it { expect(resolver.ips_for(unresolved_ips)).to eq resolved_name }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
require 'middleman-core/preview_server/server_hostname'
|
||||
|
||||
RSpec.describe Middleman::PreviewServer::ServerHostname do
|
||||
subject(:hostname) { described_class.new(string) }
|
||||
let(:string) { 'www.example.com' }
|
||||
|
||||
describe '#to_s' do
|
||||
context 'when hostname' do
|
||||
it { expect(hostname.to_s).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv4' do
|
||||
let(:string) { '127.0.0.1' }
|
||||
it { expect(hostname.to_s).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv6' do
|
||||
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
|
||||
it { expect(hostname.to_s).to eq string }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_browser' do
|
||||
context 'when hostname' do
|
||||
it { expect(hostname.to_browser).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv4' do
|
||||
let(:string) { '127.0.0.1' }
|
||||
it { expect(hostname.to_browser).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv6' do
|
||||
let(:string) { '::1' }
|
||||
it { expect(hostname.to_browser).to eq string }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
require 'middleman-core/preview_server/server_ip_address'
|
||||
|
||||
RSpec.describe Middleman::PreviewServer::ServerIpAddress do
|
||||
subject(:ip_address) { described_class.new(string) }
|
||||
let(:string) { '127.0.0.1' }
|
||||
|
||||
describe '#to_s' do
|
||||
context 'when ipv4' do
|
||||
let(:string) { '127.0.0.1' }
|
||||
it { expect(ip_address.to_s).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv6' do
|
||||
context 'without suffix' do
|
||||
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
|
||||
it { expect(ip_address.to_s).to eq string }
|
||||
end
|
||||
|
||||
context 'with suffix' do
|
||||
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b%wlp1s0' }
|
||||
let(:result) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
|
||||
it { expect(ip_address.to_s).to eq result }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_browser' do
|
||||
context 'when ip_address' do
|
||||
it { expect(ip_address.to_browser).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv4' do
|
||||
let(:string) { '127.0.0.1' }
|
||||
it { expect(ip_address.to_browser).to eq string }
|
||||
end
|
||||
|
||||
context 'when ipv6' do
|
||||
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
|
||||
it { expect(ip_address.to_browser).to eq "[#{string}]" }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,3 +3,29 @@ SimpleCov.root(File.expand_path(File.dirname(__FILE__) + '/..'))
|
|||
|
||||
require 'coveralls'
|
||||
Coveralls.wear!
|
||||
|
||||
require 'aruba/api'
|
||||
RSpec.configure do |config|
|
||||
config.include Aruba::Api
|
||||
end
|
||||
|
||||
# encoding: utf-8
|
||||
RSpec.configure do |config|
|
||||
config.filter_run :focus
|
||||
config.run_all_when_everything_filtered = true
|
||||
|
||||
config.default_formatter = 'doc' if config.files_to_run.one?
|
||||
|
||||
# config.profile_examples = 10
|
||||
config.order = :random
|
||||
Kernel.srand config.seed
|
||||
|
||||
config.expect_with :rspec do |expectations|
|
||||
expectations.syntax = :expect
|
||||
end
|
||||
|
||||
config.mock_with :rspec do |mocks|
|
||||
mocks.syntax = :expect
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue