Overhaul content-type handling, making it configurable via page/proxy commands as well as frontmatter with the 'content_type' parameter.
Now, users can set content type explicitly for their files in a number of ways, or rely on automatic file-extension content types. Proxied files default to automatic file-extension content types, but then fall back to the content type of the resource they proxy to. There is also a bug fixed around correctly setting content type inside send_file. Fixes #821.
This commit is contained in:
parent
397ccd2c1e
commit
7a4aa109a6
15 changed files with 118 additions and 53 deletions
43
middleman-core/features/content_type.feature
Normal file
43
middleman-core/features/content_type.feature
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
Feature: Setting the right content type for files
|
||||||
|
|
||||||
|
Scenario: The right content type gets automatically determined
|
||||||
|
Given the Server is running at "content-type-app"
|
||||||
|
When I go to "/index.html"
|
||||||
|
Then the content type should be "text/html"
|
||||||
|
When I go to "/images/blank.gif"
|
||||||
|
Then the content type should be "image/gif"
|
||||||
|
When I go to "/javascripts/app.js"
|
||||||
|
Then the content type should be "application/javascript"
|
||||||
|
When I go to "/stylesheets/site.css"
|
||||||
|
Then the content type should be "text/css"
|
||||||
|
When I go to "/README"
|
||||||
|
Then the content type should be "text/plain"
|
||||||
|
|
||||||
|
Scenario: Content type can be set explicitly via page or proxy or frontmatter
|
||||||
|
Given a fixture app "content-type-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
page "README", :content_type => 'text/awesome'
|
||||||
|
proxy "bar", "index.html", :content_type => 'text/custom'
|
||||||
|
proxy "foo", "README" # auto-delegate to target content type
|
||||||
|
"""
|
||||||
|
And the Server is running at "content-type-app"
|
||||||
|
When I go to "/README"
|
||||||
|
Then the content type should be "text/awesome"
|
||||||
|
When I go to "/bar"
|
||||||
|
Then the content type should be "text/custom"
|
||||||
|
When I go to "/foo"
|
||||||
|
Then the content type should be "text/awesome"
|
||||||
|
When I go to "/override.html"
|
||||||
|
Then the content type should be "text/neato"
|
||||||
|
|
||||||
|
Scenario: Content types can be overridden with mime_type
|
||||||
|
Given a fixture app "content-type-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
mime_type('.js', 'application/x-javascript')
|
||||||
|
"""
|
||||||
|
And the Server is running at "content-type-app"
|
||||||
|
When I go to "/javascripts/app.js"
|
||||||
|
Then the content type should be "application/x-javascript"
|
||||||
|
|
1
middleman-core/fixtures/content-type-app/config.rb
Normal file
1
middleman-core/fixtures/content-type-app/config.rb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
# I'm an htaccess file!
|
1
middleman-core/fixtures/content-type-app/source/README
Normal file
1
middleman-core/fixtures/content-type-app/source/README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
I have no file extension.
|
BIN
middleman-core/fixtures/content-type-app/source/images/blank.gif
Executable file
BIN
middleman-core/fixtures/content-type-app/source/images/blank.gif
Executable file
Binary file not shown.
After Width: | Height: | Size: 43 B |
|
@ -0,0 +1 @@
|
||||||
|
I'm an HTML file!
|
|
@ -0,0 +1 @@
|
||||||
|
// This is JavaScript
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
content_type: 'text/neato'
|
||||||
|
---
|
||||||
|
|
||||||
|
I'm awesome!
|
|
@ -0,0 +1 @@
|
||||||
|
/* This is CSS */
|
|
@ -185,6 +185,15 @@ module Middleman::CoreExtensions
|
||||||
def data
|
def data
|
||||||
::Middleman::Util.recursively_enhance(raw_data).freeze
|
::Middleman::Util.recursively_enhance(raw_data).freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Override Resource#content_type to take into account frontmatter
|
||||||
|
def content_type
|
||||||
|
# Allow setting content type in frontmatter too
|
||||||
|
fm_type = data[:content_type]
|
||||||
|
return fm_type if fm_type
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
|
|
@ -246,12 +246,11 @@ module Middleman
|
||||||
return not_found(res, request_path) unless resource && !resource.ignored?
|
return not_found(res, request_path) unless resource && !resource.ignored?
|
||||||
|
|
||||||
# If this path is a binary file, send it immediately
|
# If this path is a binary file, send it immediately
|
||||||
return send_file(resource.source_file, env, res) if resource.binary?
|
return send_file(resource, env) if resource.binary?
|
||||||
|
|
||||||
current_path = resource.destination_path
|
current_path = resource.destination_path
|
||||||
|
|
||||||
# Set a HTTP content type based on the request's extensions
|
res['Content-Type'] = resource.content_type || 'text/plain'
|
||||||
content_type(res, mime_type(resource.ext))
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Write out the contents of the page
|
# Write out the contents of the page
|
||||||
|
@ -278,11 +277,8 @@ module Middleman
|
||||||
# @param [Symbol] type File extension
|
# @param [Symbol] type File extension
|
||||||
# @param [String] value Mime type
|
# @param [String] value Mime type
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def mime_type(type, value=nil)
|
def mime_type(type, value)
|
||||||
return type if type.nil? || type.to_s.include?('/')
|
|
||||||
return ::Rack::Mime.mime_type('.txt') if type.empty?
|
|
||||||
type = ".#{type}" unless type.to_s[0] == ?.
|
type = ".#{type}" unless type.to_s[0] == ?.
|
||||||
return ::Rack::Mime.mime_type(type, nil) unless value
|
|
||||||
::Rack::Mime::MIME_TYPES[type] = value
|
::Rack::Mime::MIME_TYPES[type] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -296,40 +292,14 @@ module Middleman
|
||||||
# Immediately send static file
|
# Immediately send static file
|
||||||
#
|
#
|
||||||
# @param [String] path File to send
|
# @param [String] path File to send
|
||||||
def send_file(path, env, res)
|
def send_file(resource, env)
|
||||||
extension = File.extname(path)
|
|
||||||
matched_mime = mime_type(extension)
|
|
||||||
matched_mime = "application/octet-stream" if matched_mime.nil?
|
|
||||||
content_type res, matched_mime
|
|
||||||
|
|
||||||
file = ::Rack::File.new nil
|
file = ::Rack::File.new nil
|
||||||
file.path = path
|
file.path = resource.source_file
|
||||||
response = file.serving(env)
|
response = file.serving(env)
|
||||||
response[1]['Content-Encoding'] = 'gzip' if %w(.svgz).include?(extension)
|
response[1]['Content-Encoding'] = 'gzip' if %w(.svgz .gz).include?(resource.ext)
|
||||||
|
response[1]['Content-Type'] = resource.content_type || "application/octet-stream"
|
||||||
halt response
|
halt response
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set the content type for the current request
|
|
||||||
#
|
|
||||||
# @param [String] type Content type
|
|
||||||
# @param [Hash] params
|
|
||||||
# @return [void]
|
|
||||||
def content_type(res, type, params={})
|
|
||||||
return unless type
|
|
||||||
default = params.delete :default
|
|
||||||
mime_type = mime_type(type) || default
|
|
||||||
throw "Unknown media type: %p" % type if mime_type.nil?
|
|
||||||
mime_type = mime_type.dup
|
|
||||||
unless params.include? :charset
|
|
||||||
params[:charset] = params.delete('charset') || "utf-8"
|
|
||||||
end
|
|
||||||
params.delete :charset if mime_type.include? 'charset'
|
|
||||||
unless params.empty?
|
|
||||||
mime_type << (mime_type.include?(';') ? ', ' : ';')
|
|
||||||
mime_type << params.map { |kv| kv.join('=') }.join(', ')
|
|
||||||
end
|
|
||||||
res['Content-Type'] = mime_type
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
require 'rack'
|
||||||
|
|
||||||
|
module Middleman::Sitemap::Extensions
|
||||||
|
# Content type is implemented as a module so it can be overridden by other sitemap extensions
|
||||||
|
module ContentType
|
||||||
|
# The preferred MIME content type for this resource
|
||||||
|
def content_type
|
||||||
|
# Allow explcitly setting content type from page/proxy options
|
||||||
|
meta_type = metadata[:options][:content_type]
|
||||||
|
return meta_type if meta_type
|
||||||
|
|
||||||
|
# Look up mime type based on extension
|
||||||
|
::Rack::Mime.mime_type(ext, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -42,18 +42,10 @@ module Middleman
|
||||||
@proxied_to
|
@proxied_to
|
||||||
end
|
end
|
||||||
|
|
||||||
# Whether this page has a template file
|
# The resource for the page this page is proxied to. Throws an exception
|
||||||
# @return [Boolean]
|
# if there is no resource.
|
||||||
def template?
|
# @return [Sitemap::Resource]
|
||||||
if proxy?
|
def proxied_to_resource
|
||||||
store.find_resource_by_path(proxied_to).template?
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_source_file
|
|
||||||
if proxy?
|
|
||||||
proxy_resource = store.find_resource_by_path(proxied_to)
|
proxy_resource = store.find_resource_by_path(proxied_to)
|
||||||
|
|
||||||
unless proxy_resource
|
unless proxy_resource
|
||||||
|
@ -64,7 +56,25 @@ module Middleman
|
||||||
raise "You can't proxy #{path} to #{proxied_to} which is itself a proxy."
|
raise "You can't proxy #{path} to #{proxied_to} which is itself a proxy."
|
||||||
end
|
end
|
||||||
|
|
||||||
proxy_resource.source_file
|
proxy_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_source_file
|
||||||
|
if proxy?
|
||||||
|
proxied_to_resource.source_file
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def content_type
|
||||||
|
mime_type = super
|
||||||
|
return mime_type if mime_type
|
||||||
|
|
||||||
|
if proxy?
|
||||||
|
proxied_to_resource.content_type
|
||||||
|
else
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require "middleman-core/sitemap/extensions/traversal"
|
require "middleman-core/sitemap/extensions/traversal"
|
||||||
|
require "middleman-core/sitemap/extensions/content_type"
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ module Middleman
|
||||||
# Sitemap Resource class
|
# Sitemap Resource class
|
||||||
class Resource
|
class Resource
|
||||||
include Middleman::Sitemap::Extensions::Traversal
|
include Middleman::Sitemap::Extensions::Traversal
|
||||||
|
include Middleman::Sitemap::Extensions::ContentType
|
||||||
|
|
||||||
# @return [Middleman::Application]
|
# @return [Middleman::Application]
|
||||||
attr_reader :app
|
attr_reader :app
|
||||||
|
|
|
@ -71,6 +71,10 @@ Then /^going to "([^\"]*)" should not raise an exception$/ do |url|
|
||||||
lambda { @browser.get(URI.escape(url)) }.should_not raise_exception
|
lambda { @browser.get(URI.escape(url)) }.should_not raise_exception
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Then /^the content type should be "([^\"]*)"$/ do |expected|
|
||||||
|
@browser.last_response.content_type.should start_with(expected)
|
||||||
|
end
|
||||||
|
|
||||||
Then /^I should see "([^\"]*)"$/ do |expected|
|
Then /^I should see "([^\"]*)"$/ do |expected|
|
||||||
@browser.last_response.body.should include(expected)
|
@browser.last_response.body.should include(expected)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue