2012-05-20 01:49:44 +02:00
|
|
|
# Built on Rack
|
2013-12-28 01:26:31 +01:00
|
|
|
require 'rack'
|
|
|
|
require 'rack/file'
|
|
|
|
require 'rack/lint'
|
|
|
|
require 'rack/head'
|
2012-05-20 01:49:44 +02:00
|
|
|
|
2013-12-31 23:41:17 +01:00
|
|
|
require 'middleman-core/util'
|
2014-01-03 22:15:02 +01:00
|
|
|
require 'middleman-core/template_renderer'
|
2013-12-31 23:41:17 +01:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
module Middleman
|
|
|
|
module CoreExtensions
|
2012-05-07 23:41:39 +02:00
|
|
|
# Base helper to manipulate asset paths
|
2012-05-01 22:11:42 +02:00
|
|
|
module Request
|
|
|
|
# Extension registered
|
|
|
|
class << self
|
|
|
|
# @private
|
2014-01-01 23:50:42 +01:00
|
|
|
def included(app)
|
2012-05-12 17:48:03 +02:00
|
|
|
# CSSPIE HTC File
|
2012-05-24 23:29:29 +02:00
|
|
|
::Rack::Mime::MIME_TYPES['.htc'] = 'text/x-component'
|
2012-05-01 22:11:42 +02:00
|
|
|
|
|
|
|
# Let's serve all HTML as UTF-8
|
2012-06-14 13:28:00 +02:00
|
|
|
::Rack::Mime::MIME_TYPES['.html'] = 'text/html; charset=utf-8'
|
|
|
|
::Rack::Mime::MIME_TYPES['.htm'] = 'text/html; charset=utf-8'
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
app.extend ClassMethods
|
|
|
|
app.extend ServerMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-24 20:01:46 +02:00
|
|
|
Middleman.extend CompatibleClassMethods
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Include instance methods
|
|
|
|
app.send :include, InstanceMethods
|
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
module ClassMethods
|
|
|
|
# Reset Rack setup
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
def reset!
|
2013-04-13 07:23:20 +02:00
|
|
|
@rack_app = nil
|
2012-05-01 22:11:42 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Get the static instance
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
# @return [Middleman::Application]
|
|
|
|
def inst(&block)
|
|
|
|
@inst ||= begin
|
|
|
|
mm = new(&block)
|
|
|
|
mm.run_hook :ready
|
2014-01-01 03:21:30 +01:00
|
|
|
mm.config_context.execute_ready_callbacks
|
2012-05-01 22:11:42 +02:00
|
|
|
mm
|
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Set the shared instance
|
|
|
|
#
|
|
|
|
# @private
|
2014-04-29 19:50:21 +02:00
|
|
|
attr_writer :inst
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Return built Rack app
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
# @return [Rack::Builder]
|
|
|
|
def to_rack_app(&block)
|
2013-04-13 07:23:20 +02:00
|
|
|
@rack_app ||= begin
|
|
|
|
app = ::Rack::Builder.new
|
|
|
|
app.use Rack::Lint
|
2013-10-20 01:18:11 +02:00
|
|
|
app.use Rack::Head
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-04-29 19:44:24 +02:00
|
|
|
Array(@middleware).each do |klass, options, middleware_block|
|
|
|
|
app.use(klass, *options, &middleware_block)
|
2013-04-13 07:23:20 +02:00
|
|
|
end
|
2012-10-13 22:12:21 +02:00
|
|
|
|
2013-04-13 07:23:20 +02:00
|
|
|
inner_app = inst(&block)
|
2013-12-28 01:26:31 +01:00
|
|
|
app.map('/') { run inner_app }
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-04-29 19:44:24 +02:00
|
|
|
Array(@mappings).each do |path, map_block|
|
|
|
|
app.map(path, &map_block)
|
2013-04-13 07:23:20 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-13 07:23:20 +02:00
|
|
|
app
|
2012-05-01 22:11:42 +02:00
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Prototype app. Used in config.ru
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
# @return [Rack::Builder]
|
|
|
|
def prototype
|
2012-09-11 06:56:12 +02:00
|
|
|
reset!
|
|
|
|
to_rack_app
|
2012-05-01 22:11:42 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
# Call prototype, use in config.ru
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
def call(env)
|
|
|
|
prototype.call(env)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Use Rack middleware
|
|
|
|
#
|
2012-05-02 20:18:16 +02:00
|
|
|
# @param [Class] middleware Middleware module
|
2012-05-01 22:11:42 +02:00
|
|
|
# @return [void]
|
|
|
|
def use(middleware, *args, &block)
|
|
|
|
@middleware ||= []
|
|
|
|
@middleware << [middleware, args, block]
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Add Rack App mapped to specific path
|
|
|
|
#
|
2012-05-02 20:18:16 +02:00
|
|
|
# @param [String] map Path to map
|
2012-05-01 22:11:42 +02:00
|
|
|
# @return [void]
|
|
|
|
def map(map, &block)
|
|
|
|
@mappings ||= []
|
|
|
|
@mappings << [map, block]
|
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
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.
|
|
|
|
#
|
2014-04-29 19:44:24 +02:00
|
|
|
# rubocop:disable ClassVars
|
2012-05-01 22:11:42 +02:00
|
|
|
# @return [Class]
|
|
|
|
def server(&block)
|
|
|
|
@@servercounter ||= 0
|
|
|
|
@@servercounter += 1
|
2013-06-03 20:48:16 +02:00
|
|
|
const_set("MiddlemanApplication#{@@servercounter}", Class.new(Middleman::Application, &block))
|
2012-05-01 22:11:42 +02:00
|
|
|
end
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-24 20:01:46 +02:00
|
|
|
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)
|
2013-06-03 20:48:16 +02:00
|
|
|
::Middleman::Application.server(&block)
|
2012-05-24 20:01:46 +02:00
|
|
|
end
|
|
|
|
end
|
2012-05-20 01:49:44 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Methods to be mixed-in to Middleman::Application
|
|
|
|
module InstanceMethods
|
2014-04-29 19:50:21 +02:00
|
|
|
delegate :use, to: :"self.class"
|
|
|
|
delegate :map, to: :"self.class"
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
def call(env)
|
|
|
|
dup.call!(env)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Rack Interface
|
|
|
|
#
|
2012-05-02 20:18:16 +02:00
|
|
|
# @param env Rack environment
|
2012-05-01 22:11:42 +02:00
|
|
|
def call!(env)
|
|
|
|
# Store environment, request and response for later
|
2014-01-02 04:09:47 +01:00
|
|
|
req = ::Rack::Request.new(env)
|
2013-04-10 07:12:37 +02:00
|
|
|
res = ::Rack::Response.new
|
2012-05-01 22:11:42 +02:00
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
logger.debug "== Request: #{env['PATH_INFO']}"
|
2012-05-01 22:11:42 +02:00
|
|
|
|
|
|
|
# Catch :halt exceptions and use that response if given
|
|
|
|
catch(:halt) do
|
|
|
|
process_request(env, req, res)
|
|
|
|
|
|
|
|
res.status = 404
|
2012-07-19 07:10:02 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
res.finish
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Halt the current request and return a response
|
|
|
|
#
|
2012-05-02 20:18:16 +02:00
|
|
|
# @param [String] response Response value
|
2012-05-01 22:11:42 +02:00
|
|
|
def halt(response)
|
|
|
|
throw :halt, response
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
|
|
|
# Core response method. We process the request, check with
|
2012-05-02 20:18:16 +02:00
|
|
|
# the sitemap, and return the correct file, response or status
|
|
|
|
# message.
|
2012-05-01 22:11:42 +02:00
|
|
|
#
|
2012-05-02 20:18:16 +02:00
|
|
|
# @param env
|
|
|
|
# @param [Rack::Response] res
|
2014-04-29 20:43:05 +02:00
|
|
|
def process_request(env, _, res)
|
2012-05-01 22:11:42 +02:00
|
|
|
start_time = Time.now
|
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
request_path = URI.decode(env['PATH_INFO'].dup)
|
2012-09-16 08:36:00 +02:00
|
|
|
if request_path.respond_to? :force_encoding
|
|
|
|
request_path.force_encoding('UTF-8')
|
2012-05-01 22:11:42 +02:00
|
|
|
end
|
2013-12-31 23:41:17 +01:00
|
|
|
request_path = ::Middleman::Util.full_path(request_path, self)
|
2012-05-01 22:11:42 +02:00
|
|
|
|
|
|
|
# Run before callbacks
|
|
|
|
run_hook :before
|
|
|
|
|
2012-09-16 08:36:00 +02:00
|
|
|
# Get the resource object for this path
|
2014-01-03 02:25:31 +01:00
|
|
|
resource = sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))
|
2012-05-01 22:11:42 +02:00
|
|
|
|
|
|
|
# Return 404 if not in sitemap
|
2012-09-16 08:36:00 +02:00
|
|
|
return not_found(res, request_path) unless resource && !resource.ignored?
|
|
|
|
|
2012-12-31 05:36:06 +01:00
|
|
|
# If this path is a binary file, send it immediately
|
2013-04-06 23:05:26 +02:00
|
|
|
return send_file(resource, env) if resource.binary?
|
2012-12-31 05:36:06 +01:00
|
|
|
|
2013-04-06 23:05:26 +02:00
|
|
|
res['Content-Type'] = resource.content_type || 'text/plain'
|
2012-05-01 22:11:42 +02:00
|
|
|
|
|
|
|
begin
|
|
|
|
# Write out the contents of the page
|
2014-01-03 23:56:16 +01:00
|
|
|
res.write resource.render
|
2012-05-01 22:11:42 +02:00
|
|
|
|
|
|
|
# Valid content is a 200 status
|
|
|
|
res.status = 200
|
2014-01-03 22:15:02 +01:00
|
|
|
rescue Middleman::TemplateRenderer::TemplateNotFound => e
|
2012-05-01 22:11:42 +02:00
|
|
|
res.write "Error: #{e.message}"
|
|
|
|
res.status = 500
|
|
|
|
end
|
|
|
|
|
|
|
|
# End the request
|
2014-01-03 23:56:16 +01:00
|
|
|
logger.debug "== Finishing Request: #{resource.destination_path} (#{(Time.now - start_time).round(2)}s)"
|
2012-05-01 22:11:42 +02:00
|
|
|
halt res.finish
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-01 22:11:42 +02:00
|
|
|
# Add a new mime-type for a specific extension
|
|
|
|
#
|
|
|
|
# @param [Symbol] type File extension
|
|
|
|
# @param [String] value Mime type
|
|
|
|
# @return [void]
|
2013-04-06 23:05:26 +02:00
|
|
|
def mime_type(type, value)
|
2014-04-29 19:50:21 +02:00
|
|
|
type = ".#{type}" unless type.to_s[0] == '.'
|
2012-05-01 22:11:42 +02:00
|
|
|
::Rack::Mime::MIME_TYPES[type] = value
|
|
|
|
end
|
|
|
|
|
|
|
|
# Halt request and return 404
|
2012-09-16 08:36:00 +02:00
|
|
|
def not_found(res, path)
|
2013-04-06 23:48:00 +02:00
|
|
|
res.status = 404
|
2012-09-16 08:36:00 +02:00
|
|
|
res.write "<html><body><h1>File Not Found</h1><p>#{path}</p></body>"
|
2012-05-01 22:11:42 +02:00
|
|
|
res.finish
|
|
|
|
end
|
|
|
|
|
|
|
|
# Immediately send static file
|
2013-04-06 23:05:26 +02:00
|
|
|
def send_file(resource, env)
|
2012-05-01 22:11:42 +02:00
|
|
|
file = ::Rack::File.new nil
|
2013-04-06 23:05:26 +02:00
|
|
|
file.path = resource.source_file
|
2012-05-01 22:11:42 +02:00
|
|
|
response = file.serving(env)
|
2013-07-13 03:35:25 +02:00
|
|
|
status = response[0]
|
2013-04-06 23:05:26 +02:00
|
|
|
response[1]['Content-Encoding'] = 'gzip' if %w(.svgz .gz).include?(resource.ext)
|
2013-07-13 03:35:25 +02:00
|
|
|
# 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)
|
2013-12-28 01:26:31 +01:00
|
|
|
response[1]['Content-Type'] = resource.content_type || 'application/octet-stream'
|
2013-07-13 03:35:25 +02:00
|
|
|
end
|
2012-05-01 22:11:42 +02:00
|
|
|
halt response
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2012-05-11 07:12:55 +02:00
|
|
|
end
|