Class: SWS::Application
- Inherits:
-
Object
- Object
- SWS::Application
- Defined in:
- lib/sws/application.rb
Overview
Main class of each SWS application. Can be accessed as a singleton object. Contains global data (session independent) and perform fundamental request/response/exception handling. Defines request handlers.
Constant Summary collapse
- @@instance =
Singleton instance of an application
nil
Instance Attribute Summary collapse
-
#adaptor ⇒ Object
readonly
Adaptor object - determines the type of interface the application uses (eg. Adaptor, FastCGIAdaptor etc).
-
#component_paths ⇒ Object
readonly
Paths in which all files of components will be searched for.
-
#component_request_handler_key ⇒ Object
readonly
Request handler key for component requests.
-
#default_component_class_name ⇒ Object
readonly
Name of the subclass of Component that will be used as default for serving requests (eg. for default DirectAction or “/”).
-
#default_direct_action_class ⇒ Object
readonly
Name of the subclass of DirectAction that will be used to serve DirectAction requests with no class specified.
-
#default_encoding ⇒ Object
Default encoding for components.
-
#direct_action_request_handler_key ⇒ Object
readonly
Request handler key for direct action requests.
-
#exception_component ⇒ Object
readonly
Component class used to generate exception page.
-
#page_name_request_handler_key ⇒ Object
readonly
Request handler key for page by name requests.
-
#request_handlers ⇒ Object
readonly
Handlers for different types of requests.
-
#resource_paths ⇒ Object
readonly
Paths in which resources will be searched for.
-
#resource_request_handler_key ⇒ Object
readonly
Request handler key for resource requests.
-
#session_class ⇒ Object
readonly
Name of the subclass of Session that will be used for storing sessions.
Class Method Summary collapse
-
.instance ⇒ Object
Returns the singleton instance of the application.
Instance Method Summary collapse
-
#get_component(component_name) ⇒ Object
Returns ComponentFiles struct for given component name.
-
#handle_component_request(request, url_content) ⇒ Object
Handles component requests.
-
#handle_direct_action_request(request, url_content) ⇒ Object
Handles direct action requests.
-
#handle_exception(request, exception) ⇒ Object
Exception handling method.
-
#handle_page_name_request(request, url_content) ⇒ Object
Handles page name requests.
-
#handle_refuse_session(request) ⇒ Object
Called when new session is to be refused.
-
#handle_request(request) ⇒ Object
Takes the request and calls one of request_handlers basing on request handler key contained in URL.
-
#handle_resource_request(request, url_content) ⇒ Object
Handles resource requests.
- #handle_too_far_backtrack(request) ⇒ Object
-
#initialize ⇒ Application
constructor
Creates new singleton Application object.
-
#request_loop ⇒ Object
Main request loop of the application.
-
#run ⇒ Object
Starts the application (starts the adaptor and goes into #request_loop).
Constructor Details
#initialize ⇒ Application
Creates new singleton Application object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/sws/application.rb', line 82 def initialize () if ( @@instance != nil ) raise "Cannot create Application instance if one exists!" else @@instance = self end @request_count = 0 @frameworks = Hash.new load_config_file() setup_request_handlers() setup_component_cache() setup_session_cleaner() end |
Instance Attribute Details
#adaptor ⇒ Object (readonly)
Adaptor object - determines the type of interface the application uses (eg. Adaptor, FastCGIAdaptor etc)
28 29 30 |
# File 'lib/sws/application.rb', line 28 def adaptor @adaptor end |
#component_paths ⇒ Object (readonly)
Paths in which all files of components will be searched for
61 62 63 |
# File 'lib/sws/application.rb', line 61 def component_paths @component_paths end |
#component_request_handler_key ⇒ Object (readonly)
Request handler key for component requests
49 50 51 |
# File 'lib/sws/application.rb', line 49 def component_request_handler_key @component_request_handler_key end |
#default_component_class_name ⇒ Object (readonly)
Name of the subclass of Component that will be used as default for serving requests (eg. for default DirectAction or “/”). Defaults to “Main”
68 69 70 |
# File 'lib/sws/application.rb', line 68 def default_component_class_name @default_component_class_name end |
#default_direct_action_class ⇒ Object (readonly)
Name of the subclass of DirectAction that will be used to serve DirectAction requests with no class specified. Defaults to DirectAction
72 73 74 |
# File 'lib/sws/application.rb', line 72 def default_direct_action_class @default_direct_action_class end |
#default_encoding ⇒ Object
Default encoding for components
24 25 26 |
# File 'lib/sws/application.rb', line 24 def default_encoding @default_encoding end |
#direct_action_request_handler_key ⇒ Object (readonly)
Request handler key for direct action requests
55 56 57 |
# File 'lib/sws/application.rb', line 55 def direct_action_request_handler_key @direct_action_request_handler_key end |
#exception_component ⇒ Object (readonly)
Component class used to generate exception page
79 80 81 |
# File 'lib/sws/application.rb', line 79 def exception_component @exception_component end |
#page_name_request_handler_key ⇒ Object (readonly)
Request handler key for page by name requests
52 53 54 |
# File 'lib/sws/application.rb', line 52 def page_name_request_handler_key @page_name_request_handler_key end |
#request_handlers ⇒ Object (readonly)
Handlers for different types of requests. There are following types of requests:
-
component request: URL /cp/11324134 - request for existing component registered
in @pages hash - the number is component id, ‘cp’ - request handler key
-
page by name request: URL /pn/132443243/pageName - request for component
by its name - ‘pn’ is request handler key, number is previous component id (it is necessary so that one cannot access page directly by just writing eg. /pn/43532322/pageName)
-
direct action: URL /da/className/actionName - like direct action in WO.
Again, ‘da’ is request handler key, actionName is self-descripting
-
resource request: URL /rs/frameworkName/resourceName - request for
application resource, eg. image or CSS file. You can also provide custom request handlers - just add them to Key - request handler key (eg. ‘pn’, ‘da’), value - method handling this type of request.
46 47 48 |
# File 'lib/sws/application.rb', line 46 def request_handlers @request_handlers end |
#resource_paths ⇒ Object (readonly)
Paths in which resources will be searched for
64 65 66 |
# File 'lib/sws/application.rb', line 64 def resource_paths @resource_paths end |
#resource_request_handler_key ⇒ Object (readonly)
Request handler key for resource requests
58 59 60 |
# File 'lib/sws/application.rb', line 58 def resource_request_handler_key @resource_request_handler_key end |
#session_class ⇒ Object (readonly)
Name of the subclass of Session that will be used for storing sessions. Defaults to Session
76 77 78 |
# File 'lib/sws/application.rb', line 76 def session_class @session_class end |
Class Method Details
.instance ⇒ Object
Returns the singleton instance of the application
230 231 232 |
# File 'lib/sws/application.rb', line 230 def Application.instance () return @@instance end |
Instance Method Details
#get_component(component_name) ⇒ Object
Returns ComponentFiles struct for given component name. Usually called only once per component class, so it is a good place to require its ruby file.
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 |
# File 'lib/sws/application.rb', line 569 def get_component( component_name ) # Find component path path = @config[CONFIG_COMPONENTS][component_name] unless ( path ) framework = @frameworks.values.find { |framework| framework.components[component_name] } if ( framework ) path = File.join( framework.path,framework.components[component_name] ) else raise( "Component #{component_name} was not found in application or frameworks" ) end end # Retrieve component files. For components in nested namespaces only the # last element of the name is used. file = File.join( path, component_name.split( /::/ ).last ) if ( FileTest.file?( file+".rb" ) ) then ruby_file = file+".rb" end if ( FileTest.file?( file+".sws" ) ) then sws_file = file+".sws" end if ( FileTest.file?( file+".html" ) ) then html_file = file+".html" end if ( FileTest.file?( file+".api" ) ) then api_file = file+".api" end if ( ruby_file ) require( ruby_file ) klass = SWS.get_class( component_name ) return ComponentInfo.new( klass, sws_file, html_file, api_file ) end raise "Cannot find component files for component #{component_name}" end |
#handle_component_request(request, url_content) ⇒ Object
Handles component requests
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/sws/application.rb', line 425 def handle_component_request ( request,url_content ) # check if url_content =~ /^component_data(/action) if ( md = /^([^\/]*)\/+(.*)$/.match( url_content ) ) component_data = md[1] action_object_id = md[2].to_i else #no "/" after component_id component_data = url_content end component_id, request_number = component_data.split( /\./ ).collect { |el| el.to_i } $log_sws_component.debug( "Request number: #{request_number}" ) $log_sws_component.debug( "Got component id: #{component_id}" ) component = request.session.get_from_cache( component_id ) $log_sws_component.debug( "Retrieved component id: #{component.object_id}" ) unless ( component && request_number ) if request.session.old?( component_id ) # Backtracked too far return handle_too_far_backtrack( request ) else # new session or random URL - redirect to default component # We don't care about any parameters request.erase_content() component = Component.create( @default_component_class_name,request ) request_number = nil end else # TODO: make it possible to present a custom error page on backtrack (it # would require holding ids of all old components in session) if ( action_object_id ) #action_object_id contains id of object, whose action should be performed #right now it can only be a Hyperlink or a Form action_object = component.action_components[ action_object_id ] if ( action_object ) # We don't call the action immediately - only mark it as enabled and # it will be called during call_action phase of request-response loop action_object.enable_action() else component = Component.create( @default_component_class_name,request ) end end end $log_sws_component.debug( "Component id: #{component.object_id}, request_number #{request_number}" ) return component.process_request( request, request_number ) end |
#handle_direct_action_request(request, url_content) ⇒ Object
Handles direct action requests
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/sws/application.rb', line 508 def handle_direct_action_request ( request,url_content ) class_name, method_name = url_content.split( /\/+/,2 ) if ( method_name ) klass = SWS.get_class( class_name + "Action" ) else klass = @default_direct_action_class method_name = class_name end unless ( klass && klass.ancestors.include?( DirectAction ) ) raise TypeError.new( "Direct action class name for url #{url_content} does not inherit from SWS::DirectAction" ) end direct_action = klass.new( request ) new_component, response = direct_action.action_for_name( method_name ) return [new_component, response] end |
#handle_exception(request, exception) ⇒ Object
Exception handling method. If you want to customize error handling, just override this one.
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/sws/application.rb', line 266 def handle_exception ( request,exception ) $log_sws_request.warn( "Exception in application loop: #{exception}" ) $log_sws_request.warn( "Backtrace: #{exception.backtrace.join("\n")}" ) begin exception_page = Component.create( @exception_component, request ) exception_page.exception = exception component,response = exception_page.process_request( request ) rescue Exception => exception #just in case there is an error in SWS itself $log_sws_component.warn( "Exception in exception component #{@exception_component}: #{exception}" ) $log_sws_component.warn( "Backtrace: #{exception.backtrace.join("\n")}" ) response = Response.new( request,"500" ) response. << request.session. response.headers["Content-type"] = "text/html;charset=#{@default_encoding}" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0" response << "<HTML><BODY><H1>Exception raised!</H1>\n" response << "<H3>#{exception.class}:#{exception}</H3>\n" response << "Backtrace: <BR> #{exception.backtrace.join("<BR>\n")}" response << "</BODY></HTML>\n" end return response end |
#handle_page_name_request(request, url_content) ⇒ Object
Handles page name requests
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/sws/application.rb', line 479 def handle_page_name_request ( request,url_content ) if ( md = /^(-?\d+)\/(.+)/.match( url_content ) ) previous_component = request.session.get_from_cache( md[1].to_i ) unless ( previous_component ) request.erase_content() component = Component.create( @default_component_class_name,request ) return component.process_request( request ) else next_component_name = md[2] component = Component.create( next_component_name, request, next_component_name) return component.process_request( request ) end else raise( "Malformed url for pn type request" ) end end |
#handle_refuse_session(request) ⇒ Object
Called when new session is to be refused. Default implementation return a simplistic info page. Note that this method probably shouldn’t return a component, as a component may require session to work properly.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/sws/application.rb', line 321 def handle_refuse_session ( request ) response = Response.new( request ,"503" ) response.headers["Content-type"] = "text/html;charset=#{@default_encoding}" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0" response << "<HTML><BODY><H1>Session refused!</H1>\n" response << "<H3>Session refused due to application overload. Please try again later.</H3>" response << "</BODY></HTML>\n" return response end |
#handle_request(request) ⇒ Object
Takes the request and calls one of request_handlers basing on request handler key contained in URL. Also performs session retrieval/creation.
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 |
# File 'lib/sws/application.rb', line 340 def handle_request ( request ) # hack for requests for favicon if it doesn't exist # TODO: it blocks ALL favicon.ico requests, but should block only if # favicon.ico is not provided # TODO: or we should omit this issue after implementation of # handling more than one current component if ( request.query_string =~ /favicon.ico$/ || request.path =~ /favicon.ico$/ ) return Response.new( request, "404 Not Found" ) end session = request.session if ( session == nil ) if ( @session_class.sessions.size < @max_sessions ) session = @session_class.new request.session = session else # Too many sessions if ( @refuse_sessions ) return handle_refuse_session( request ) else # Delete Least Recently Used session session_to_delete = @session_class.sessions.values.inject do |session1,session2| session1.last_access_time < session2.last_access_time ? session1 : session2 end @session_class.delete_session( session_to_delete ) session = @session_class.new request.session = session end end end session.last_access_time = Time.now #strip leading /'s path = request.path.sub( /^\/+/,"" ) #path =~ ^/key/url_content$ if ( md = /^([^\/]*)\/+(.*)$/.match( path ) ) request_handler_key = md[1] #TODO: rename it: its just the url without request handler key #I assume the query string is already stripped url_content = md[2] request_handler = method( @request_handlers[request_handler_key] ) else #call default request handler request_handler = method( @request_handlers[path] ) url_content = path end begin component,response = request_handler.call( request,url_content ) # The resource request handler does not return component # That means that we only want to send response without any component. # Variable component is used to return response. Look at # SWS::Component#process_request for more. if( response == nil ) return component end if ( component ) request.session.add_to_cache( component ) end return response rescue Exception => exception return handle_exception( request,exception ) end end |
#handle_resource_request(request, url_content) ⇒ Object
Handles resource requests
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 |
# File 'lib/sws/application.rb', line 531 def handle_resource_request ( request,url_content ) response = Response.new( request ) framework_name,resource_name = url_content.split( /\/+/,2 ) if ( framework_name == 'app' ) resource = @config[CONFIG_RESOURCES][resource_name] else framework = @frameworks[framework_name] resource = framework.resources[resource_name] end unless ( resource ) raise( "Cannot find resource #{resource_name} in framework #{framework}" ) end response.headers["Content-type"] = resource["mime-type"] if ( framework ) path = File.join( framework.path, resource["filename"] ) else path = resource["filename"] end unless( File.exists?( path ) ) raise( "File #{path} does not exist" ) end file = File.open( path ) response << file.read file.close() return nil,response end |
#handle_too_far_backtrack(request) ⇒ Object
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/sws/application.rb', line 301 def handle_too_far_backtrack ( request ) # TODO: not sure about the status response = Response.new( request,"200 SWS" ) response. << request.session. response.headers["Content-type"] = "text/html;charset=#{@default_encoding}" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0" # TODO: allow the user to enter the application again response << "<HTML><BODY><H1>You backtracked too far</H1>\n" response << "</BODY></HTML>\n" return nil,response end |
#request_loop ⇒ Object
Main request loop of the application. Reads Request objects from the adaptor and passes them to #handle_request method
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/sws/application.rb', line 246 def request_loop () @adaptor.each_request do |request| @request_count += 1 # The response has to be returned from the block - handle request returns one response = nil @session_cleaner_mutex.synchronize { response = handle_request( request ) } response end end |
#run ⇒ Object
Starts the application (starts the adaptor and goes into #request_loop)
236 237 238 239 240 241 |
# File 'lib/sws/application.rb', line 236 def run () @adaptor.run() request_loop() end |