lib/rhoconnect/server.rb in rhoconnect-3.4.5 vs lib/rhoconnect/server.rb in rhoconnect-4.0.0.beta.10

- old
+ new

@@ -3,236 +3,167 @@ require 'erb' require 'json' require 'fileutils' require 'rhoconnect' -# all middlewares +# all middlewares, conditions, handlers - everything, that makes our product so cool! +Dir[File.join(File.dirname(__FILE__),'handler', '*.rb')].each { |mw| require mw } Dir[File.join(File.dirname(__FILE__),'middleware','**','*.rb')].each { |mw| require mw } +Dir[File.join(File.dirname(__FILE__),'condition','**','*.rb')].each { |mw| require mw } module Rhoconnect - + class ApiException < Exception attr_accessor :error_code def initialize(error_code,message) super(message) @error_code = error_code - end + end end - + class Server < Sinatra::Base set :static, true set :stats, false # default secret @secret = '<changeme>' - + + class << self + def set_default(setting, value) + @default_settings ||= {} + @default_settings[setting] = value + end + + # these methods will be called by the subclasses + # to prevent double inclusion + # of the base middleware + # def reset_rc_base! + # #@middleware_configured = true + # end + + # def self.inherited(subclass) + # subclass.reset_rc_base! + # end + end + # Setup route and mimetype for bulk data downloads # TODO: Figure out why "mime :data, 'application/octet-stream'" doesn't work - Rack::Mime::MIME_TYPES['.data'] = 'application/octet-stream' - + Rack::Mime::MIME_TYPES['.data'] = 'application/octet-stream' + include Rhoconnect + # common conditions + extend Rhoconnect::Condition::AdminRequired + extend Rhoconnect::Condition::LoginRequired + extend Rhoconnect::Condition::SourceRequired + extend Rhoconnect::Condition::ClientRequired + extend Rhoconnect::Condition::Verbs + extend Rhoconnect::Condition::VerifySuccess + # RC Handlers + include Rhoconnect::Handler::Query::ExecuteMethods + include Rhoconnect::Handler::Changes::ExecuteMethods + include Rhoconnect::Handler::Search::ExecuteMethods + include Rhoconnect::Handler::PluginCallbacks::ExecuteMethods + include Rhoconnect::Handler::Authenticate::ExecuteMethods + # Set rhoconnect middleware set :use_middleware, Proc.new { return false if @middleware_configured # Middleware might be configured only once! use Rhoconnect::Middleware::XDomainSessionWrapper - use Rack::Cors do |cfg| - cfg.allow do |allow| - allow.origins(/.*/) - allow.resource '/application', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true - allow.resource '/application/*', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true - allow.resource '/api/application', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true - allow.resource '/api/application/*', :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true - allow.resource "/app/#{Rhoconnect::API_VERSION}/*", :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true - allow.resource "/rc/#{Rhoconnect::API_VERSION}/app/*", :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true - end - end use Rhoconnect::Middleware::BodyContentTypeParser use Rhoconnect::Middleware::Stats Rhoconnect::Server.set :secret, @secret unless settings.respond_to?(:secret) use Rack::Session::Cookie, :key => 'rhoconnect_session', :expire_after => Rhoconnect.cookie_expire, :secret => Rhoconnect::Server.secret - use Rhoconnect::Middleware::CurrentRequest use Rhoconnect::Middleware::CurrentApp use Rhoconnect::Middleware::CurrentUser - use Rhoconnect::Middleware::AdminUser - use Rhoconnect::Middleware::LoginRequired - + @middleware_configured ||= true } helpers do def request_action request.env['PATH_INFO'].split('/').last end - def check_login - begin - yield - rescue LoginException => le - throw :halt, [401, le.message] - rescue Exception => e - throw :halt, [500, e.message] - end - end - - def do_login - check_login do - if login - status(200) - else - raise LoginException.new("Unable to authenticate '#{params[:login]}'") - end - end - end - - def do_ans_login - check_login do - ans_login ? status(204) : status(401) - end - end - - def login - if params[:login].nil? or params[:login].empty? - return false - end - if params[:login] == 'rhoadmin' - user = User.authenticate(params[:login], params[:password]) - elsif current_app and current_app.can_authenticate? and params[:login] - user = current_app.authenticate(params[:login], params[:password], session) - end - if user - session[:login] = user.login - session[:app_name] = APP_NAME - else - false - end - end - - def ans_login - if current_app and current_app.can_ans_authenticate? - auth = Rack::Auth::Basic::Request.new(request.env) - current_app.ans_authenticate( - auth.credentials.first, auth.credentials.last - ) if auth.provided? and auth.basic? and auth.credentials - else - false - end - end - - def logout - session[:login] = nil - end - def current_user @env[Rhoconnect::CURRENT_USER] end def current_app @env[Rhoconnect::CURRENT_APP] end + def request_action + Rhoconnect.resource_action(request.env) + end + def current_client - if @client.nil? - client_id = @env[Rhoconnect::CLIENT_ID_HEADER] - client_id = params[:client_id] unless client_id - if client_id - @client = Client.load(client_id.to_s, - params[:source_name] ? {:source_name => current_source.name} : {:source_name => '*'}) - if @client and current_user and @client.user_id != current_user.login - @client.switch_user(current_user.login) - end - end - end - @client + @env[Rhoconnect::CURRENT_CLIENT] end def current_source - return @source if @source - user = current_user - if params[:source_name] and user - @source = Source.load(params[:source_name], - {:user_id => user.login,:app_id => APP_NAME}) - - # if source does not exist create one for dynamic adapter - unless @source - sconfig = Rhoconnect.source_config(params[:source_name]) - @source = Source.create(sconfig.merge!({:name => params[:source_name]}),{:user_id => user.login, :app_id => APP_NAME}) - current_app.sources << @source.name - end - @source - else - log "ERROR: Can't load source, no source_name provided.\n" - nil - end + @env[Rhoconnect::CURRENT_SOURCE] end - def current_client_sync - ClientSync.new(current_source,current_client,params[:p_size]) - end - def catch_all begin yield rescue ApiException => ae throw :halt, [ae.error_code, ae.message] rescue Exception => e log e.message + e.backtrace.join("\n") throw :halt, [500, e.message] end end - - def mark_deprecated_call_and_reroute(name, namespace, *params) - namespace_val = namespace.nil? ? "<namespace>" : "#{namespace}" - http_method = request.get? ? "GET" : "POST" - warning_message = "Use of the #{http_method} #{request.path} is deprecated. Use #{http_method} /api/#{namespace_val}/#{name} instead." + + def mark_deprecated_call_and_reroute_api4(verb, new_route, old_verb, old_route, route_handler, filter_handler=nil, klass = nil) + warning_message = "Use of the #{old_verb.to_s.upcase} #{old_route} is deprecated. Use Rhoconnect API #{Rhoconnect::API_VERSION} instead." response.headers['Warning'] = warning_message Rhoconnect.log warning_message - if namespace != nil - call env.merge('PATH_INFO' => "/api/#{namespace}/#{name}") + if klass + klass_instance = klass.new + klass_instance.helpers.call! env.merge('PATH_INFO' => new_route, 'REQUEST_METHOD' => verb.to_s.upcase) else - yield *params + execute_api_call(route_handler, filter_handler) end end - - def mark_deprecated_call_and_reroute_api4(verb, new_route, old_verb, old_route, client_call, &block) - warning_message = "Use of the #{old_verb.to_s.upcase} #{old_route} is deprecated. Use Rhoconnect API #{Rhoconnect::API_VERSION} instead." - response.headers['Warning'] = warning_message - Rhoconnect.log warning_message - - execute_api_call(client_call, &block) - end - - def execute_api_call(client_call = false) + + def execute_api_call(route_handler, filter_handler = nil) catch_all do - res = yield params, current_user, self + proc = route_handler.bind(self) + res = nil + if filter_handler + res = send filter_handler, proc + else + res = proc.call + end if params.has_key? :warning Rhoconnect.log params[:warning] response.headers['Warning'] = params[:warning] end res end end end - - private + + private def self._use_async_framework return false if settings.respond_to?(:use_async_model) and settings.use_async_model == false return false if @dispatch_framework_initialized - + @dispatch_framework_initialized ||=true if RUBY_VERSION =~ /1.9/ and not defined?(JRUBY_VERSION) - begin - require 'rhoconnect/async' + begin + require 'rhoconnect/async' register Rhoconnect::Synchrony - helpers Rhoconnect::AsyncHelpers rescue LoadError => e # if it fails here - Async can not be used settings.use_async_model = false warning_for_async_gems = <<_INSTALL_ASYNC_GEMS - + ***** WARNING ***** Rhoconnect has detected that Ruby 1.9.x is used and tried to initialize Async Framework, but failed: #{e.inspect} @@ -251,82 +182,120 @@ end else set :use_async_model, false end end - - def self.new - # by default, enable this feature - if not settings.respond_to?(:use_async_model) or settings.use_async_model != false - set :use_async_model, true + + class << self + def new + # by default, enable this feature + if not settings.respond_to?(:use_async_model) or settings.use_async_model != false + set :use_async_model, true + end + # this must be called first - because + # it redefines some of the middleware + _use_async_framework + + if settings.respond_to?(:stats) and settings.send(:stats) == true + Rhoconnect.stats = true + else + Rhoconnect::Server.disable :stats + Rhoconnect.stats = false + end + Rhoconnect::Server.settings.use_middleware + #puts "Controller #{self.name} has been initialized with #{middleware.inspect}" + super end - # this must be called first - because - # it redefines some of the middleware - Rhoconnect::Server._use_async_framework - - if settings.respond_to?(:stats) and settings.send(:stats) == true - Rhoconnect.stats = true - else - Rhoconnect::Server.disable :stats - Rhoconnect.stats = false - end - settings.use_middleware - - super end - + def initialize # Whine about default session secret check_default_secret!(Rhoconnect::Server.secret) super end - + Rhoconnect.log "Rhoconnect Server v#{Rhoconnect::VERSION} started..." before do cache_control :no_cache headers({'pragma'=>'no-cache'}) end - get '/' do - redirect "/console/" - end - - def self.api(name, namespace = nil, verb = :post, &block) - # this method is deprecated - so we should warn the users - # that it will be removed in 4.0 - warning_for_server_api = <<_DEPRECATE_SERVER_API -***** WARNING ***** - Server.api method is deprecated and will be removed in Rhoconnect 4.0 - Please change your implementation to Server.api4 -_DEPRECATE_SERVER_API - puts warning_for_server_api - - old_api_prefix = (namespace == :application) ? :application : :api - client_call = (namespace == :application) ? true : false - send verb, "/#{old_api_prefix}/#{name}" do - mark_deprecated_call_and_reroute(name, namespace, params, &block) - end - - send verb, "/api/#{namespace}/#{name}" do - execute_api_call(client_call, &block) - end - end - - def self.api4(method_name, verb, route, admin = true, deprecated_route = nil, &block) + def self.api4(resource, route_url, verb = :post, options = {}, &block) + deprecated_route = options[:deprecated_route] + options.delete(:deprecated_route) + + # TODO: Re-work deprecation handling as soon as client is updated + # the only reason we do criss-cross routing is because old-style routes + # had one root and new-style routes now reside in separate controllers + rc_handler = options[:rc_handler] + options.delete(:rc_handler) + rc_handler_method = nil + rc_handler_method = "execute_#{rc_handler}_handler".to_sym if rc_handler unless deprecated_route.nil? deprecated_urls = deprecated_route[:url].is_a?(String) ? [deprecated_route[:url]] : deprecated_route[:url] deprecated_urls.each do |deprecated_url| - send deprecated_route[:verb], deprecated_url do - mark_deprecated_call_and_reroute_api4(verb, route, deprecated_route[:verb], deprecated_url, !admin, &block) + d_verb = deprecated_route[:verb].to_sym + dep_route_handler = Rhoconnect::DefaultServer.send(:generate_method, :dep_route_handler, &block) + + # build deprecation hash to be used later at run time for re-directing + dep_info = {} + dep_info[:klass] = self + dep_info[:route_handler] = dep_route_handler + dep_info[:rc_handler_method] = rc_handler_method + dep_info[:verb] = verb + dep_info[:route_url] = route_url + + Rhoconnect::DefaultServer.registered_routes(d_verb)[deprecated_url] ||= {} + Rhoconnect::DefaultServer.registered_routes(d_verb)[deprecated_url][self.name] = dep_info + Rhoconnect::DefaultServer.send d_verb, deprecated_url, options do + klass = nil + req_verb = env['REQUEST_METHOD'].downcase.to_sym + req_path = env['PATH_INFO'] + # retrieve controller-specific deprecation handler + if params[:source_name] + controller_name = "#{params[:source_name]}Controller" + dep_info = Rhoconnect::DefaultServer.registered_routes(req_verb)[req_path][controller_name] + if dep_info + klass = dep_info[:klass] + verb = dep_info[:verb] + route_url = dep_info[:route_url] + dep_route_handler = dep_info[:route_handler] + rc_handler_method = dep_info[:rc_handler_method] + end + end + mark_deprecated_call_and_reroute_api4(verb, route_url, req_verb, req_path, dep_route_handler, rc_handler_method, klass) end end end - send verb, route do - execute_api_call(!admin, &block) + + # turn block into UnboundMethod - so that we can bind it later with + # particular Controller instance + route_handler = send(:generate_method, :route_handler, &block) + route verb.to_s.upcase, route_url, options do + execute_api_call(route_handler, rc_handler_method) end end end + + # serves OBSOLETED routes and root + # TODO - OBSOLETED routes should be removed + class DefaultServer < Rhoconnect::Server + helpers Rhoconnect::Handler::Helpers::BulkData + + # to prevent registering the same route several times + def self.registered_routes(verb) + @registered_routes ||= {} + @registered_routes[verb] ||= {} + @registered_routes[verb] + end + + get '/' do + redirect "/console/" + end + end end include Rhoconnect -Dir[File.join(File.dirname(__FILE__),'api','**','*.rb')].each { |api| load api } +# load all controllers , starting with base +require 'rhoconnect/controller/base' +Dir[File.join(File.dirname(__FILE__),'controller','**','*.rb')].each { |api| require api }