From: Michal Fojtik <[email protected]>
---
server/lib/sinatra/respond_to.rb | 313 ++++++++++++++++++--------------------
server/server.rb | 8 +-
2 files changed, 157 insertions(+), 164 deletions(-)
diff --git a/server/lib/sinatra/respond_to.rb b/server/lib/sinatra/respond_to.rb
index 926b10d..b4efac4 100644
--- a/server/lib/sinatra/respond_to.rb
+++ b/server/lib/sinatra/respond_to.rb
@@ -1,73 +1,133 @@
+# respond_to (The MIT License)
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
of this software
+# and associated documentation files (the 'Software'), to deal in the Software
without restriction,
+# including without limitation the rights to use, copy, modify, merge,
publish, distribute,
+# sublicense, and/or sell copies of the Software, and to permit persons to
whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
+
require 'sinatra/base'
-require 'sinatra/accept_media_types'
+require 'rack/accept'
-# Accept header parsing was looked at but deemed
-# too much of an irregularity to deal with. Problems with the header
-# differences from IE, Firefox, Safari, and every other UA causes
-# problems with the expected output. The general expected behavior
-# would be serve html when no extension provided, but most UAs say
-# they will accept application/xml with out a quality indicator, meaning
-# you'd get the xml block served insead. Just plain retarded, use the
-# extension and you'll never be suprised.
+use Rack::Accept
module Sinatra
module RespondTo
- class UnhandledFormat < Sinatra::NotFound; end
- class MissingTemplate < Sinatra::NotFound
- def code; 500 end
+
+ class MissingTemplate < Sinatra::NotFound; end
+
+ # Define all MIME types you want to support here.
+ # This conversion table will be used for auto-negotiation
+ # with browser in sinatra when no 'format' parameter is specified.
+
+ SUPPORTED_ACCEPT_HEADERS = {
+ :xml => [
+ 'text/xml',
+ 'application/xml'
+ ],
+ :html => [
+ 'text/html',
+ 'application/xhtml+xml'
+ ],
+ :json => [
+ 'application/json'
+ ]
+ }
+
+ # We need to pass array of available response types to
+ # best_media_type method
+ def accept_to_array
+ SUPPORTED_ACCEPT_HEADERS.keys.collect do |key|
+ SUPPORTED_ACCEPT_HEADERS[key]
+ end.flatten
end
- TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm,
:c, :cc, :conf,
- :csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for,
:gemspec, :h, :hh, :htm,
- :log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake,
:rb, :rdf, :rtf, :ru,
- :s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl,
:xhtml, :xsl, :xslt, :yaml,
- :yml, :ics, :png]
+ # Then, when we get best media type for response, we need
+ # to know which format to choose
+ def lookup_format_from_mime(mime)
+ SUPPORTED_ACCEPT_HEADERS.keys.each do |format|
+ return format if SUPPORTED_ACCEPT_HEADERS[format].include?(mime)
+ end
+ end
def self.registered(app)
- app.helpers RespondTo::Helpers
- app.set :default_charset, 'utf-8'
- app.set :default_content, :html
- app.set :assume_xhr_is_js, true
+ app.helpers RespondTo::Helpers
- #deltacloud: removed the code that tried to 'guess' content based on
extension
- #as this broke blobstore api (stripping blob names). Use ?format if
present, otherwise
- #use http accept, otherwise set to default
app.before do
- if (params[:format])
- @mime_types = [Helpers::mime_type(params[:format])]
- format params[:format]
- elsif env['HTTP_ACCEPT'].nil? || env['HTTP_ACCEPT'].empty?
- ext = options.default_content
+
+ # Skip development error image and static content
+ next if self.class.development? && request.path_info =~
%r{/__sinatra__/.*?.png}
+ next if options.static? && options.public? && (request.get? ||
request.head?) && static_file?(request.path_info)
+
+ # Remove extension from URI
+ # Extension will be available as a 'extension' method
(extension=='txt')
+
+ request.path_info.sub! %r{\.([^\./]+)$}, ''
+ extension $1
+
+ # If ?format= is present, ignore all Accept negotiations because
+ # we are not dealing with browser
+ if request.params.has_key? 'format'
+ format params['format'].to_sym
+ end
+
+ # Let's make a little exception here to handle
+ # /api/instance_states[.gv/.png] calls
+ if extension.eql?('gv')
+ format :gv
+ elsif extension.eql?('png')
+ format :png
end
- end
- app.configure :development do |dev|
- dev.error UnhandledFormat do
- content_type :html, :charset => 'utf-8'
+ # Get Rack::Accept::Response object and find best possible
+ # mime type to output.
+ # This negotiation works fine with latest rest-client gem:
+ #
+ # RestClient.get 'http://localhost:3001/api', {:accept => :json } =>
+ # 'application/json'
+ # RestClient.get 'http://localhost:3001/api', {:accept => :xml } =>
+ # 'application/xml'
+ #
+ # Also browsers like Firefox (3.6.x) and Chromium reporting
+ # 'application/xml+xhtml' which is recognized as :html reponse
+ # In browser you can force output using ?format=[format] parameter.
+
+ rack_accept = env['rack-accept.request']
+
+ if rack_accept.media_type.to_s.strip.eql?('Accept:')
+ format :xml
+ else
+ format
lookup_format_from_mime(rack_accept.best_media_type(accept_to_array))
+ end
- (<<-HTML).gsub(/^ {10}/, '')
- <!DOCTYPE html>
- <html>
- <head>
- <style type="text/css">
- body {
text-align:center;font-family:helvetica,arial;font-size:22px;
- color:#888;margin:20px}
- #c {margin:0 auto;width:500px;text-align:left}
- </style>
- </head>
- <body>
- <h2>Sinatra doesn't know this ditty.</h2>
- <img src='/__sinatra__/404.png'>
- <div id="c">
- Try this:
- <pre>#{request.request_method.downcase} '#{request.path_info}'
do\n respond_to do |wants|\n wants.#{format} { "Hello World" }\n
end\nend</pre>
- </div>
- </body>
- </html>
- HTML
+ end
+
+ app.class_eval do
+ # This code was copied from respond_to plugin
+ # http://github.com/cehoffman/sinatra-respond_to
+ # MIT License
+ alias :render_without_format :render
+ def render(*args, &block)
+ assumed_layout = args[1] == :layout
+ args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
+ render_without_format *args, &block
+ rescue Errno::ENOENT => e
+ raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
+ raise e
end
+ private :render
+ end
+ # This code was copied from respond_to plugin
+ # http://github.com/cehoffman/sinatra-respond_to
+ app.configure :development do |dev|
dev.error MissingTemplate do
content_type :html, :charset => 'utf-8'
response.status = request.env['sinatra.error'].code
@@ -83,7 +143,6 @@ module Sinatra
layout = case engine
when 'haml' then "!!!\n%html\n %body= yield"
when 'erb' then "<html>\n <body>\n <%= yield %>\n
</body>\n</html>"
- when 'builder' then ::Sinatra::VERSION =~ /^1.0/ ? "xml <<
yield" : "builder do |xml|\n xml << yield\nend"
end
layout =
"<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
@@ -97,19 +156,16 @@ module Sinatra
color:#888;margin:20px}
#c {margin:0 auto;width:500px;text-align:left;}
small {float:right;clear:both;}
- pre {clear:both;}
+ pre {clear:both;text-align:left;font-size:70%;width:500px;margin:0
auto;}
</style>
</head>
<body>
<h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
<img src='/__sinatra__/500.png'>
+ <pre>#{request.env['sinatra.error'].backtrace.join("\n")}</pre>
<div id="c">
- Try this:<br />
- #{layout}
- <small>#{path}.#{format}.#{engine}</small>
- <pre>Hello World!</pre>
<small>application.rb</small>
- <pre>#{request.request_method.downcase} '#{request.path_info}'
do\n respond_to do |wants|\n wants.#{engine == 'builder' ? 'xml' : 'html'}
{ #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n
end\nend</pre>
+ <pre>#{request.request_method.downcase} '#{request.path_info}'
do\n respond_to do |wants|\n wants.#{format} { #{engine} :#{path} }\n
end\nend</pre>
</div>
</body>
</html>
@@ -117,75 +173,23 @@ module Sinatra
end
end
-
- app.class_eval do
- private
- def accept_list
- @mime_types || Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
- end
-
- # Changes in 1.0 Sinatra reuse render for layout so we store
- # the original value to tell us if this is an automatic attempt
- # to do a layout call. If it is, it might fail with Errno::ENOENT
- # and we want to pass that back to sinatra since it isn't a
MissingTemplate
- # error
- def render_with_format(*args, &block)
- assumed_layout = args[1] == :layout
- args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
- render_without_format *args, &block
- rescue Errno::ENOENT => e
- raise MissingTemplate, "#{args[1]}.#{args[0]}" unless
assumed_layout
- raise e
- end
- alias_method :render_without_format, :render
- alias_method :render, :render_with_format
-
- if ::Sinatra::VERSION =~ /^0\.9/
- def lookup_layout_with_format(*args)
- args[1] = "#{args[1]}.#{format}".to_sym if
args[1].is_a?(::Symbol)
- lookup_layout_without_format *args
- end
- alias_method :lookup_layout_without_format, :lookup_layout
- alias_method :lookup_layout, :lookup_layout_with_format
- end
- end
end
module Helpers
- # Patch the content_type function to remember the set type
- # This helps cut down on time in the format helper so it
- # doesn't have to do a reverse lookup on the header
+
+ # This code was copied from respond_to plugin
+ # http://github.com/cehoffman/sinatra-respond_to
def self.included(klass)
klass.class_eval do
- def content_type_with_save(*args)
+ alias :content_type_without_save :content_type
+ def content_type(*args)
content_type_without_save *args
@_format = args.first.to_sym
response['Content-Type']
end
- alias_method :content_type_without_save, :content_type
- alias_method :content_type, :content_type_with_save
- end if ::Sinatra::VERSION =~ /^1.0/
- end
-
- def self.mime_type(sym)
- ::Sinatra::Base.respond_to?(:mime_type) &&
::Sinatra::Base.mime_type(sym) || ::Sinatra::Base.media_type(sym)
- end
-
- def format(val=nil)
- unless val.nil?
- mime_type = ::Sinatra::RespondTo::Helpers.mime_type(val)
- fail "Unknown media type #{val}\nTry registering the extension with
a mime type" if mime_type.nil?
-
- @_format = val.to_sym
- response['Content-Type'].sub!(/^[^;]+/, mime_type)
- charset options.default_charset if
Sinatra::RespondTo::TEXT_MIME_TYPES.include?(format) and format!=:png
end
-
- @_format
end
- # This is mostly just a helper so request.path_info isn't changed when
- # serving files from the public directory
def static_file?(path)
public_dir = File.expand_path(options.public)
path = File.expand_path(File.join(public_dir, unescape(path)))
@@ -193,60 +197,43 @@ module Sinatra
path[0, public_dir.length] == public_dir && File.file?(path)
end
- def charset(val=nil)
- fail "Content-Type must be set in order to specify a charset" if
response['Content-Type'].nil?
- if response['Content-Type'] =~ /charset=[^;]+/
- response['Content-Type'].sub!(/charset=[^;]+/, (val == '' && '') ||
"charset=#{val}")
- else
- response['Content-Type'] += ";charset=#{val}"
- end unless val.nil?
-
- response['Content-Type'][/charset=([^;]+)/, 1]
+ # Extension holds trimmed extension. This is extra usefull
+ # when you want to build original URI (with extension)
+ # You can simply call "#{request.env['REQUEST_URI']}.#{extension}"
+ def extension(val=nil)
+ @_extension ||= val
+ @_extension
end
-
- def respond_to(&block)
- wants = Format.new
- yield wants
- fmt, type, handler = match_accept_type(accept_list, wants)
- raise UnhandledFormat if fmt.nil?
- format fmt
- handler.nil? ? nil : handler.call
+
+ # This helper will holds current format. Helper should be
+ # accesible from all places in Sinatra
+ def format(val=nil)
+ @_format ||= val
+ @_format
end
- def match_accept_type(mime_types, format)
- selected = []
- accepted_types = mime_types.map {|type|
Regexp.escape(type).gsub(/\\\*/,'.*') }
- # Fix for Chrome based browsers which returns XML when 'xhtml' is
requested.
- if env['HTTP_USER_AGENT'] =~ /Chrome/ and accepted_types.size>1
- accepted_types[0], accepted_types[1] = accepted_types[1],
accepted_types[0]
- if accepted_types[0].eql?('application/xhtml\\+xml')
- accepted_types[0] = 'text/html'
- end
+ def respond_to(&block)
+ wants = {}
+
+ def wants.method_missing(type, *args, &handler)
+ self[type] = handler
end
- accepted_types.each do |at|
- format.each do |fmt, ht, handler|
- (selected = [fmt, ht, handler]) and break if ht.match(at)
- end
- break unless selected.empty?
+
+ # Set proper content-type and encoding for
+ # text based formats
+ if [:xml, :gv, :html, :json].include?(format)
+ content_type format, :charset => 'utf-8'
end
- selected
- end
+ yield wants
+ # Raise this error if requested format is not defined
+ # in respond_to { } block.
+ raise MissingTemplate if wants[format].nil?
- # NOTE Array instead of hash because order matters (wildcard type
- # matches first handler)
- class Format < Array #:nodoc:
- def method_missing(format, *args, &handler)
- mt = Sinatra::RespondTo::Helpers.mime_type(format)
- if mt.nil?
- Sinatra::Base.send(:fail, "Unknown media type for respond_to:
#{format}\nTry registering the extension with a mime type")
- end
- self << [format.to_s, mt, handler]
- end
+ wants[format].call
end
+
end
+
end
end
-
-Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
-Sinatra::Application.register Sinatra::RespondTo
diff --git a/server/server.rb b/server/server.rb
index 3fef8bf..a64e79d 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -38,6 +38,8 @@ error Deltacloud::BackendError do
report_error(500, "backend_error")
end
+Sinatra::Application.register Sinatra::RespondTo
+
# Redirect to /api
get '/' do redirect url_for('/api'); end
@@ -140,7 +142,10 @@ collection :instance_states do
format.png do
# Trick respond_to into looking up the right template for the
# graphviz file
- format(:gv); gv = erb :"instance_states/show"; format(:png)
+ format_backup = format
+ format(:gv)
+ gv = erb(:"instance_states/show")
+ format(format_backup)
png = ''
cmd = 'dot -Kdot -Gpad="0.2,0.2" -Gsize="5.0,8.0" -Gdpi="180" -Tpng'
Open3.popen3( cmd ) do |stdin, stdout, stderr|
@@ -148,6 +153,7 @@ collection :instance_states do
stdin.close()
png = stdout.read
end
+ content_type 'image/png'
png
end
end
--
1.7.2.3