2013-12-28 01:26:31 +01:00
require 'webrick'
2015-05-03 05:54:58 +02:00
require 'webrick/https'
require 'openssl'
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'
2014-07-05 21:14:58 +02:00
require 'middleman-core/rack'
2015-06-25 15:39:56 +02:00
require 'middleman-core/preview_server/server_information'
require 'middleman-core/preview_server/server_url'
2015-11-07 18:51:19 +01:00
require 'middleman-core/preview_server/server_information_callback_proxy'
2012-07-09 09:16:13 +02:00
2014-07-02 20:05:57 +02:00
# rubocop:disable GlobalVars
2012-05-20 01:49:44 +02:00
module Middleman
2015-06-25 15:39:56 +02:00
class PreviewServer
2012-05-20 01:49:44 +02:00
class << self
2014-07-05 20:17:41 +02:00
extend Forwardable
2015-06-25 15:39:56 +02:00
attr_reader :app , :ssl_certificate , :ssl_private_key , :environment , :server_information
2012-08-14 00:39:06 +02:00
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 = { } )
2015-06-25 15:39:56 +02:00
# Do not buffer output, otherwise testing of output does not work
$stdout . sync = true
$stderr . sync = true
2012-09-13 19:13:57 +02:00
@options = opts
2015-06-25 15:39:56 +02:00
@server_information = ServerInformation . new
2015-11-07 18:50:55 +01:00
@server_information . https = ( @options [ :https ] == true )
2015-06-25 15:39:56 +02:00
# New app evaluates the middleman configuration. Since this can be
# invalid as well, we need to evaluate the configuration BEFORE
# checking for validity
2015-09-17 22:53:43 +02:00
the_app = initialize_new_app
2015-06-25 15:39:56 +02:00
# And now comes the check
unless server_information . valid?
2015-09-17 22:53:43 +02:00
$stderr . puts %( == Running Middleman failed: #{ server_information . reason } . Please fix that and try again. )
2015-06-25 15:39:56 +02:00
exit 1
end
2012-08-14 00:39:06 +02:00
2015-06-25 15:39:56 +02:00
mount_instance ( the_app )
2015-05-08 10:12:57 +02:00
2015-09-17 23:46:27 +02:00
app . logger . debug %( == Server information is provided by #{ server_information . handler } )
app . logger . debug %( == The Middleman is running in " #{ environment } " environment )
2015-11-07 18:50:55 +01:00
app . logger . debug format ( '== The Middleman preview server is bound to %s' , ServerUrl . new ( hosts : server_information . listeners , port : server_information . port , https : server_information . https? ) . to_bind_addresses . join ( ', ' ) )
app . logger . info format ( '== View your site at %s' , ServerUrl . new ( hosts : server_information . site_addresses , port : server_information . port , https : server_information . https? ) . to_urls . join ( ', ' ) )
app . logger . info format ( '== Inspect your site configuration at %s' , ServerUrl . new ( hosts : server_information . site_addresses , port : server_information . port , https : server_information . https? ) . to_config_urls . join ( ', ' ) )
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
2015-11-07 18:51:19 +01:00
app . execute_callbacks ( :before_server , [ ServerInformationCallbackProxy . new ( server_information ) ] )
2015-11-11 22:20:37 +01:00
if @options [ :daemon ]
# To output the child PID, let's make preview server a daemon by hand
if child_pid = fork
app . logger . info " == Middleman preview server is running in background with PID #{ child_pid } "
2015-11-27 23:16:55 +01:00
Process . detach child_pid
2015-11-11 22:20:37 +01:00
exit 0
else
$stdout . reopen ( '/dev/null' , 'w' )
$stderr . reopen ( '/dev/null' , 'w' )
$stdin . reopen ( '/dev/null' , 'r' )
end
end
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
2015-09-17 23:46:27 +02:00
app . 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-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
2015-09-17 23:46:27 +02:00
app . logger . info '== The Middleman is reloading'
2012-11-11 03:16:47 +01:00
2013-10-20 04:39:10 +02:00
begin
2015-09-17 22:53:43 +02:00
app = initialize_new_app
2014-04-29 19:50:21 +02:00
rescue = > e
2015-09-17 23:46:27 +02:00
$stderr . puts " Error reloading Middleman: #{ e } \n #{ e . backtrace . join ( " \n " ) } "
app . 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-02 22:48:47 +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
2015-09-17 23:46:27 +02:00
app . 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 19:50:21 +02:00
private
2014-04-29 19:44:24 +02:00
2015-09-17 22:53:43 +02:00
def initialize_new_app
2013-12-31 23:41:17 +01:00
opts = @options . dup
2014-07-05 22:41:59 +02:00
:: Middleman :: Logger . singleton (
opts [ :debug ] ? 0 : 1 ,
opts [ :instrumenting ] || false
)
2014-07-05 21:14:58 +02:00
app = :: Middleman :: Application . new do
config [ :environment ] = opts [ :environment ] . to_sym if opts [ :environment ]
2014-07-16 03:01:45 +02:00
config [ :watcher_disable ] = opts [ :disable_watcher ]
config [ :watcher_force_polling ] = opts [ :force_polling ]
config [ :watcher_latency ] = opts [ :latency ]
2015-03-02 03:17:22 +01:00
config [ :port ] = opts [ :port ] if opts [ :port ]
2015-06-25 15:39:56 +02:00
config [ :bind_address ] = opts [ :bind_address ]
config [ :server_name ] = opts [ :server_name ]
config [ :https ] = opts [ :https ] unless opts [ :https ] . nil?
2015-05-03 05:54:58 +02:00
config [ :ssl_certificate ] = opts [ :ssl_certificate ] if opts [ :ssl_certificate ]
config [ :ssl_private_key ] = opts [ :ssl_private_key ] if opts [ :ssl_private_key ]
2012-08-14 00:39:06 +02:00
2014-07-16 03:01:45 +02:00
ready do
match_against = [
%r{ ^config \ .rb$ } ,
%r{ ^environments/[^ \ .](.*) \ .rb$ } ,
%r{ ^lib/[^ \ .](.*) \ .rb$ } ,
2015-09-17 23:46:27 +02:00
%r{ ^ #{ config [ :helpers_dir ] } /[^ \ .](.*) \ .rb$ }
2014-07-16 03:01:45 +02:00
]
# config.rb
2015-08-17 23:28:15 +02:00
watcher = files . watch :reload ,
2015-09-17 18:41:17 +02:00
path : root ,
only : match_against
2015-08-17 23:28:15 +02:00
# Hack around node_modules in root.
watcher . listener . ignore ( / ^node_modules / )
2015-08-17 23:33:19 +02:00
# Hack around sass cache in root.
watcher . listener . ignore ( / ^ \ .sass-cache / )
2015-08-18 00:59:48 +02:00
# Hack around bundler cache in root.
watcher . listener . ignore ( / ^vendor \/ bundle / )
2014-07-16 03:01:45 +02:00
end
end
2015-09-17 22:53:43 +02:00
# store configured port to make a check later on possible
configured_port = app . config [ :port ]
2012-08-14 00:39:06 +02:00
2015-09-17 22:53:43 +02:00
# Use configuration values to set `bind_address` etc. in
# `server_information`
server_information . use app . config
2012-11-11 03:16:47 +01:00
2015-09-17 23:46:27 +02:00
app . 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
2012-11-11 03:16:47 +01:00
2015-11-27 23:16:55 +01:00
@environment = app . config [ :environment ]
2015-05-03 05:54:58 +02:00
2015-05-04 19:02:32 +02:00
@ssl_certificate = app . config [ :ssl_certificate ]
@ssl_private_key = app . config [ :ssl_private_key ]
2012-11-11 03:16:47 +01:00
2014-12-23 23:54:21 +01:00
app . files . on_change :reload do
2014-07-16 03:01:45 +02:00
$mm_reload = true
@webrick . stop
2014-07-05 21:14:58 +02:00
end
2013-04-13 07:48:56 +02:00
# Add in the meta pages application
2014-07-05 21:14:58 +02:00
meta_app = Middleman :: MetaPages :: Application . new ( app )
app . map '/__middleman' do
2013-04-13 07:48:56 +02:00
run meta_app
end
2014-07-05 21:14:58 +02:00
app
2012-09-13 19:13:57 +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
2014-07-02 20:05:57 +02:00
2014-07-02 19:11:52 +02:00
@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 = {
2015-06-25 15:39:56 +02:00
Port : server_information . port ,
2014-04-29 19:50:21 +02:00
AccessLog : [ ] ,
2015-06-25 15:39:56 +02:00
ServerName : server_information . server_name ,
BindAddress : server_information . bind_address . to_s ,
2014-04-29 19:50:21 +02:00
DoNotReverseLookup : true
2012-05-20 01:49:44 +02:00
}
2012-08-14 00:39:06 +02:00
2015-11-07 18:50:55 +01:00
if server_information . https?
2015-05-03 05:54:58 +02:00
http_opts [ :SSLEnable ] = true
if ssl_certificate || ssl_private_key
2015-05-04 02:11:49 +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
http_opts [ :SSLCertName ] = [
2015-05-04 02:11:49 +02:00
%w( CN localhost ) ,
%w( CN # {host} )
] . uniq
2015-06-25 15:39:56 +02:00
cert , key = create_self_signed_cert ( 1024 , [ [ 'CN' , server_information . server_name ] ] , server_information . site_addresses , '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
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-09-17 23:46:27 +02:00
$stderr . puts %( == Port " #{ http_opts [ :Port ] } " is in use. This should not have happened. Please start "middleman server" again. )
2012-09-13 19:13:57 +02:00
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.
2015-06-25 15:39:56 +02:00
def create_self_signed_cert ( bits , cn , aliases , comment )
2015-05-08 07:09:09 +02:00
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 )
2015-06-25 15:39:56 +02:00
cert . add_extension ef . create_extension ( 'subjectAltName' , aliases . map { | d | " DNS: #{ d } " } . join ( ',' ) )
2015-05-08 07:09:09 +02:00
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 )
2014-07-05 21:14:58 +02:00
rack_app = :: Middleman :: Rack . new ( @app ) . to_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
2014-07-16 03:01:45 +02:00
@app . shutdown!
2012-11-11 03:16:47 +01:00
2014-07-16 03:01:45 +02:00
@app = nil
2012-05-20 01:49:44 +02:00
end
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