Skip to content
This repository has been archived by the owner on Jan 10, 2022. It is now read-only.

Core Calipso CalipsoRouter

cliftonc edited this page Aug 3, 2011 · 8 revisions

calipso.calipsoRouter :: Connect Middleware

The exported calipsoRouter function is loaded by express as middleware, that then responds to each request.

In Pseudo code, the responsibility of the calipsoRouter is as follows:

  • Initialise Calipso

    • Store the Config (calipso.config)
    • Create an Event Emitter (calipso.e) for custom module events (not the init / route events).
    • Configure logging (using calipso.lib.winston)
    • Load the theme
    • Load the modules
    • Initialise the modules
  • Create and return middleware function (req,res,next) that:

    • Initialises menu objects in response
    • Process form if required and stash it in req.form
    • Pass request to the event based module router

The following sections will describe the key elements of this flow:

Configure Logging

Logging is controlled by Winston, and provides the following core api:

// Shortcuts to Default
calipso.log = winston.info; // Default function

// Shortcuts to NPM levels
calipso.silly = winston.silly;
calipso.verbose = winston.verbose;
calipso.info = winston.info;
calipso.warn = winston.warn;
calipso.debug = winston.debug;
calipso.error = winston.error;

For example:

calipso.error("This is an error message that will be printed to the console ...",{hello:"world"});

Produces output:

28 Jul 09:04:12 - error: This is an error message ... hello=world

Logging is configured as part of the core configuration, accessible via the core administration page.

logs:  
  level: 'info',
  conso : {
    enabled: true
  },
  file: {
    enabled: false,
    filepath: 'logs/calipso.log'
  }
}

Loading the Theme

Themes are stored in the themes folder under the root of a Calipso site, they are designed to be completely self-contained, so they can be copied and installed between sites. The full explanation of how a theme is structured is described here: Themes, this section will explain how they are loaded by the core.

Themes are loaded by asynchronously by calipso via the loadTheme function, that wraps the core theme management library defined in lib/Theme.js.

Loading the modules

Module loading is done asynchronously. The approach is very simple and can be described in the following pseudo code:

  1. Scan all the module folders (anything under modules) and build an array of modules available.
  2. Scan the configuration to check which of these modules are enabled (if they are not in the configuration then they are not enabled).
  3. Initialise the calipso.modules object with the list of all modules, including those enabled.
  4. Load the 'about' info from the package.json.
  5. Load any module templates into the template cache.
  6. Create the dependency event listener network, creating event listeners for dependent modules (used during initialisation). In this function, if a module has a dependency that is not enabled, that module is disabled.

At the end of this process, the modules object (calipso.modules), then has a property for each module, that looks like this:

contentVersions: 
   { name: 'contentVersions',
     type: 'core',
     path: 'modules/core/contentVersions',
     enabled: true,
     inited: false,
     about: 
      { name: 'contentVersions',
        description: 'Provides versioning hooks and forms to enable storage and retrieval of different versions of content.',
        version: '0.2.0',
        homepage: 'http://calip.so',
        repository: [Object],
        author: 'Clifton Cunningham <clifton.cunningham@gmail.com> (cliftoncunningham.co.uk)' },
     fn: 
      { init: [Function: init],
        route: [Function: route],            
        depends: [Object] },
     router: 
      { moduleName: 'contentVersions',
        modulePath: 'modules/core/contentVersions',
        routes: [],
        addRoute: [Function],
        route: [Function] },
     templates: 
      { diff: [Function],
        show: [Function],
        list: [Function] },
     check: { content: false },
     event: 
      { moduleName: 'contentVersions',
        _events: [Object],
        init_start: [Function],
        init_finish: [Function],
        route_start: [Function],
        route_finish: [Function] } } }

Initialising the modules

After the modules have been loaded, they are then initialised. This is the part where we first require the module, and then call the module initialisation functions to actually enable the module. This is all executed asynchronously, using the core 'INIT' event model defined for modules in lib/Event.js to announce that a module has initialised.

The following pseudo-code describes the process:

  1. Loop through all modules that are enabled, have no dependencies, and are not marked as 'last'.
  2. Initialise the module.
  3. ** On receipt of an INIT event (on listener attached in step 6 above), a module will check if all of its dependencies have been met (e.g. if it has received events for all of the things it depends on), if so, it then iniatilises.
  4. ** On recepit of an INIT event, check if all modules have been initialised - if so, call the init callback for the application.

In this manner, eventually all dependent modules will be initialised as they recieve notification from modules that they depend on. If dependencies are not met, the module cannot be initialised and should be safely disabled.

At this point Calipso is fully initialised, and ready to respond to a request. At this point, the calipso.modules.contentVersions object looks like:

contentVersions: 
   { name: 'contentVersions',
     type: 'core',
     path: 'modules/core/contentVersions',
     enabled: true,
     inited: true,
     about: 
      { name: 'contentVersions',
        description: 'Provides versioning hooks and forms to enable storage and retrieval of different versions of content.',
        version: '0.2.0',
        homepage: 'http://calip.so',
        repository: [Object],
        author: 'Clifton Cunningham <clifton.cunningham@gmail.com> (cliftoncunningham.co.uk)' },
     fn: 
      { init: [Function: init],
        route: [Function: route],            
        depends: [Object] },
     router: 
      { moduleName: 'contentVersions',
        modulePath: 'modules/core/contentVersions',
        routes: 
          [ { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id' },
            { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id' },
            { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id/versions' },
            { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id/versions/diff/:a' },
            { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id/versions/diff/:a/:b' },
            { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id/version/:version' },
            { fn: [Object],
              options: [Object],
              path: 'GET /content/show/:id/version/:version/revert' },
            [length]: 7 ],
        addRoute: [Function],
        route: [Function] },
     templates: 
      { diff: [Function],
        show: [Function],
        list: [Function] },
     check: { content: true },
     event: 
      { moduleName: 'contentVersions',
        _events: [Object],
        init_start: [Function],
        init_finish: [Function],
        route_start: [Function],
        route_finish: [Function] } } }

The key difference here is, the module is initialised (inited === true), its dependencies are enabled (check.content === true) and the routes are registered (router.routes).

Event Based Module Routing

In exactly the same way as the modules are initialised, they are also routed. The key difference is that the events, and the event listeners must have request scope (not server scope as in the initialisation process). This is why the events related to routing are attached to the request object itself, and so re-created with each request.

The pseudo code is very similar, but uses different functions (e.g. it makes heavy use of lib/Router.js).

  1. Create a router event emittter and attach it to the request object (req.event rather than calipso.modules.module.event).
  2. Create the dependency event listener network, creating event listeners for dependent modules (used during routing).
  3. Loop through all modules that are enabled, are not last and have no dependencies:
  4. Call the module route function, passing in the current request, response objects.
  5. ** On receipt of an ROUTED event, a module will check if all of its dependencies have been met (e.g. if it has received events for all of the things it depends on), if so, it then call the module route function.
  6. ** Once all modules have been routed, return the response to the user.

Return to Index