2013-12-28 01:26:31 +01:00
require 'webrick'
2015-05-03 05:54:58 +02:00
require 'webrick/https'
require 'openssl'
2015-05-04 02:53:54 +02:00
require 'socket'
2012-10-13 22:12:47 +02:00
require 'middleman-core/meta_pages'
2013-12-31 23:41:17 +01:00
require 'middleman-core/logger'
2012-07-09 09:16:13 +02:00
2014-04-29 19:44:24 +02:00
# rubocop:disable GlobalVars
2012-05-20 01:49:44 +02:00
module Middleman
module PreviewServer
class << self
2015-05-08 10:12:57 +02:00
attr_reader :app , :host , :port , :ssl_certificate , :ssl_private_key , :environment
2014-04-29 19:44:24 +02:00
delegate :logger , to : :app
2012-08-14 00:39:06 +02:00
2015-05-03 05:54:58 +02:00
def https?
@https
end
2012-05-20 01:49:44 +02:00
# Start an instance of Middleman::Application
# @return [void]
2012-09-13 19:13:57 +02:00
def start ( opts = { } )
@options = opts
2012-08-14 00:39:06 +02:00
2013-10-20 04:39:10 +02:00
mount_instance ( new_app )
2015-05-08 10:12:57 +02:00
logger . debug %( == The Middleman is running in " #{ environment } " environment )
2015-05-04 02:53:54 +02:00
logger . info " == The Middleman is standing watch at #{ uri } ( #{ uri ( public_ip ) } ) "
2014-02-17 10:28:57 +01:00
logger . info " == Inspect your site configuration at #{ uri + '__middleman' } "
2012-05-20 08:17:13 +02:00
@initialized || = false
2014-07-02 19:11:52 +02:00
return if @initialized
@initialized = true
2012-08-14 00:39:06 +02:00
2014-07-02 19:11:52 +02:00
register_signal_handlers
2012-08-14 00:39:06 +02:00
2014-07-02 19:11:52 +02:00
# Save the last-used @options so it may be re-used when
# reloading later on.
:: Middleman :: Profiling . report ( 'server_start' )
2012-07-19 07:10:02 +02:00
2014-07-02 19:11:52 +02:00
loop do
@webrick . start
2013-03-03 21:31:42 +01:00
2014-07-02 19:11:52 +02:00
# $mm_shutdown is set by the signal handler
if $mm_shutdown
shutdown
exit
elsif $mm_reload
$mm_reload = false
reload
2013-03-03 21:31:42 +01:00
end
2012-05-20 01:49:44 +02:00
end
end
# Detach the current Middleman::Application instance
# @return [void]
def stop
2012-09-28 08:02:59 +02:00
begin
2013-12-28 01:26:31 +01:00
logger . info '== The Middleman is shutting down'
2012-09-28 08:02:59 +02:00
rescue
# if the user closed their terminal STDOUT/STDERR won't exist
end
2012-06-28 08:27:27 +02:00
if @listener
@listener . stop
@listener = nil
end
2012-05-20 01:49:44 +02:00
unmount_instance
end
2012-08-14 00:39:06 +02:00
2012-05-20 01:49:44 +02:00
# Simply stop, then start the server
# @return [void]
def reload
2013-12-28 01:26:31 +01:00
logger . info '== The Middleman is reloading'
2012-11-11 03:16:47 +01:00
2013-10-20 04:39:10 +02:00
begin
app = new_app
2014-04-29 01:02:18 +02:00
rescue = > e
2013-10-20 04:39:10 +02:00
logger . error " Error reloading Middleman: #{ e } \n #{ e . backtrace . join ( " \n " ) } "
2013-12-28 01:26:31 +01:00
logger . info '== The Middleman is still running the application from before the error'
2013-10-20 04:39:10 +02:00
return
end
2012-09-13 19:13:57 +02:00
unmount_instance
2015-05-11 18:24:22 +02:00
@webrick . shutdown
@webrick = nil
2013-10-20 04:39:10 +02:00
mount_instance ( app )
2012-11-11 03:16:47 +01:00
2013-12-28 01:26:31 +01:00
logger . info '== The Middleman has reloaded'
2012-05-20 01:49:44 +02:00
end
# Stop the current instance, exit Webrick
# @return [void]
def shutdown
stop
@webrick . shutdown
end
2012-08-14 00:39:06 +02:00
2014-04-29 01:02:18 +02:00
private
2014-04-29 19:44:24 +02:00
2012-09-13 19:13:57 +02:00
def new_app
2013-12-31 23:41:17 +01:00
opts = @options . dup
2015-03-02 03:17:22 +01:00
2013-04-13 07:48:56 +02:00
server = :: Middleman :: Application . server
# Add in the meta pages application
meta_app = Middleman :: MetaPages :: Application . new ( server )
server . map '/__middleman' do
run meta_app
end
@app = server . inst do
2013-12-31 23:41:17 +01:00
:: Middleman :: Logger . singleton (
opts [ :debug ] ? 0 : 1 ,
opts [ :instrumenting ] || false
)
2014-07-02 19:11:52 +02:00
config [ :environment ] = opts [ :environment ] . to_sym if opts [ :environment ]
2015-03-02 03:17:22 +01:00
config [ :port ] = opts [ :port ] if opts [ :port ]
2015-05-07 09:21:11 +02:00
config [ :host ] = opts [ :host ] . presence || Socket . gethostname . tr ( ' ' , '+' )
2015-05-03 05:54:58 +02:00
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 ]
2012-09-13 19:13:57 +02:00
end
2015-03-02 03:17:22 +01:00
2015-05-08 10:12:57 +02:00
@host = @app . config [ :host ]
@port = @app . config [ :port ]
@https = @app . config [ :https ]
@environment = @app . config [ :environment ]
2015-05-03 05:54:58 +02:00
@ssl_certificate = @app . config [ :ssl_certificate ]
@ssl_private_key = @app . config [ :ssl_private_key ]
2015-03-02 03:17:22 +01:00
@app
2012-09-13 19:13:57 +02:00
end
2012-08-14 00:39:06 +02:00
2012-05-20 01:49:44 +02:00
def start_file_watcher
2014-07-18 19:54:48 +02:00
return if @listener || @options [ :disable_watcher ]
2012-11-11 03:16:47 +01:00
2014-07-02 19:26:18 +02:00
# Watcher Library
require 'listen'
2012-11-11 03:16:47 +01:00
2014-07-18 19:54:48 +02:00
options = { force_polling : @options [ :force_polling ] }
2014-07-02 19:26:18 +02:00
options [ :latency ] = @options [ :latency ] if @options [ :latency ]
2012-11-11 03:16:47 +01:00
2015-05-17 21:25:17 +02:00
@listener = Listen . to ( :: Middleman :: Util . current_directory , options ) do | modified , added , removed |
2012-05-20 01:49:44 +02:00
added_and_modified = ( modified + added )
2012-09-13 19:51:16 +02:00
# See if the changed file is config.rb or lib/*.rb
2013-03-22 18:28:38 +01:00
if needs_to_reload? ( added_and_modified + removed )
2013-10-20 04:39:10 +02:00
$mm_reload = true
@webrick . stop
2012-09-13 19:51:16 +02:00
else
2015-05-17 21:25:17 +02:00
wd = Pathname ( :: Middleman :: Util . current_directory )
2012-05-20 08:05:06 +02:00
added_and_modified . each do | path |
2015-05-17 21:25:17 +02:00
relative_path = Pathname ( path ) . relative_path_from ( wd ) . to_s
2014-07-02 19:26:18 +02:00
next if app . files . ignored? ( relative_path )
app . files . did_change ( relative_path )
2012-05-20 01:49:44 +02:00
end
removed . each do | path |
2015-05-17 21:25:17 +02:00
relative_path = Pathname ( path ) . relative_path_from ( wd ) . to_s
2014-07-02 19:26:18 +02:00
next if app . files . ignored? ( relative_path )
app . files . did_delete ( relative_path )
2012-05-20 01:49:44 +02:00
end
end
end
2012-08-14 00:39:06 +02:00
2012-05-20 01:49:44 +02:00
# Don't block this thread
2014-07-02 19:26:18 +02:00
@listener . start
2012-05-20 01:49:44 +02:00
end
2012-08-14 00:39:06 +02:00
2012-09-28 08:02:59 +02:00
# Trap some interupt signals and shut down smoothly
2012-05-20 01:49:44 +02:00
# @return [void]
def register_signal_handlers
2012-09-28 08:02:59 +02:00
%w( INT HUP TERM QUIT ) . each do | sig |
2014-07-02 19:11:52 +02:00
next unless Signal . list [ sig ]
Signal . trap ( sig ) do
# Do as little work as possible in the signal context
$mm_shutdown = true
@webrick . stop
2012-09-28 08:02:59 +02:00
end
end
2012-05-20 01:49:44 +02:00
end
2012-08-14 00:39:06 +02:00
# Initialize webrick
2012-05-20 01:49:44 +02:00
# @return [void]
2012-11-11 03:16:47 +01:00
def setup_webrick ( is_logging )
2012-05-20 01:49:44 +02:00
http_opts = {
2014-04-29 19:44:24 +02:00
Port : port ,
AccessLog : [ ] ,
2015-05-07 09:21:11 +02:00
ServerName : host ,
2014-04-29 19:44:24 +02:00
DoNotReverseLookup : true
2012-05-20 01:49:44 +02:00
}
2012-08-14 00:39:06 +02:00
2015-05-03 05:54:58 +02:00
if https?
http_opts [ :SSLEnable ] = true
if ssl_certificate || ssl_private_key
2015-05-16 22:21:12 +02:00
raise 'You must provide both :ssl_certificate and :ssl_private_key' unless ssl_private_key && ssl_certificate
2015-05-03 05:54:58 +02:00
http_opts [ :SSLCertificate ] = OpenSSL :: X509 :: Certificate . new File . read ssl_certificate
http_opts [ :SSLPrivateKey ] = OpenSSL :: PKey :: RSA . new File . read ssl_private_key
else
# use a generated self-signed cert
2015-05-16 22:21:12 +02:00
cert , key = create_self_signed_cert ( 1024 , [ [ 'CN' , host ] ] , 'Middleman Preview Server' )
2015-05-08 07:09:09 +02:00
http_opts [ :SSLCertificate ] = cert
http_opts [ :SSLPrivateKey ] = key
2015-05-03 05:54:58 +02:00
end
end
2012-07-09 09:16:13 +02:00
if is_logging
http_opts [ :Logger ] = FilteredWebrickLog . new
else
http_opts [ :Logger ] = :: WEBrick :: Log . new ( nil , 0 )
2012-05-20 01:49:44 +02:00
end
2012-08-14 00:39:06 +02:00
2015-05-08 17:36:43 +02:00
attempts_left = 4
tried_ports = [ ]
2012-09-13 19:13:57 +02:00
begin
:: WEBrick :: HTTPServer . new ( http_opts )
2013-04-06 23:48:00 +02:00
rescue Errno :: EADDRINUSE
2015-05-25 03:49:21 +02:00
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 } "
2012-09-13 19:13:57 +02:00
exit ( 1 )
end
2012-05-20 01:49:44 +02:00
end
2012-08-14 00:39:06 +02:00
2015-05-08 07:09:09 +02:00
# 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 )
rsa = OpenSSL :: PKey :: RSA . new ( bits )
cert = OpenSSL :: X509 :: Certificate . new
cert . version = 2
cert . serial = Time . now . to_i % ( 1 << 20 )
name = OpenSSL :: X509 :: Name . new ( cn )
cert . subject = name
cert . issuer = name
cert . not_before = Time . now
2015-05-16 22:21:12 +02:00
cert . not_after = Time . now + ( 365 * 24 * 60 * 60 )
2015-05-08 07:09:09 +02:00
cert . public_key = rsa . public_key
2015-05-16 22:21:12 +02:00
ef = OpenSSL :: X509 :: ExtensionFactory . new ( nil , cert )
2015-05-08 07:09:09 +02:00
ef . issuer_certificate = cert
cert . extensions = [
2015-05-16 22:21:12 +02:00
ef . create_extension ( 'basicConstraints' , 'CA:FALSE' ) ,
ef . create_extension ( 'keyUsage' , 'keyEncipherment' ) ,
ef . create_extension ( 'subjectKeyIdentifier' , 'hash' ) ,
ef . create_extension ( 'extendedKeyUsage' , 'serverAuth' ) ,
ef . create_extension ( 'nsComment' , comment )
]
aki = ef . create_extension ( 'authorityKeyIdentifier' ,
'keyid:always,issuer:always' )
2015-05-08 07:09:09 +02:00
cert . add_extension ( aki )
cert . sign ( rsa , OpenSSL :: Digest :: SHA1 . new )
2015-05-16 22:21:12 +02:00
[ cert , rsa ]
2015-05-08 07:09:09 +02:00
end
2012-05-20 01:49:44 +02:00
# Attach a new Middleman::Application instance
# @param [Middleman::Application] app
# @return [void]
2013-10-20 04:39:10 +02:00
def mount_instance ( app )
@app = app
2012-09-28 08:02:59 +02:00
2012-11-11 03:16:47 +01:00
@webrick || = setup_webrick ( @options [ :debug ] || false )
2012-09-13 19:51:16 +02:00
start_file_watcher
2013-03-22 18:28:38 +01:00
2012-10-13 22:12:47 +02:00
rack_app = app . class . to_rack_app
2013-12-28 01:26:31 +01:00
@webrick . mount '/' , :: Rack :: Handler :: WEBrick , rack_app
2012-05-20 01:49:44 +02:00
end
2012-08-14 00:39:06 +02:00
2012-05-20 01:49:44 +02:00
# Detach the current Middleman::Application instance
# @return [void]
def unmount_instance
2013-12-28 01:26:31 +01:00
@webrick . unmount '/'
2012-05-20 01:49:44 +02:00
@app = nil
end
# Whether the passed files are config.rb, lib/*.rb or helpers
# @param [Array<String>] paths Array of paths to check
# @return [Boolean] Whether the server needs to reload
def needs_to_reload? ( paths )
2014-09-16 00:36:41 +02:00
relative_paths = paths . map do | p |
Pathname ( p ) . relative_path_from ( Pathname ( app . root ) ) . to_s
end
2012-09-13 19:13:57 +02:00
match_against = [
2014-09-16 00:36:41 +02:00
%r{ ^config \ .rb$ } ,
2013-03-22 18:22:16 +01:00
%r{ ^lib/[^ \ .](.*) \ .rb$ } ,
%r{ ^helpers/[^ \ .](.*) \ .rb$ }
2012-09-13 19:13:57 +02:00
]
2012-11-11 03:16:47 +01:00
2012-09-13 19:13:57 +02:00
if @options [ :reload_paths ]
@options [ :reload_paths ] . split ( ',' ) . each do | part |
match_against << %r{ ^ #{ part } }
end
end
2012-11-11 03:16:47 +01:00
2014-09-16 00:36:41 +02:00
relative_paths . any? do | path |
2012-09-13 19:13:57 +02:00
match_against . any? do | matcher |
2013-02-09 09:20:37 +01:00
path =~ matcher
2012-09-13 19:13:57 +02:00
end
2012-05-20 01:49:44 +02:00
end
end
2014-02-17 10:28:57 +01:00
# Returns the URI the preview server will run on
# @return [URI]
2015-05-06 00:56:08 +02:00
def uri ( host = @host )
2014-02-17 10:28:57 +01:00
scheme = https? ? 'https' : 'http'
2015-05-04 02:53:54 +02:00
URI ( " #{ scheme } :// #{ host } : #{ @port } / " )
end
# An IPv4 address on this machine which should be externally addressable.
# @return [String]
def public_ip
2015-05-04 03:48:31 +02:00
ip = Socket . ip_address_list . find { | ai | ai . ipv4? && ! ai . ipv4_loopback? }
ip ? ip . ip_address : '127.0.0.1'
2014-02-17 10:28:57 +01:00
end
2015-05-25 03:49:21 +02:00
# Returns unused TCP port
# @return [Fixnum]
def unused_tcp_port
server = TCPServer . open ( 0 )
port = server . addr [ 1 ]
server . close
port
end
2012-05-20 01:49:44 +02:00
end
2012-07-09 09:16:13 +02:00
class FilteredWebrickLog < :: WEBrick :: Log
def log ( level , data )
2014-07-02 19:11:52 +02:00
super ( level , data ) unless data =~ %r{ Could not determine content-length of response body. }
2012-07-09 09:16:13 +02:00
end
end
2012-05-20 01:49:44 +02:00
end
2012-05-20 08:05:06 +02:00
end