Rack-based HTML rewriter
This commit is contained in:
parent
1006739e98
commit
927a1758ba
|
@ -94,7 +94,7 @@ Feature: Assets get a file hash appended to their and references to them are upd
|
|||
activate :relative_assets
|
||||
activate :directory_indexes
|
||||
require 'lib/middleware.rb'
|
||||
use Middleware
|
||||
use ::Middleware
|
||||
"""
|
||||
Given the Server is running at "asset-hash-app"
|
||||
When I go to "/"
|
||||
|
|
|
@ -10,13 +10,37 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
require 'digest/sha1'
|
||||
require 'rack/mock'
|
||||
require 'uri'
|
||||
require 'middleman-core/middleware/inline_url_rewriter'
|
||||
end
|
||||
|
||||
def after_configuration
|
||||
# Allow specifying regexes to ignore, plus always ignore apple touch icons
|
||||
@ignore = Array(options.ignore) + [/^apple-touch-icon/]
|
||||
|
||||
app.use Middleware, exts: options.exts, middleman_app: app, ignore: @ignore
|
||||
app.use ::Middleman::Middleware::InlineURLRewriter,
|
||||
:id => :asset_hash,
|
||||
:url_extensions => options.exts,
|
||||
:source_extensions => %w(.htm .html .php .css .js),
|
||||
:ignore => @ignore,
|
||||
:middleman_app => app,
|
||||
:proc => method(:rewrite_url)
|
||||
end
|
||||
|
||||
def rewrite_url(asset_path, dirpath)
|
||||
relative_path = Pathname.new(asset_path).relative?
|
||||
|
||||
full_asset_path = if relative_path
|
||||
dirpath.join(asset_path).to_s
|
||||
else
|
||||
asset_path
|
||||
end
|
||||
|
||||
if asset_page = app.sitemap.find_resource_by_path(full_asset_path)
|
||||
replacement_path = "/#{asset_page.destination_path}"
|
||||
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
||||
|
||||
replacement_path
|
||||
end
|
||||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
|
@ -44,7 +68,10 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
return if resource.ignored?
|
||||
|
||||
# Render through the Rack interface so middleware and mounted apps get a shot
|
||||
response = @rack_client.get(URI.escape(resource.destination_path), 'bypass_asset_hash' => 'true')
|
||||
response = @rack_client.get(URI.escape(resource.destination_path), {
|
||||
'bypass_inline_url_rewriter_asset_hash' => 'true'
|
||||
})
|
||||
|
||||
raise "#{resource.path} should be in the sitemap!" unless response.status == 200
|
||||
|
||||
digest = Digest::SHA1.hexdigest(response.body)[0..7]
|
||||
|
@ -55,74 +82,4 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
def ignored_resource?(resource)
|
||||
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) }
|
||||
end
|
||||
|
||||
# The asset hash middleware is responsible for rewriting references to
|
||||
# assets to include their new, hashed name.
|
||||
class Middleware
|
||||
def initialize(app, options={})
|
||||
@rack_app = app
|
||||
@exts = options[:exts]
|
||||
@ignore = options[:ignore]
|
||||
@exts_regex_text = @exts.map { |e| Regexp.escape(e) }.join('|')
|
||||
@middleman_app = options[:middleman_app]
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, response = @rack_app.call(env)
|
||||
|
||||
# We don't want to use this middleware when rendering files to figure out their hash!
|
||||
return [status, headers, response] if env['bypass_asset_hash'] == 'true'
|
||||
|
||||
path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app)
|
||||
|
||||
if path =~ /(^\/$)|(\.(htm|html|php|css|js)$)/
|
||||
body = ::Middleman::Util.extract_response_text(response)
|
||||
if body
|
||||
status, headers, response = Rack::Response.new(rewrite_paths(body, path), status, headers).finish
|
||||
end
|
||||
end
|
||||
|
||||
[status, headers, response]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rewrite_paths(body, path)
|
||||
dirpath = Pathname.new(File.dirname(path))
|
||||
|
||||
# TODO: This regex will change some paths in plan HTML (not in a tag) - is that OK?
|
||||
body.gsub(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{@exts_regex_text}))/) do |match|
|
||||
opening_character = $1
|
||||
asset_path = $2
|
||||
|
||||
relative_path = Pathname.new(asset_path).relative?
|
||||
|
||||
asset_path = dirpath.join(asset_path).to_s if relative_path
|
||||
|
||||
if @ignore.any? { |r| asset_path.match(r) }
|
||||
match
|
||||
elsif asset_page = @middleman_app.sitemap.find_resource_by_path(asset_path)
|
||||
replacement_path = "/#{asset_page.destination_path}"
|
||||
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
||||
|
||||
"#{opening_character}#{replacement_path}"
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# =================Temp Generate Test data==============================
|
||||
# ["jpg", "png", "gif"].each do |ext|
|
||||
# [["<p>", "</p>"], ["<p><img src=", " /></p>"], ["<p>background-image:url(", ");</p>"]].each do |outer|
|
||||
# [["",""], ["'", "'"], ['"','"']].each do |inner|
|
||||
# [["", ""], ["/", ""], ["../", ""], ["../../", ""], ["../../../", ""], ["http://example.com/", ""], ["a","a"], ["1","1"], [".", "."], ["-","-"], ["_","_"]].each do |path_parts|
|
||||
# name = 'images/100px.'
|
||||
# puts outer[0] + inner[0] + path_parts[0] + name + ext + path_parts[1] + inner[1] + outer[1]
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# puts "<br /><br /><br />"
|
||||
# end
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
require 'middleman-core/util'
|
||||
require 'rack'
|
||||
require 'rack/response'
|
||||
|
||||
module Middleman
|
||||
module Middleware
|
||||
class InlineURLRewriter
|
||||
def initialize(app, options={})
|
||||
@rack_app = app
|
||||
@middleman_app = options[:middleman_app]
|
||||
|
||||
@uid = options[:id]
|
||||
@proc = options[:proc]
|
||||
|
||||
raise "InlineURLRewriter requires a :proc to call with inline URL results" unless @proc
|
||||
|
||||
@exts = options[:url_extensions]
|
||||
|
||||
@source_exts = options[:source_extensions]
|
||||
@source_exts_regex_text = Regexp.union(@source_exts).to_s
|
||||
|
||||
@ignore = options[:ignore]
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, response = @rack_app.call(env)
|
||||
|
||||
# Allow upstream request to skip all rewriting
|
||||
return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true'
|
||||
|
||||
# Allow upstream request to skip this specific rewriting
|
||||
if @uid
|
||||
uid_key = "bypass_inline_url_rewriter_#{@uid}"
|
||||
return [status, headers, response] if env[uid_key] == 'true'
|
||||
end
|
||||
|
||||
path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app)
|
||||
|
||||
if path =~ /(^\/$)|(#{@source_exts_regex_text}$)/
|
||||
if body = ::Middleman::Util.extract_response_text(response)
|
||||
dirpath = Pathname.new(File.dirname(path))
|
||||
|
||||
rewritten = ::Middleman::Util.rewrite_paths(body, path, @exts) do |asset_path|
|
||||
relative_path = Pathname.new(asset_path).relative?
|
||||
|
||||
full_asset_path = if relative_path
|
||||
dirpath.join(asset_path).to_s
|
||||
else
|
||||
asset_path
|
||||
end
|
||||
|
||||
@ignore.none? { |r| full_asset_path.match(r) } && @proc.call(asset_path, dirpath)
|
||||
end
|
||||
|
||||
status, headers, response = ::Rack::Response.new(
|
||||
rewritten,
|
||||
status,
|
||||
headers
|
||||
).finish
|
||||
end
|
||||
end
|
||||
|
||||
[status, headers, response]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -222,6 +222,19 @@ module Middleman
|
|||
end
|
||||
end
|
||||
|
||||
def rewrite_paths(body, path, exts, &block)
|
||||
body.dup.gsub(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{Regexp.union(exts)}))/) do |match|
|
||||
opening_character = $1
|
||||
asset_path = $2
|
||||
|
||||
if result = yield(asset_path)
|
||||
"#{opening_character}#{result}"
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Is mime type known to be non-binary?
|
||||
|
|
Loading…
Reference in a new issue