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
> 

Reply via email to