Merge pull request #1314 from middleman/rack-out
Untangle Rack from Application
This commit is contained in:
commit
b02c9e5724
|
@ -53,6 +53,7 @@ module Middleman::Cli
|
||||||
|
|
||||||
require 'middleman-core'
|
require 'middleman-core'
|
||||||
require 'middleman-core/logger'
|
require 'middleman-core/logger'
|
||||||
|
require 'middleman-core/rack'
|
||||||
|
|
||||||
require 'rack'
|
require 'rack'
|
||||||
require 'rack/mock'
|
require 'rack/mock'
|
||||||
|
@ -66,7 +67,7 @@ module Middleman::Cli
|
||||||
verbose = options['verbose'] ? 0 : 1
|
verbose = options['verbose'] ? 0 : 1
|
||||||
instrument = options['instrument']
|
instrument = options['instrument']
|
||||||
|
|
||||||
app = ::Middleman::Application.server.inst do
|
app = ::Middleman::Application.new do
|
||||||
config[:mode] = :build
|
config[:mode] = :build
|
||||||
config[:environment] = env
|
config[:environment] = env
|
||||||
::Middleman::Logger.singleton(verbose, instrument)
|
::Middleman::Logger.singleton(verbose, instrument)
|
||||||
|
@ -118,7 +119,8 @@ module Middleman::Cli
|
||||||
@to_clean = Set.new
|
@to_clean = Set.new
|
||||||
|
|
||||||
@logger = @app.logger
|
@logger = @app.logger
|
||||||
@rack = ::Rack::MockRequest.new(@app.class.to_rack_app)
|
rack_app = ::Middleman::Rack.new(@app).to_app
|
||||||
|
@rack = ::Rack::MockRequest.new(rack_app)
|
||||||
|
|
||||||
super(base, @build_dir, config)
|
super(base, @build_dir, config)
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,21 +26,38 @@ require 'middleman-core/config_context'
|
||||||
require 'middleman-core/file_renderer'
|
require 'middleman-core/file_renderer'
|
||||||
require 'middleman-core/template_renderer'
|
require 'middleman-core/template_renderer'
|
||||||
|
|
||||||
# Rack Request
|
|
||||||
require 'middleman-core/core_extensions/request'
|
|
||||||
|
|
||||||
# Core Middleman Class
|
# Core Middleman Class
|
||||||
module Middleman
|
module Middleman
|
||||||
class Application
|
class Application
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
|
||||||
# Global configuration
|
class << self
|
||||||
include Configuration::Global
|
# Global configuration for the whole Middleman project.
|
||||||
|
# @return [ConfigurationManager]
|
||||||
|
def config
|
||||||
|
@config ||= ::Middleman::Configuration::ConfigurationManager.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# Root project directory (overwritten in middleman build/server)
|
||||||
|
# @return [String]
|
||||||
|
def root
|
||||||
|
ENV['MM_ROOT'] || Dir.pwd
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pathname-addressed root
|
||||||
|
def root_path
|
||||||
|
Pathname(root)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Uses callbacks
|
# Uses callbacks
|
||||||
include Hooks
|
include Hooks
|
||||||
include Hooks::InstanceHooks
|
include Hooks::InstanceHooks
|
||||||
|
|
||||||
|
define_hook :initialized
|
||||||
|
define_hook :after_configuration
|
||||||
|
define_hook :before_configuration
|
||||||
|
|
||||||
# Before request hook
|
# Before request hook
|
||||||
define_hook :before
|
define_hook :before
|
||||||
|
|
||||||
|
@ -56,19 +73,6 @@ module Middleman
|
||||||
define_hook :before_render
|
define_hook :before_render
|
||||||
define_hook :after_render
|
define_hook :after_render
|
||||||
|
|
||||||
# Root project directory (overwritten in middleman build/server)
|
|
||||||
# @return [String]
|
|
||||||
def self.root
|
|
||||||
ENV['MM_ROOT'] || Dir.pwd
|
|
||||||
end
|
|
||||||
def_delegator :"self.class", :root
|
|
||||||
|
|
||||||
# Pathname-addressed root
|
|
||||||
def self.root_path
|
|
||||||
Pathname(root)
|
|
||||||
end
|
|
||||||
def_delegator :"self.class", :root_path
|
|
||||||
|
|
||||||
# Name of the source directory
|
# Name of the source directory
|
||||||
# @return [String]
|
# @return [String]
|
||||||
config.define_setting :source, 'source', 'Name of the source directory'
|
config.define_setting :source, 'source', 'Name of the source directory'
|
||||||
|
@ -158,44 +162,33 @@ module Middleman
|
||||||
}
|
}
|
||||||
}, 'Callbacks that can exclude paths from the sitemap'
|
}, 'Callbacks that can exclude paths from the sitemap'
|
||||||
|
|
||||||
define_hook :initialized
|
|
||||||
define_hook :instance_available
|
|
||||||
define_hook :after_configuration
|
|
||||||
define_hook :before_configuration
|
|
||||||
|
|
||||||
config.define_setting :autoload_sprockets, true, 'Automatically load sprockets at startup?'
|
config.define_setting :autoload_sprockets, true, 'Automatically load sprockets at startup?'
|
||||||
config[:autoload_sprockets] = (ENV['AUTOLOAD_SPROCKETS'] == 'true') if ENV['AUTOLOAD_SPROCKETS']
|
config[:autoload_sprockets] = (ENV['AUTOLOAD_SPROCKETS'] == 'true') if ENV['AUTOLOAD_SPROCKETS']
|
||||||
|
|
||||||
# Basic Rack Request Handling
|
attr_reader :config_context
|
||||||
include Middleman::CoreExtensions::Request
|
attr_reader :sitemap
|
||||||
|
attr_reader :cache
|
||||||
|
attr_reader :template_context_class
|
||||||
|
attr_reader :config
|
||||||
|
attr_reader :generic_template_context
|
||||||
|
attr_reader :extensions
|
||||||
|
attr_reader :middleware
|
||||||
|
attr_reader :mappings
|
||||||
|
|
||||||
# Reference to Logger singleton
|
# Reference to Logger singleton
|
||||||
def_delegator :"::Middleman::Logger", :singleton, :logger
|
def_delegator :"::Middleman::Logger", :singleton, :logger
|
||||||
|
def_delegator :"::Middleman::Util", :instrument
|
||||||
# New container for config.rb commands
|
def_delegators :"self.class", :root, :root_path
|
||||||
attr_reader :config_context
|
|
||||||
|
|
||||||
# Reference to Sitemap
|
|
||||||
attr_reader :sitemap
|
|
||||||
|
|
||||||
# Template cache
|
|
||||||
attr_reader :cache
|
|
||||||
|
|
||||||
attr_reader :template_context_class
|
|
||||||
|
|
||||||
# Hack to get a sandboxed copy of these helpers for overriding similar methods inside Markdown renderers.
|
|
||||||
attr_reader :generic_template_context
|
|
||||||
def_delegators :@generic_template_context, :link_to, :image_tag, :asset_path
|
def_delegators :@generic_template_context, :link_to, :image_tag, :asset_path
|
||||||
|
|
||||||
attr_reader :extensions
|
|
||||||
|
|
||||||
# Initialize the Middleman project
|
# Initialize the Middleman project
|
||||||
def initialize(&block)
|
def initialize(&block)
|
||||||
self.class.inst = self
|
|
||||||
|
|
||||||
# Search the root of the project for required files
|
# Search the root of the project for required files
|
||||||
$LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
|
$LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
|
||||||
|
|
||||||
|
@middleware = []
|
||||||
|
@mappings = []
|
||||||
|
|
||||||
@template_context_class = Class.new(Middleman::TemplateContext)
|
@template_context_class = Class.new(Middleman::TemplateContext)
|
||||||
@generic_template_context = @template_context_class.new(self)
|
@generic_template_context = @template_context_class.new(self)
|
||||||
@config_context = ConfigContext.new(self, @template_context_class)
|
@config_context = ConfigContext.new(self, @template_context_class)
|
||||||
|
@ -204,7 +197,8 @@ module Middleman
|
||||||
::Middleman::TemplateRenderer.cache.clear
|
::Middleman::TemplateRenderer.cache.clear
|
||||||
|
|
||||||
# Setup the default values from calls to set before initialization
|
# Setup the default values from calls to set before initialization
|
||||||
self.class.config.load_settings(self.class.superclass.config.all_settings)
|
@config = ::Middleman::Configuration::ConfigurationManager.new
|
||||||
|
@config.load_settings(self.class.config.all_settings)
|
||||||
|
|
||||||
@extensions = ::Middleman::ExtensionManager.new(self)
|
@extensions = ::Middleman::ExtensionManager.new(self)
|
||||||
@extensions.auto_activate(:before_sitemap)
|
@extensions.auto_activate(:before_sitemap)
|
||||||
|
@ -238,8 +232,6 @@ module Middleman
|
||||||
|
|
||||||
evaluate_configuration(&block)
|
evaluate_configuration(&block)
|
||||||
|
|
||||||
run_hook :instance_available
|
|
||||||
|
|
||||||
# This is for making the tests work - since the tests
|
# This is for making the tests work - since the tests
|
||||||
# don't completely reload middleman, I18n.load_path can get
|
# don't completely reload middleman, I18n.load_path can get
|
||||||
# polluted with paths from other test app directories that don't
|
# polluted with paths from other test app directories that don't
|
||||||
|
@ -262,6 +254,9 @@ module Middleman
|
||||||
config_context.execute_after_configuration_callbacks
|
config_context.execute_after_configuration_callbacks
|
||||||
|
|
||||||
@extensions.activate_all
|
@extensions.activate_all
|
||||||
|
|
||||||
|
run_hook :ready
|
||||||
|
@config_context.execute_ready_callbacks
|
||||||
end
|
end
|
||||||
|
|
||||||
def evaluate_configuration(&block)
|
def evaluate_configuration(&block)
|
||||||
|
@ -317,7 +312,21 @@ module Middleman
|
||||||
File.join(root, config[:source])
|
File.join(root, config[:source])
|
||||||
end
|
end
|
||||||
|
|
||||||
def_delegator ::Middleman::Util, :instrument
|
# Use Rack middleware
|
||||||
|
#
|
||||||
|
# @param [Class] middleware Middleware module
|
||||||
|
# @return [void]
|
||||||
|
def use(middleware, *args, &block)
|
||||||
|
@middleware << [middleware, args, block]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add Rack App mapped to specific path
|
||||||
|
#
|
||||||
|
# @param [String] map Path to map
|
||||||
|
# @return [void]
|
||||||
|
def map(map, &block)
|
||||||
|
@mappings << [map, block]
|
||||||
|
end
|
||||||
|
|
||||||
# Work around this bug: http://bugs.ruby-lang.org/issues/4521
|
# Work around this bug: http://bugs.ruby-lang.org/issues/4521
|
||||||
# where Ruby will call to_s/inspect while printing exception
|
# where Ruby will call to_s/inspect while printing exception
|
||||||
|
@ -328,9 +337,5 @@ module Middleman
|
||||||
end
|
end
|
||||||
alias_method :inspect, :to_s # Ruby 2.0 calls inspect for NoMethodError instead of to_s
|
alias_method :inspect, :to_s # Ruby 2.0 calls inspect for NoMethodError instead of to_s
|
||||||
|
|
||||||
# Hooks clones _hooks from the class to the instance.
|
|
||||||
# https://github.com/apotonick/hooks/blob/master/lib/hooks/instance_hooks.rb#L10
|
|
||||||
# Middleman expects the same list of hooks for class and instance hooks:
|
|
||||||
def_delegator :"self.class", :_hooks
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'rack/mime'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
class ConfigContext
|
class ConfigContext
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
@ -87,5 +89,15 @@ module Middleman
|
||||||
config.define_setting(key, default) unless config.defines_setting?(key)
|
config.define_setting(key, default) unless config.defines_setting?(key)
|
||||||
@app.config[key] = block_given? ? block : default
|
@app.config[key] = block_given? ? block : default
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add a new mime-type for a specific extension
|
||||||
|
#
|
||||||
|
# @param [Symbol] type File extension
|
||||||
|
# @param [String] value Mime type
|
||||||
|
# @return [void]
|
||||||
|
def mime_type(type, value)
|
||||||
|
type = ".#{type}" unless type.to_s[0] == '.'
|
||||||
|
::Rack::Mime::MIME_TYPES[type] = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +1,5 @@
|
||||||
module Middleman
|
module Middleman
|
||||||
module Configuration
|
module Configuration
|
||||||
# Access to a global configuration manager for the whole Middleman project,
|
|
||||||
# plus backwards compatibility mechanisms for older Middleman projects.
|
|
||||||
module Global
|
|
||||||
def self.included(app)
|
|
||||||
app.send :extend, ClassMethods
|
|
||||||
app.send :def_delegator, :"self.class", :config
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Global configuration for the whole Middleman project.
|
|
||||||
# @return [ConfigurationManager]
|
|
||||||
def config
|
|
||||||
@_config ||= ConfigurationManager.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# A class that manages a collection of documented settings.
|
# A class that manages a collection of documented settings.
|
||||||
# Can be used by extensions as well as the main Middleman
|
# Can be used by extensions as well as the main Middleman
|
||||||
|
|
|
@ -1,263 +0,0 @@
|
||||||
# Built on Rack
|
|
||||||
require 'rack'
|
|
||||||
require 'rack/file'
|
|
||||||
require 'rack/lint'
|
|
||||||
require 'rack/head'
|
|
||||||
|
|
||||||
require 'middleman-core/util'
|
|
||||||
require 'middleman-core/template_renderer'
|
|
||||||
|
|
||||||
module Middleman
|
|
||||||
module CoreExtensions
|
|
||||||
# Base helper to manipulate asset paths
|
|
||||||
module Request
|
|
||||||
# Extension registered
|
|
||||||
class << self
|
|
||||||
# @private
|
|
||||||
def included(app)
|
|
||||||
# CSSPIE HTC File
|
|
||||||
::Rack::Mime::MIME_TYPES['.htc'] = 'text/x-component'
|
|
||||||
|
|
||||||
# Let's serve all HTML as UTF-8
|
|
||||||
::Rack::Mime::MIME_TYPES['.html'] = 'text/html; charset=utf-8'
|
|
||||||
::Rack::Mime::MIME_TYPES['.htm'] = 'text/html; charset=utf-8'
|
|
||||||
|
|
||||||
app.extend ClassMethods
|
|
||||||
app.extend ServerMethods
|
|
||||||
|
|
||||||
Middleman.extend CompatibleClassMethods
|
|
||||||
|
|
||||||
# Include instance methods
|
|
||||||
app.send :include, InstanceMethods
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Reset Rack setup
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
def reset!
|
|
||||||
@rack_app = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the static instance
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
# @return [Middleman::Application]
|
|
||||||
def inst(&block)
|
|
||||||
@inst ||= begin
|
|
||||||
mm = new(&block)
|
|
||||||
mm.run_hook :ready
|
|
||||||
mm.config_context.execute_ready_callbacks
|
|
||||||
mm
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set the shared instance
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
attr_writer :inst
|
|
||||||
|
|
||||||
# Return built Rack app
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
# @return [Rack::Builder]
|
|
||||||
def to_rack_app(&block)
|
|
||||||
@rack_app ||= begin
|
|
||||||
app = ::Rack::Builder.new
|
|
||||||
app.use Rack::Lint
|
|
||||||
app.use Rack::Head
|
|
||||||
|
|
||||||
Array(@middleware).each do |klass, options, middleware_block|
|
|
||||||
app.use(klass, *options, &middleware_block)
|
|
||||||
end
|
|
||||||
|
|
||||||
inner_app = inst(&block)
|
|
||||||
app.map('/') { run inner_app }
|
|
||||||
|
|
||||||
Array(@mappings).each do |path, map_block|
|
|
||||||
app.map(path, &map_block)
|
|
||||||
end
|
|
||||||
|
|
||||||
app
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Prototype app. Used in config.ru
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
# @return [Rack::Builder]
|
|
||||||
def prototype
|
|
||||||
reset!
|
|
||||||
to_rack_app
|
|
||||||
end
|
|
||||||
|
|
||||||
# Call prototype, use in config.ru
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
def call(env)
|
|
||||||
prototype.call(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use Rack middleware
|
|
||||||
#
|
|
||||||
# @param [Class] middleware Middleware module
|
|
||||||
# @return [void]
|
|
||||||
def use(middleware, *args, &block)
|
|
||||||
@middleware ||= []
|
|
||||||
@middleware << [middleware, args, block]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add Rack App mapped to specific path
|
|
||||||
#
|
|
||||||
# @param [String] map Path to map
|
|
||||||
# @return [void]
|
|
||||||
def map(map, &block)
|
|
||||||
@mappings ||= []
|
|
||||||
@mappings << [map, block]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ServerMethods
|
|
||||||
# Create a new Class which is based on Middleman::Application
|
|
||||||
# Used to create a safe sandbox into which extensions and
|
|
||||||
# configuration can be included later without impacting
|
|
||||||
# other classes and instances.
|
|
||||||
#
|
|
||||||
# @return [Class]
|
|
||||||
def server(&block)
|
|
||||||
@servercounter ||= 0
|
|
||||||
@servercounter += 1
|
|
||||||
const_set("MiddlemanApplication#{@servercounter}", Class.new(Middleman::Application, &block))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module CompatibleClassMethods
|
|
||||||
# Create a new Class which is based on Middleman::Application
|
|
||||||
# Used to create a safe sandbox into which extensions and
|
|
||||||
# configuration can be included later without impacting
|
|
||||||
# other classes and instances.
|
|
||||||
#
|
|
||||||
# @return [Class]
|
|
||||||
def server(&block)
|
|
||||||
::Middleman::Application.server(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Methods to be mixed-in to Middleman::Application
|
|
||||||
module InstanceMethods
|
|
||||||
def self.included(app)
|
|
||||||
app.send :def_delegators, :"self.class", :use, :map
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
dup.call!(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Rack Interface
|
|
||||||
#
|
|
||||||
# @param env Rack environment
|
|
||||||
def call!(env)
|
|
||||||
# Store environment, request and response for later
|
|
||||||
req = ::Rack::Request.new(env)
|
|
||||||
res = ::Rack::Response.new
|
|
||||||
|
|
||||||
logger.debug "== Request: #{env['PATH_INFO']}"
|
|
||||||
|
|
||||||
# Catch :halt exceptions and use that response if given
|
|
||||||
catch(:halt) do
|
|
||||||
process_request(env, req, res)
|
|
||||||
|
|
||||||
res.status = 404
|
|
||||||
|
|
||||||
res.finish
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Halt the current request and return a response
|
|
||||||
#
|
|
||||||
# @param [String] response Response value
|
|
||||||
def halt(response)
|
|
||||||
throw :halt, response
|
|
||||||
end
|
|
||||||
|
|
||||||
# Core response method. We process the request, check with
|
|
||||||
# the sitemap, and return the correct file, response or status
|
|
||||||
# message.
|
|
||||||
#
|
|
||||||
# @param env
|
|
||||||
# @param [Rack::Response] res
|
|
||||||
def process_request(env, _, res)
|
|
||||||
start_time = Time.now
|
|
||||||
|
|
||||||
request_path = URI.decode(env['PATH_INFO'].dup)
|
|
||||||
if request_path.respond_to? :force_encoding
|
|
||||||
request_path.force_encoding('UTF-8')
|
|
||||||
end
|
|
||||||
request_path = ::Middleman::Util.full_path(request_path, self)
|
|
||||||
|
|
||||||
# Run before callbacks
|
|
||||||
run_hook :before
|
|
||||||
|
|
||||||
# Get the resource object for this path
|
|
||||||
resource = sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))
|
|
||||||
|
|
||||||
# Return 404 if not in sitemap
|
|
||||||
return not_found(res, request_path) unless resource && !resource.ignored?
|
|
||||||
|
|
||||||
# If this path is a binary file, send it immediately
|
|
||||||
return send_file(resource, env) if resource.binary?
|
|
||||||
|
|
||||||
res['Content-Type'] = resource.content_type || 'text/plain'
|
|
||||||
|
|
||||||
begin
|
|
||||||
# Write out the contents of the page
|
|
||||||
res.write resource.render
|
|
||||||
|
|
||||||
# Valid content is a 200 status
|
|
||||||
res.status = 200
|
|
||||||
rescue Middleman::TemplateRenderer::TemplateNotFound => e
|
|
||||||
res.write "Error: #{e.message}"
|
|
||||||
res.status = 500
|
|
||||||
end
|
|
||||||
|
|
||||||
# End the request
|
|
||||||
logger.debug "== Finishing Request: #{resource.destination_path} (#{(Time.now - start_time).round(2)}s)"
|
|
||||||
halt res.finish
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add a new mime-type for a specific extension
|
|
||||||
#
|
|
||||||
# @param [Symbol] type File extension
|
|
||||||
# @param [String] value Mime type
|
|
||||||
# @return [void]
|
|
||||||
def mime_type(type, value)
|
|
||||||
type = ".#{type}" unless type.to_s[0] == '.'
|
|
||||||
::Rack::Mime::MIME_TYPES[type] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
# Halt request and return 404
|
|
||||||
def not_found(res, path)
|
|
||||||
res.status = 404
|
|
||||||
res.write "<html><head></head><body><h1>File Not Found</h1><p>#{path}</p></body></html>"
|
|
||||||
res.finish
|
|
||||||
end
|
|
||||||
|
|
||||||
# Immediately send static file
|
|
||||||
def send_file(resource, env)
|
|
||||||
file = ::Rack::File.new nil
|
|
||||||
file.path = resource.source_file
|
|
||||||
response = file.serving(env)
|
|
||||||
status = response[0]
|
|
||||||
response[1]['Content-Encoding'] = 'gzip' if %w(.svgz .gz).include?(resource.ext)
|
|
||||||
# Do not set Content-Type if status is 1xx, 204, 205 or 304, otherwise
|
|
||||||
# Rack will throw an error (500)
|
|
||||||
if !(100..199).include?(status) && ![204, 205, 304].include?(status)
|
|
||||||
response[1]['Content-Type'] = resource.content_type || 'application/octet-stream'
|
|
||||||
end
|
|
||||||
halt response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -53,7 +53,6 @@ module Middleman
|
||||||
# * {#after_configuration}
|
# * {#after_configuration}
|
||||||
# * {#after_build}
|
# * {#after_build}
|
||||||
# * {#before_build}
|
# * {#before_build}
|
||||||
# * {#instance_available}
|
|
||||||
#
|
#
|
||||||
# There are also some less common hooks that can be listened to from within an extension's `initialize` method:
|
# There are also some less common hooks that can be listened to from within an extension's `initialize` method:
|
||||||
#
|
#
|
||||||
|
@ -181,16 +180,15 @@ module Middleman
|
||||||
def_delegator :"@app.extensions[:file_watcher]", :api, :file_watcher
|
def_delegator :"@app.extensions[:file_watcher]", :api, :file_watcher
|
||||||
|
|
||||||
# Extensions are instantiated when they are activated.
|
# Extensions are instantiated when they are activated.
|
||||||
# @param [Class] klass The Middleman::Application class
|
# @param [Middleman::Application] app The Middleman::Application instance
|
||||||
# @param [Hash] options_hash The raw options hash. Subclasses should not manipulate this directly - it will be turned into {#options}.
|
# @param [Hash] options_hash The raw options hash. Subclasses should not manipulate this directly - it will be turned into {#options}.
|
||||||
# @yield An optional block that can be used to customize options before the extension is activated.
|
# @yield An optional block that can be used to customize options before the extension is activated.
|
||||||
# @yieldparam [Middleman::Configuration::ConfigurationManager] options Extension options
|
# @yieldparam [Middleman::Configuration::ConfigurationManager] options Extension options
|
||||||
def initialize(klass, options_hash={}, &block)
|
def initialize(app, options_hash={}, &block)
|
||||||
@_helpers = []
|
@_helpers = []
|
||||||
@klass = klass
|
@app = app
|
||||||
|
|
||||||
setup_options(options_hash, &block)
|
setup_options(options_hash, &block)
|
||||||
setup_app_reference_when_available
|
|
||||||
|
|
||||||
# Bind app hooks to local methods
|
# Bind app hooks to local methods
|
||||||
bind_before_configuration
|
bind_before_configuration
|
||||||
|
@ -216,10 +214,6 @@ module Middleman
|
||||||
# Respond to the `after_build` event.
|
# Respond to the `after_build` event.
|
||||||
# If an `after_build` method is implemented, that method will be run after the builder runs.
|
# If an `after_build` method is implemented, that method will be run after the builder runs.
|
||||||
|
|
||||||
# @!method instance_available
|
|
||||||
# Respond to the `instance_available` event.
|
|
||||||
# If an `instance_available` method is implemented, that method will be run after `config.rb` is run and after environment-specific config blocks have been run, but before any `after_configuration` callbacks.
|
|
||||||
|
|
||||||
# @!method manipulate_resource_list(resources)
|
# @!method manipulate_resource_list(resources)
|
||||||
# Manipulate the resource list by transforming or adding {Sitemap::Resource}s.
|
# Manipulate the resource list by transforming or adding {Sitemap::Resource}s.
|
||||||
# Sitemap manipulation is a powerful way of interacting with a project, since it can modify each {Sitemap::Resource} or generate new {Sitemap::Resources}. This method is used in a pipeline where each sitemap manipulator is run in turn, with each one being fed the output of the previous manipulator. See the source of built-in Middleman extensions like {Middleman::Extensions::DirectoryIndexes} and {Middleman::Extensions::AssetHash} for examples of how to use this.
|
# Sitemap manipulation is a powerful way of interacting with a project, since it can modify each {Sitemap::Resource} or generate new {Sitemap::Resources}. This method is used in a pipeline where each sitemap manipulator is run in turn, with each one being fed the output of the previous manipulator. See the source of built-in Middleman extensions like {Middleman::Extensions::DirectoryIndexes} and {Middleman::Extensions::AssetHash} for examples of how to use this.
|
||||||
|
@ -230,20 +224,6 @@ module Middleman
|
||||||
# @param [Array<Sitemap::Resource>] resources A list of all the resources known to the sitemap.
|
# @param [Array<Sitemap::Resource>] resources A list of all the resources known to the sitemap.
|
||||||
# @return [Array<Sitemap::Resource>] The transformed list of resources.
|
# @return [Array<Sitemap::Resource>] The transformed list of resources.
|
||||||
|
|
||||||
# Assign the app instance. Used internally.
|
|
||||||
# @api private
|
|
||||||
def app=(app)
|
|
||||||
@app = app
|
|
||||||
|
|
||||||
ext = self
|
|
||||||
|
|
||||||
return unless ext.respond_to?(:instance_available)
|
|
||||||
|
|
||||||
@klass.instance_available do
|
|
||||||
ext.instance_available
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# @yield An optional block that can be used to customize options before the extension is activated.
|
# @yield An optional block that can be used to customize options before the extension is activated.
|
||||||
|
@ -259,30 +239,14 @@ module Middleman
|
||||||
yield @options if block_given?
|
yield @options if block_given?
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_app_reference_when_available
|
|
||||||
ext = self
|
|
||||||
|
|
||||||
@klass.initialized do
|
|
||||||
ext.app = self
|
|
||||||
end
|
|
||||||
|
|
||||||
@klass.instance_available do
|
|
||||||
ext.app ||= self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bind_before_configuration
|
def bind_before_configuration
|
||||||
ext = self
|
return unless respond_to?(:before_configuration)
|
||||||
return unless ext.respond_to?(:before_configuration)
|
@app.before_configuration(&method(:before_configuration))
|
||||||
|
|
||||||
@klass.before_configuration do
|
|
||||||
ext.before_configuration
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def bind_after_configuration
|
def bind_after_configuration
|
||||||
ext = self
|
ext = self
|
||||||
@klass.after_configuration do
|
@app.after_configuration do
|
||||||
ext.after_configuration if ext.respond_to?(:after_configuration)
|
ext.after_configuration if ext.respond_to?(:after_configuration)
|
||||||
|
|
||||||
if ext.respond_to?(:manipulate_resource_list)
|
if ext.respond_to?(:manipulate_resource_list)
|
||||||
|
@ -295,7 +259,7 @@ module Middleman
|
||||||
ext = self
|
ext = self
|
||||||
return unless ext.respond_to?(:before_build)
|
return unless ext.respond_to?(:before_build)
|
||||||
|
|
||||||
@klass.before_build do |builder|
|
@app.before_build do |builder|
|
||||||
if ext.method(:before_build).arity == 1
|
if ext.method(:before_build).arity == 1
|
||||||
ext.before_build(builder)
|
ext.before_build(builder)
|
||||||
else
|
else
|
||||||
|
@ -308,7 +272,7 @@ module Middleman
|
||||||
ext = self
|
ext = self
|
||||||
return unless ext.respond_to?(:after_build)
|
return unless ext.respond_to?(:after_build)
|
||||||
|
|
||||||
@klass.after_build do |builder|
|
@app.after_build do |builder|
|
||||||
if ext.method(:after_build).arity == 1
|
if ext.method(:after_build).arity == 1
|
||||||
ext.after_build(builder)
|
ext.after_build(builder)
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module Middleman
|
module Middleman
|
||||||
class ExtensionManager
|
class ExtensionManager
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
|
||||||
def_delegator :@app, :logger
|
def_delegator :@app, :logger
|
||||||
def_delegators :@activated, :[]
|
def_delegators :@activated, :[]
|
||||||
|
|
||||||
|
@ -44,11 +45,11 @@ module Middleman
|
||||||
if extension.supports_multiple_instances?
|
if extension.supports_multiple_instances?
|
||||||
@activated[ext_name] ||= {}
|
@activated[ext_name] ||= {}
|
||||||
key = "instance_#{@activated[ext_name].keys.length}"
|
key = "instance_#{@activated[ext_name].keys.length}"
|
||||||
@activated[ext_name][key] = extension.new(@app.class, options, &block)
|
@activated[ext_name][key] = extension.new(@app, options, &block)
|
||||||
elsif @activated.key?(ext_name)
|
elsif @activated.key?(ext_name)
|
||||||
raise "#{ext_name} has already been activated and cannot be re-activated."
|
raise "#{ext_name} has already been activated and cannot be re-activated."
|
||||||
else
|
else
|
||||||
@activated[ext_name] = extension.new(@app.class, options, &block)
|
@activated[ext_name] = extension.new(@app, options, &block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,10 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
@rack_client = ::Rack::MockRequest.new(app.class.to_rack_app)
|
@rack_client ||= begin
|
||||||
|
rack_app = ::Middleman::Rack.new(app).to_app
|
||||||
|
::Rack::MockRequest.new(rack_app)
|
||||||
|
end
|
||||||
|
|
||||||
# Process resources in order: binary images and fonts, then SVG, then JS/CSS.
|
# Process resources in order: binary images and fonts, then SVG, then JS/CSS.
|
||||||
# This is so by the time we get around to the text files (which may reference
|
# This is so by the time we get around to the text files (which may reference
|
||||||
|
|
|
@ -17,9 +17,9 @@ module Middleman
|
||||||
@middleman = middleman
|
@middleman = middleman
|
||||||
|
|
||||||
meta_pages = self
|
meta_pages = self
|
||||||
@rack_app = Rack::Builder.new do
|
@rack_app = ::Rack::Builder.new do
|
||||||
# Serve assets from metadata/assets
|
# Serve assets from metadata/assets
|
||||||
use Rack::Static, urls: ['/assets'], root: File.join(File.dirname(__FILE__), 'meta_pages')
|
use ::Rack::Static, urls: ['/assets'], root: File.join(File.dirname(__FILE__), 'meta_pages')
|
||||||
|
|
||||||
map '/' do
|
map '/' do
|
||||||
run meta_pages.method(:index)
|
run meta_pages.method(:index)
|
||||||
|
@ -46,7 +46,7 @@ module Middleman
|
||||||
|
|
||||||
# Inspect the sitemap
|
# Inspect the sitemap
|
||||||
def sitemap(_)
|
def sitemap(_)
|
||||||
resources = @middleman.inst.sitemap.resources(true)
|
resources = @middleman.sitemap.resources(true)
|
||||||
|
|
||||||
sitemap_tree = SitemapTree.new
|
sitemap_tree = SitemapTree.new
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ module Middleman
|
||||||
|
|
||||||
# Inspect configuration
|
# Inspect configuration
|
||||||
def config(_)
|
def config(_)
|
||||||
global_config = @middleman.inst.config.all_settings.map { |c| ConfigSetting.new(c) }
|
global_config = @middleman.config.all_settings.map { |c| ConfigSetting.new(c) }
|
||||||
extension_config = {}
|
extension_config = {}
|
||||||
|
|
||||||
@middleman.inst.extensions.each do |ext_name, extension|
|
@middleman.extensions.each do |ext_name, extension|
|
||||||
next if ::Middleman::Extension.auto_activated.include? ext_name
|
next if ::Middleman::Extension.auto_activated.include? ext_name
|
||||||
|
|
||||||
if extension.is_a?(Hash)
|
if extension.is_a?(Hash)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
require 'webrick'
|
require 'webrick'
|
||||||
require 'middleman-core/meta_pages'
|
require 'middleman-core/meta_pages'
|
||||||
require 'middleman-core/logger'
|
require 'middleman-core/logger'
|
||||||
|
require 'middleman-core/rack'
|
||||||
|
|
||||||
# rubocop:disable GlobalVars
|
# rubocop:disable GlobalVars
|
||||||
module Middleman
|
module Middleman
|
||||||
|
@ -100,17 +101,17 @@ module Middleman
|
||||||
opts[:instrumenting] || false
|
opts[:instrumenting] || false
|
||||||
)
|
)
|
||||||
|
|
||||||
server = ::Middleman::Application.server
|
app = ::Middleman::Application.new do
|
||||||
|
config[:environment] = opts[:environment].to_sym if opts[:environment]
|
||||||
|
end
|
||||||
|
|
||||||
# Add in the meta pages application
|
# Add in the meta pages application
|
||||||
meta_app = Middleman::MetaPages::Application.new(server)
|
meta_app = Middleman::MetaPages::Application.new(app)
|
||||||
server.map '/__middleman' do
|
app.map '/__middleman' do
|
||||||
run meta_app
|
run meta_app
|
||||||
end
|
end
|
||||||
|
|
||||||
@app = server.inst do
|
app
|
||||||
config[:environment] = opts[:environment].to_sym if opts[:environment]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_file_watcher
|
def start_file_watcher
|
||||||
|
@ -197,7 +198,7 @@ module Middleman
|
||||||
|
|
||||||
start_file_watcher
|
start_file_watcher
|
||||||
|
|
||||||
rack_app = app.class.to_rack_app
|
rack_app = ::Middleman::Rack.new(@app).to_app
|
||||||
@webrick.mount '/', ::Rack::Handler::WEBrick, rack_app
|
@webrick.mount '/', ::Rack::Handler::WEBrick, rack_app
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
138
middleman-core/lib/middleman-core/rack.rb
Normal file
138
middleman-core/lib/middleman-core/rack.rb
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
require 'rack'
|
||||||
|
require 'rack/file'
|
||||||
|
require 'rack/lint'
|
||||||
|
require 'rack/head'
|
||||||
|
|
||||||
|
require 'middleman-core/util'
|
||||||
|
require 'middleman-core/template_renderer'
|
||||||
|
|
||||||
|
# CSSPIE HTC File
|
||||||
|
::Rack::Mime::MIME_TYPES['.htc'] = 'text/x-component'
|
||||||
|
|
||||||
|
# Let's serve all HTML as UTF-8
|
||||||
|
::Rack::Mime::MIME_TYPES['.html'] = 'text/html; charset=utf-8'
|
||||||
|
::Rack::Mime::MIME_TYPES['.htm'] = 'text/html; charset=utf-8'
|
||||||
|
|
||||||
|
module Middleman
|
||||||
|
class Rack
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def to_app
|
||||||
|
app = ::Rack::Builder.new
|
||||||
|
|
||||||
|
app.use ::Rack::Lint
|
||||||
|
app.use ::Rack::Head
|
||||||
|
|
||||||
|
@middleman.middleware.each do |klass, options, middleware_block|
|
||||||
|
app.use(klass, *options, &middleware_block)
|
||||||
|
end
|
||||||
|
|
||||||
|
inner_app = self
|
||||||
|
app.map('/') { run inner_app }
|
||||||
|
|
||||||
|
@middleman.mappings.each do |path, map_block|
|
||||||
|
app.map(path, &map_block)
|
||||||
|
end
|
||||||
|
|
||||||
|
app
|
||||||
|
end
|
||||||
|
|
||||||
|
def_delegator :"::Middleman::Logger", :singleton, :logger
|
||||||
|
|
||||||
|
def initialize(middleman)
|
||||||
|
@middleman = middleman
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rack Interface
|
||||||
|
#
|
||||||
|
# @param env Rack environment
|
||||||
|
def call(env)
|
||||||
|
# Store environment, request and response for later
|
||||||
|
req = ::Rack::Request.new(env)
|
||||||
|
res = ::Rack::Response.new
|
||||||
|
|
||||||
|
logger.debug "== Request: #{env['PATH_INFO']}"
|
||||||
|
|
||||||
|
# Catch :halt exceptions and use that response if given
|
||||||
|
catch(:halt) do
|
||||||
|
process_request(env, req, res)
|
||||||
|
res.status = 404
|
||||||
|
res.finish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Halt the current request and return a response
|
||||||
|
#
|
||||||
|
# @param [String] response Response value
|
||||||
|
def halt(response)
|
||||||
|
throw :halt, response
|
||||||
|
end
|
||||||
|
|
||||||
|
# Core response method. We process the request, check with
|
||||||
|
# the sitemap, and return the correct file, response or status
|
||||||
|
# message.
|
||||||
|
#
|
||||||
|
# @param env
|
||||||
|
# @param [Rack::Response] res
|
||||||
|
def process_request(env, _, res)
|
||||||
|
start_time = Time.now
|
||||||
|
|
||||||
|
request_path = URI.decode(env['PATH_INFO'].dup)
|
||||||
|
if request_path.respond_to? :force_encoding
|
||||||
|
request_path.force_encoding('UTF-8')
|
||||||
|
end
|
||||||
|
request_path = ::Middleman::Util.full_path(request_path, @middleman)
|
||||||
|
|
||||||
|
# Run before callbacks
|
||||||
|
@middleman.run_hook :before
|
||||||
|
|
||||||
|
# Get the resource object for this path
|
||||||
|
resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))
|
||||||
|
|
||||||
|
# Return 404 if not in sitemap
|
||||||
|
return not_found(res, request_path) unless resource && !resource.ignored?
|
||||||
|
|
||||||
|
# If this path is a binary file, send it immediately
|
||||||
|
return send_file(resource, env) if resource.binary?
|
||||||
|
|
||||||
|
res['Content-Type'] = resource.content_type || 'text/plain'
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Write out the contents of the page
|
||||||
|
res.write resource.render
|
||||||
|
|
||||||
|
# Valid content is a 200 status
|
||||||
|
res.status = 200
|
||||||
|
rescue Middleman::TemplateRenderer::TemplateNotFound => e
|
||||||
|
res.write "Error: #{e.message}"
|
||||||
|
res.status = 500
|
||||||
|
end
|
||||||
|
|
||||||
|
# End the request
|
||||||
|
logger.debug "== Finishing Request: #{resource.destination_path} (#{(Time.now - start_time).round(2)}s)"
|
||||||
|
halt res.finish
|
||||||
|
end
|
||||||
|
|
||||||
|
# Halt request and return 404
|
||||||
|
def not_found(res, path)
|
||||||
|
res.status = 404
|
||||||
|
res.write "<html><head></head><body><h1>File Not Found</h1><p>#{path}</p></body></html>"
|
||||||
|
res.finish
|
||||||
|
end
|
||||||
|
|
||||||
|
# Immediately send static file
|
||||||
|
def send_file(resource, env)
|
||||||
|
file = ::Rack::File.new nil
|
||||||
|
file.path = resource.source_file
|
||||||
|
response = file.serving(env)
|
||||||
|
status = response[0]
|
||||||
|
response[1]['Content-Encoding'] = 'gzip' if %w(.svgz .gz).include?(resource.ext)
|
||||||
|
# Do not set Content-Type if status is 1xx, 204, 205 or 304, otherwise
|
||||||
|
# Rack will throw an error (500)
|
||||||
|
if !(100..199).include?(status) && ![204, 205, 304].include?(status)
|
||||||
|
response[1]['Content-Type'] = resource.content_type || 'application/octet-stream'
|
||||||
|
end
|
||||||
|
halt response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'rack/mock'
|
require 'rack/mock'
|
||||||
|
require 'middleman-core/rack'
|
||||||
|
|
||||||
Given /^a clean server$/ do
|
Given /^a clean server$/ do
|
||||||
@initialize_commands = []
|
@initialize_commands = []
|
||||||
|
@ -43,14 +44,14 @@ Given /^the Server is running$/ do
|
||||||
initialize_commands = @initialize_commands || []
|
initialize_commands = @initialize_commands || []
|
||||||
initialize_commands.unshift lambda { config[:show_exceptions] = false }
|
initialize_commands.unshift lambda { config[:show_exceptions] = false }
|
||||||
|
|
||||||
@server_inst = Middleman::Application.server.inst do
|
@server_inst = ::Middleman::Application.new do
|
||||||
initialize_commands.each do |p|
|
initialize_commands.each do |p|
|
||||||
instance_exec(&p)
|
instance_exec(&p)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
app_rack = @server_inst.class.to_rack_app
|
rack = ::Middleman::Rack.new(@server_inst)
|
||||||
@browser = ::Rack::MockRequest.new(app_rack)
|
@browser = ::Rack::MockRequest.new(rack.to_app)
|
||||||
end
|
end
|
||||||
|
|
||||||
Given /^the Server is running at "([^\"]*)"$/ do |app_path|
|
Given /^the Server is running at "([^\"]*)"$/ do |app_path|
|
||||||
|
|
Loading…
Reference in a new issue