Ack, but i had few problems with whitespaces in the code. Fix it please.
On Tue, Jun 28, 2011 at 03:51:42PM +0200, [email protected] wrote: > From: Michal Fojtik <[email protected]> > > > Signed-off-by: Michal fojtik <[email protected]> > --- > client/specs/content_spec.rb | 152 ++++++++++++++ > server/lib/deltacloud/base_driver/exceptions.rb | 7 + > server/lib/sinatra/rack_accept.rb | 152 ++++++++++++++ > server/lib/sinatra/respond_to.rb | 248 > ----------------------- > server/server.rb | 7 +- > 5 files changed, 315 insertions(+), 251 deletions(-) > create mode 100644 client/specs/content_spec.rb > create mode 100644 server/lib/sinatra/rack_accept.rb > delete mode 100644 server/lib/sinatra/respond_to.rb > > diff --git a/client/specs/content_spec.rb b/client/specs/content_spec.rb > new file mode 100644 > index 0000000..78ae937 > --- /dev/null > +++ b/client/specs/content_spec.rb > @@ -0,0 +1,152 @@ > +# > +# Copyright (C) 2009-2011 Red Hat, Inc. > +# > +# Licensed to the Apache Software Foundation (ASF) under one or more > +# contributor license agreements. See the NOTICE file distributed with > +# this work for additional information regarding copyright ownership. The > +# ASF licenses this file to you under the Apache License, Version 2.0 (the > +# "License"); you may not use this file except in compliance with the > +# License. You may obtain a copy of the License at > +# > +# http://www.apache.org/licenses/LICENSE-2.0 > +# > +# Unless required by applicable law or agreed to in writing, software > +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT > +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the > +# License for the specific language governing permissions and limitations > +# under the License. > + > +require 'specs/spec_helper' > + > +def client > + RestClient::Resource.new(API_URL) > +end > + > +def headers(header) > + encoded_credentials = > ["#{API_NAME}:#{API_PASSWORD}"].pack("m0").gsub(/\n/,'') > + { :authorization => "Basic " + encoded_credentials }.merge(header) > +end > + > +describe "return JSON" do > + > + it 'should return JSON when using application/json, */*' do > + header_hash = { > + 'Accept' => "application/json, */*" > + } > + client.get(header_hash) do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^application\/json/ > + end > + end > + > + it 'should return JSON when using just application/json' do > + header_hash = { > + 'Accept' => "application/json" > + } > + client.get(header_hash) do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^application\/json/ > + end > + end > + > +end > + > +describe "return HTML in different browsers" do > + > + it "wants XML using format parameter" do > + client['?format=xml'].get('Accept' => 'application/xhtml+xml') do > |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^application\/xml/ > + end > + end > + > + it "wants HTML using format parameter and accept set to XML" do > + client['?format=html'].get('Accept' => 'application/xml') do |response, > request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^text\/html/ > + end > + end > + > + it "wants a PNG image" do > + client['instance_states?format=png'].get('Accept' => 'image/png') do > |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^image\/png/ > + end > + end > + > + it "doesn't have accept header" do > + client.get('Accept' => '') do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^application\/xml/ > + end > + end > + > + it "can handle unknown formats" do > + client.get('Accept' => 'format/unknown') do |response, request, &block| > + response.code.should == 406 > + end > + end > + > + it "wants explicitly XML" do > + client.get('Accept' => 'application/xml') do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^application\/xml/ > + end > + end > + > + it "Internet Explorer" do > + header_hash = { > + 'Accept' => "text/html, application/xhtml+xml, */*", > + 'User-agent' => "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; > en-US)" > + } > + client.get(header_hash) do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^text\/html/ > + end > + end > + > + it "Mozilla Firefox" do > + client.get('Accept' => > "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") do > |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^text\/html/ > + end > + end > + > + it "Chrome" do > + header_hash = { > + 'Accept' => > "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", > + 'User-agent' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) > AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1" > + } > + client.get(header_hash) do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^text\/html/ > + end > + end > + > + it "Safari" do > + header_hash = { > + 'Accept' => > "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", > + 'User-agent' => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; > da-dk) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1" > + } > + client.get(header_hash) do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^text\/html/ > + end > + end > + > + it "Opera" do > + header_hash = { > + 'Accept' => "text/html, application/xml;q=0.9, application/xhtml+xml, > image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1", > + 'User-agent' => "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 > Version/11.11" > + } > + client.get(header_hash) do |response, request, &block| > + response.code.should == 200 > + response.headers[:content_type].should =~ /^text\/html/ > + end > + end > + > + > + > + > + > +end > diff --git a/server/lib/deltacloud/base_driver/exceptions.rb > b/server/lib/deltacloud/base_driver/exceptions.rb > index 391910f..d95191c 100644 > --- a/server/lib/deltacloud/base_driver/exceptions.rb > +++ b/server/lib/deltacloud/base_driver/exceptions.rb > @@ -21,6 +21,13 @@ module Deltacloud > end > end > > + class UnknownMediaTypeError < DeltacloudException > + def initialize(e, message=nil) > + message ||= e.message > + super(406, e.class.name, message, e.backtrace) > + end > + end > + > class ValidationFailure < DeltacloudException > def initialize(e, message=nil) > message ||= e.message > diff --git a/server/lib/sinatra/rack_accept.rb > b/server/lib/sinatra/rack_accept.rb > new file mode 100644 > index 0000000..9dddadd > --- /dev/null > +++ b/server/lib/sinatra/rack_accept.rb > @@ -0,0 +1,152 @@ > +# 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 'rack/accept' > + > +use Rack::Accept > + > +module Rack > + > + module RespondTo > + > + # This method is triggered after this helper is registred > + # within Sinatra. > + # We need to overide the default render method to supply correct path to > the > + # template, since Sinatra is by default looking in the current __FILE__ > path > + def self.registered(app) > + app.helpers Rack::RespondTo::Helpers > + app.class_eval do > + alias :render_without_format :render > + def render(*args, &block) > + begin > + 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 "ERROR: Missing template: #{args[1]}.#{args[0]}" unless > assumed_layout > + raise e > + end > + end > + private :render > + end > + end > + > + module Helpers > + > + # This code was inherited from respond_to plugin > + # http://github.com/cehoffman/sinatra-respond_to > + # > + # This method is used to overide the default content_type returned > from > + # rack-accept middleware. > + def self.included(klass) > + klass.class_eval do > + alias :content_type_without_save :content_type > + def content_type(*args) > + content_type_without_save *args > + request.env['rack-accept.format'] = args.first.to_sym > + response['Content-Type'] > + end > + end > + end > + > + def format(val=nil) > + request.env['rack-accept.format'] ||= val > + request.env['rack-accept.format'].to_sym > + end > + > + def static_file?(path) > + public_dir = File.expand_path(options.public) > + path = File.expand_path(File.join(public_dir, unescape(path))) > + path[0, public_dir.length] == public_dir && File.file?(path) > + end > + > + def respond_to(&block) > + wants = {} > + def wants.method_missing(type, *args, &handler) > + self[type] = handler > + end > + yield wants > + raise > Deltacloud::ExceptionHandler::UnknownMediaTypeError::new(nil, "Unknown > format") unless wants[format] > + wants[format].call > + end > + > + end > + > + end > + > + class MediaType < Sinatra::Base > + > + include Rack::RespondTo::Helpers > + > + # Define supported media types here > + # The :return key stands for content-type which will be returned > + # The :match key stands for the matching Accept header > + ACCEPTED_MEDIA_TYPES = { > + :xml => { :return => 'application/xml', :match => ['application/xml', > 'text/xml'] }, > + :json => { :return => 'application/json', :match => > ['application/json'] }, > + :html => { :return => 'text/html', :match => ['application/xhtml+xml', > 'text/html'] }, > + :png => { :return => 'image/png', :match => ['image/png'] }, > + :gv => { :return => 'application/ghostscript', :match => > ['application/ghostscript'] } > + } > + > + def call(env) > + accept, index = env['rack-accept.request'], {} > + > + # Skip everything when 'format' parameter is set in URL > + if env['rack.request.query_hash']["format"] > + media_type = case env['rack.request.query_hash']["format"] > + when 'html' then :html > + when 'xml' then :xml > + when 'json' then :json > + when 'gv' then :gv > + when 'png' then :png > + end > + index[media_type] = 1 if media_type > + else > + # Sort all requested media types in Accept using their 'q' values > + sorted_media_types = accept.media_type.qvalues.to_a.sort{ |a,b| > b[1]<=>a[1] }.collect { |t| t.first } > + # If Accept header is missing or is empty, fallback to XML format > + sorted_media_types << 'application/xml' if sorted_media_types.empty? > + # Choose the right format with the media type according to the > priority > + ACCEPTED_MEDIA_TYPES.each do |format, definition| > + definition[:match].each do |media_type| > + break if index[format] = sorted_media_types.index(media_type) > + end > + end > + # Reject formats with no/nil priority > + index.reject! { |format, priority| not priority } > + end > + > + # If after all we don't have any matching format assume that client has > + # requested unknown/wrong media type and throw an 406 error with no > body > + if index.keys.empty? > + status, headers, response = 406, {}, "" > + else > + media_type = index.to_a.sort{ |a, b| a[1]<=>b[1] }.first[0] > + # Set this environment variable for futher pickup by the 'format' > helper > + # on top > + env['rack-accept.format'] = media_type > + status, headers, response = @app.call(env) > + # Overide the Content-type with :return value of matching format > + headers['Content-Type'] = ACCEPTED_MEDIA_TYPES[media_type][:return] > + end > + > + [status, headers, response] > + end > + > + end > + > +end > + > diff --git a/server/lib/sinatra/respond_to.rb > b/server/lib/sinatra/respond_to.rb > deleted file mode 100644 > index 139573b..0000000 > --- a/server/lib/sinatra/respond_to.rb > +++ /dev/null > @@ -1,248 +0,0 @@ > -# 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 'rack/accept' > - > -use Rack::Accept > - > -module Sinatra > - module RespondTo > - > - 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 > - > - # 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.before do > - > - # 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') > - > - extension request.path_info.match(/\.([^\.\/]+)$/).to_a.first > - > - # 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 > - > - # 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 > - elsif is_chrome? > - format :html > - else > - format > lookup_format_from_mime(rack_accept.best_media_type(accept_to_array)) > - end > - > - end > - > - app.class_eval do > - > - # Simple helper to detect Chrome based browsers > - # which have screwed up they Accept headers. > - # Set HTML as default output format here > - def is_chrome? > - true if env['HTTP_USER_AGENT'] =~ /Chrome/ > - end > - > - # 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 > - > - engine = request.env['sinatra.error'].message.split('.').last > - engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine > - > - path = File.basename(request.path_info) > - path = "root" if path.nil? || path.empty? > - > - format = engine == 'builder' ? 'xml' : 'html' > - > - layout = case engine > - when 'haml' then "!!!\n%html\n %body= yield" > - when 'erb' then "<html>\n <body>\n <%= yield %>\n > </body>\n</html>" > - end > - > - layout = > "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>" > - > - (<<-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;} > - small {float:right;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"> > - <small>application.rb</small> > - <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> > - HTML > - end > - > - end > - end > - > - module Helpers > - > - # This code was copied from respond_to plugin > - # http://github.com/cehoffman/sinatra-respond_to > - def self.included(klass) > - klass.class_eval do > - 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 > - end > - end > - > - def static_file?(path) > - public_dir = File.expand_path(options.public) > - path = File.expand_path(File.join(public_dir, unescape(path))) > - > - path[0, public_dir.length] == public_dir && File.file?(path) > - end > - > - > - # 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 > - > - # This helper will holds current format. Helper should be > - # accesible from all places in Sinatra > - def format(val=nil) > - @_format ||= val > - @_format > - end > - > - def respond_to(&block) > - wants = {} > - > - def wants.method_missing(type, *args, &handler) > - self[type] = handler > - end > - > - # Set proper content-type and encoding for > - # text based formats > - if [:xml, :gv, :html, :json].include?(format) > - content_type format, :charset => 'utf-8' > - end > - yield wants > - # Raise this error if requested format is not defined > - # in respond_to { } block. > - raise MissingTemplate if wants[format].nil? > - > - wants[format].call > - end > - > - end > - > - end > -end > diff --git a/server/server.rb b/server/server.rb > index 104ea9c..787d8e4 100644 > --- a/server/server.rb > +++ b/server/server.rb > @@ -17,7 +17,7 @@ require 'sinatra' > require 'deltacloud' > require 'drivers' > require 'json' > -require 'sinatra/respond_to' > +require 'sinatra/rack_accept' > require 'sinatra/static_assets' > require 'sinatra/rabbit' > require 'sinatra/lazy_auth' > @@ -35,10 +35,13 @@ set :version, '0.3.0' > include Deltacloud::Drivers > set :drivers, Proc.new { driver_config } > > +Sinatra::Application.register Rack::RespondTo > + > use Rack::ETag > use Rack::Runtime > use Rack::MatrixParams > use Rack::DriverSelect > +use Rack::MediaType > > configure do > set :raise_errors => false > @@ -63,8 +66,6 @@ error do > report_error > end > > -Sinatra::Application.register Sinatra::RespondTo > - > # Redirect to /api > get '/' do redirect root_url, 301; end > > -- > 1.7.4.1 >
