From 7a4aa109a674f39c2efe491ae6de54754dd488b1 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sat, 6 Apr 2013 14:05:26 -0700 Subject: [PATCH] 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. --- middleman-core/features/content_type.feature | 43 +++++++++++++++++ .../fixtures/content-type-app/config.rb | 1 + .../content-type-app/source/.htaccess | 1 + .../fixtures/content-type-app/source/README | 1 + .../content-type-app/source/images/blank.gif | Bin 0 -> 43 bytes .../content-type-app/source/index.html | 1 + .../source/javascripts/app.js | 1 + .../content-type-app/source/override.html | 5 ++ .../source/stylesheets/site.css | 1 + .../core_extensions/front_matter.rb | 9 ++++ .../middleman-core/core_extensions/request.rb | 44 +++--------------- .../sitemap/extensions/content_type.rb | 16 +++++++ .../sitemap/extensions/proxies.rb | 42 ++++++++++------- .../lib/middleman-core/sitemap/resource.rb | 2 + .../step_definitions/server_steps.rb | 4 ++ 15 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 middleman-core/features/content_type.feature create mode 100644 middleman-core/fixtures/content-type-app/config.rb create mode 100644 middleman-core/fixtures/content-type-app/source/.htaccess create mode 100644 middleman-core/fixtures/content-type-app/source/README create mode 100755 middleman-core/fixtures/content-type-app/source/images/blank.gif create mode 100644 middleman-core/fixtures/content-type-app/source/index.html create mode 100644 middleman-core/fixtures/content-type-app/source/javascripts/app.js create mode 100644 middleman-core/fixtures/content-type-app/source/override.html create mode 100644 middleman-core/fixtures/content-type-app/source/stylesheets/site.css create mode 100644 middleman-core/lib/middleman-core/sitemap/extensions/content_type.rb diff --git a/middleman-core/features/content_type.feature b/middleman-core/features/content_type.feature new file mode 100644 index 00000000..175361d8 --- /dev/null +++ b/middleman-core/features/content_type.feature @@ -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" + diff --git a/middleman-core/fixtures/content-type-app/config.rb b/middleman-core/fixtures/content-type-app/config.rb new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/middleman-core/fixtures/content-type-app/config.rb @@ -0,0 +1 @@ + diff --git a/middleman-core/fixtures/content-type-app/source/.htaccess b/middleman-core/fixtures/content-type-app/source/.htaccess new file mode 100644 index 00000000..875bc5c4 --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/.htaccess @@ -0,0 +1 @@ +# I'm an htaccess file! \ No newline at end of file diff --git a/middleman-core/fixtures/content-type-app/source/README b/middleman-core/fixtures/content-type-app/source/README new file mode 100644 index 00000000..0f76e23c --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/README @@ -0,0 +1 @@ +I have no file extension. \ No newline at end of file diff --git a/middleman-core/fixtures/content-type-app/source/images/blank.gif b/middleman-core/fixtures/content-type-app/source/images/blank.gif new file mode 100755 index 0000000000000000000000000000000000000000..2498f1aac58dab923f0fd99b1c8ee6b8c53c7158 GIT binary patch literal 43 scmZ?wbhEHbWMp7uXkcKtd-pB_1B2pE79h#MpaUX6G7L;iE{qJ;0KEqWk^lez literal 0 HcmV?d00001 diff --git a/middleman-core/fixtures/content-type-app/source/index.html b/middleman-core/fixtures/content-type-app/source/index.html new file mode 100644 index 00000000..f33cd05b --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/index.html @@ -0,0 +1 @@ +I'm an HTML file! diff --git a/middleman-core/fixtures/content-type-app/source/javascripts/app.js b/middleman-core/fixtures/content-type-app/source/javascripts/app.js new file mode 100644 index 00000000..6b96b465 --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/javascripts/app.js @@ -0,0 +1 @@ +// This is JavaScript \ No newline at end of file diff --git a/middleman-core/fixtures/content-type-app/source/override.html b/middleman-core/fixtures/content-type-app/source/override.html new file mode 100644 index 00000000..0f9dc83f --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/override.html @@ -0,0 +1,5 @@ +--- +content_type: 'text/neato' +--- + +I'm awesome! diff --git a/middleman-core/fixtures/content-type-app/source/stylesheets/site.css b/middleman-core/fixtures/content-type-app/source/stylesheets/site.css new file mode 100644 index 00000000..09e040b6 --- /dev/null +++ b/middleman-core/fixtures/content-type-app/source/stylesheets/site.css @@ -0,0 +1 @@ +/* This is CSS */ \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb index 9a0b3c44..7bafbf70 100644 --- a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb +++ b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb @@ -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 diff --git a/middleman-core/lib/middleman-core/core_extensions/request.rb b/middleman-core/lib/middleman-core/core_extensions/request.rb index f4126b7c..1d9cbccb 100644 --- a/middleman-core/lib/middleman-core/core_extensions/request.rb +++ b/middleman-core/lib/middleman-core/core_extensions/request.rb @@ -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 diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/content_type.rb b/middleman-core/lib/middleman-core/sitemap/extensions/content_type.rb new file mode 100644 index 00000000..619ec225 --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap/extensions/content_type.rb @@ -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 diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb index 61091a46..18076943 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb @@ -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 diff --git a/middleman-core/lib/middleman-core/sitemap/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb index 96b71d77..776b55eb 100644 --- a/middleman-core/lib/middleman-core/sitemap/resource.rb +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -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 diff --git a/middleman-core/lib/middleman-core/step_definitions/server_steps.rb b/middleman-core/lib/middleman-core/step_definitions/server_steps.rb index ac0a7c3c..851536e5 100644 --- a/middleman-core/lib/middleman-core/step_definitions/server_steps.rb +++ b/middleman-core/lib/middleman-core/step_definitions/server_steps.rb @@ -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