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:
Ben Hollis 2013-04-06 14:05:26 -07:00
parent 397ccd2c1e
commit 7a4aa109a6
15 changed files with 118 additions and 53 deletions

View 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"

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@
# I'm an htaccess file!

View file

@ -0,0 +1 @@
I have no file extension.

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View file

@ -0,0 +1 @@
I'm an HTML file!

View file

@ -0,0 +1 @@
// This is JavaScript

View file

@ -0,0 +1,5 @@
---
content_type: 'text/neato'
---
I'm awesome!

View file

@ -0,0 +1 @@
/* This is CSS */

View file

@ -185,6 +185,15 @@ module Middleman::CoreExtensions
def data
::Middleman::Util.recursively_enhance(raw_data).freeze
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
module InstanceMethods

View file

@ -246,12 +246,11 @@ module Middleman
return not_found(res, request_path) unless resource && !resource.ignored?
# 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
# Set a HTTP content type based on the request's extensions
content_type(res, mime_type(resource.ext))
res['Content-Type'] = resource.content_type || 'text/plain'
begin
# Write out the contents of the page
@ -278,11 +277,8 @@ module Middleman
# @param [Symbol] type File extension
# @param [String] value Mime type
# @return [void]
def mime_type(type, value=nil)
return type if type.nil? || type.to_s.include?('/')
return ::Rack::Mime.mime_type('.txt') if type.empty?
def mime_type(type, value)
type = ".#{type}" unless type.to_s[0] == ?.
return ::Rack::Mime.mime_type(type, nil) unless value
::Rack::Mime::MIME_TYPES[type] = value
end
@ -296,40 +292,14 @@ module Middleman
# Immediately send static file
#
# @param [String] path File to send
def send_file(path, env, res)
extension = File.extname(path)
matched_mime = mime_type(extension)
matched_mime = "application/octet-stream" if matched_mime.nil?
content_type res, matched_mime
def send_file(resource, env)
file = ::Rack::File.new nil
file.path = path
file.path = resource.source_file
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
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

View file

@ -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

View file

@ -42,29 +42,39 @@ module Middleman
@proxied_to
end
# Whether this page has a template file
# @return [Boolean]
def template?
# The resource for the page this page is proxied to. Throws an exception
# if there is no resource.
# @return [Sitemap::Resource]
def proxied_to_resource
proxy_resource = store.find_resource_by_path(proxied_to)
unless proxy_resource
raise "Path #{path} proxies to unknown file #{proxied_to}:#{store.resources.map(&:path)}"
end
if proxy_resource.proxy?
raise "You can't proxy #{path} to #{proxied_to} which is itself a proxy."
end
proxy_resource
end
def get_source_file
if proxy?
store.find_resource_by_path(proxied_to).template?
proxied_to_resource.source_file
else
super
end
end
def get_source_file
def content_type
mime_type = super
return mime_type if mime_type
if proxy?
proxy_resource = store.find_resource_by_path(proxied_to)
unless proxy_resource
raise "Path #{path} proxies to unknown file #{proxied_to}:#{store.resources.map(&:path)}"
end
if proxy_resource.proxy?
raise "You can't proxy #{path} to #{proxied_to} which is itself a proxy."
end
proxy_resource.source_file
proxied_to_resource.content_type
else
nil
end
end
end

View file

@ -1,4 +1,5 @@
require "middleman-core/sitemap/extensions/traversal"
require "middleman-core/sitemap/extensions/content_type"
module Middleman
@ -8,6 +9,7 @@ module Middleman
# Sitemap Resource class
class Resource
include Middleman::Sitemap::Extensions::Traversal
include Middleman::Sitemap::Extensions::ContentType
# @return [Middleman::Application]
attr_reader :app

View file

@ -71,6 +71,10 @@ Then /^going to "([^\"]*)" should not raise an exception$/ do |url|
lambda { @browser.get(URI.escape(url)) }.should_not raise_exception
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|
@browser.last_response.body.should include(expected)
end