1. Robert Clipsham
  2. Serenity

Commits

Robert Clipsham  committed 3eb88ab

Added lots of documentation to Serenity.d and Controller.d.
Logging is now implemented and set-up in the bootstrap.
Logging can now be done in controllers with no setup.
Requests are now timed.
Reverted changes to build script as they didn't take into account dependencies
between modules.

  • Participants
  • Parent commits 346bc7a
  • Branches default

Comments (0)

Files changed (5)

File bootstrap.d

View file
  • Ignore whitespace
 
 import serenity.Serenity;
 import serenity.database.Sqlite;
-import serenity.SqlitePrinter;
 
 import controllers;
 
 
 int main(char[][] args)
 {
+    /// Set logging type and level
+    Log.type = Log.Type.Stderr;
+    Log.level = Log.Level.Trace;
+
+    /// Create a new SQLite Database using the given file
+    auto db = new SqliteDatabase("/home/robert/projects/serenity/test.db");
     scope(exit) Database.finalize();
-    auto db = new SqliteDatabase("/home/robert/projects/serenity/test.db");
     Database.addDatabase(db);
+
+    /// Set up routing
     Router.addRoutes([
                         "/"[]                           : "example/Home"[],
                         "/[plugin]"                     : "[plugin]/Default",
                         "/[plugin]/[controller]/[args]" : "[plugin]/[controller]/[args]"
                     ]);
     Router.errorRoute("example", "Error");
+
+    /// Launch Serenity
     Serenity.setNumberOfThreads(2, 4);
     return Serenity.exec(args);
 }

File compile

View file
  • Ignore whitespace
 
 rm -f lib/libserenity.a
 for f in $(find serenity -name \*.d); do
-    if [[ $f -nt .obj/$(dirname $f)/$(basename $f .d).o ]]; then
-        $DC -unittest $ARGS $f $OD.obj -op || exit 1
-    fi
+    $DC -unittest $ARGS $f $OD.obj -op || exit 1
 done
 ar rs lib/libserenity.a $(find .obj -name \*.o) || exit 1
 
 
 rm -f lib/libserenity-example.a
 for f in $(find example/controllers/ -name \*.d); do
-    if [[ $f -nt .obj/$(dirname $f)/$(basename $f .d).o ]]; then
-        $DC $ARGS $f $OD.obj -op
-    fi
+    $DC $ARGS $f $OD.obj -op
     m=$(basename $f .d | perl -pe 's|/|.|g')
     echo "import example.controllers.$m;" >> controllers.d
 done
 for f in $(find example/models/ -name \*.d); do
-    if [[ $f -nt .obj/$(dirname $f)/$(basename $f .d).o ]]; then
-        $DC $ARGS $f $OD.obj -op || exit 1
-    fi
+    $DC $ARGS $f $OD.obj -op || exit 1
 done
 ar rs lib/libserenity-example.a $(find .obj -name \*.o) || exit 1
 

File serenity/Controller.d

View file
  • Ignore whitespace
 module serenity.Controller;
 
 public import serenity.HtmlDocument;
+import serenity.Log;
 import serenity.Model;
 import serenity.Response : Headers;
 
 public import tango.core.RuntimeTraits : isDerived;
 
+/**
+ * Register a class as a controller
+ *
+ * Examples:
+ * ----
+ *  class MyController : Controller
+ *  {
+ *      mixin registerController!(MyController);
+ *  }
+ * ----
+ */
 template registerController(T)
 {
     static this()
     }
 }
 
+/**
+ * Register a class as a controller with a given model
+ *
+ * This is a wrapper around registerController!(T) to add in convinience
+ * methods for using models.
+ * Examples:
+ * ----
+ *  import myPlugin.models.MyModel;
+ *
+ *  class MyController : Controller
+ *  {
+ *      mixin registerController!(MyController, MyModel);
+ *      void myMethod()
+ *      {
+ *          foreach (item; model.getItems())
+ *          {
+ *              /// Do something
+ *          }
+ *      }
+ *  }
+ * ----
+ * See_Also:
+ *  example/controllers/Home.d
+ */
 template registerController(T, M : Model)
 {
     mixin registerController!(T);
     {
         _model = new M;
     }
-    M model()
+
+    protected M model()
     {
         return _model;
     }
 }
 
+/**
+ * Thrown when Controller not found
+ */
 class ControllerNotFoundException : Exception
 {
     this(char[] msg)
     }
 }
 
+/**
+ * Thrown when the specified Controller is not derived from Controller
+ */
 class InvalidControllerException : Exception
 {
     this(char[] msg)
     }
 }
 
+/**
+ * Represents a controller, where logic is implemented in a plugin
+ *
+ * All controllers should inherit from this
+ */
 abstract class Controller
 {
     private static ClassInfo[char[]] mControllers;
     protected char[][] mArguments;
     private Headers mHeaders;
+    private Logger mLog;
     private ushort mResponseCode = 200;
 
+    /**
+     * Create an instance of the given controller
+     *
+     * Params:
+     *  plugin   = The plugin containing the controller
+     *  subClass = The class name of the controller. This should the the same
+     *             as the module name. Only one controller may exist per file.
+     *  args     = Arguments to pass to the class
+     *  code     = The default response code for the controller if an error has
+     *             already occurred
+     * Throws:
+     *  ControllerNotFoundException when the given controller does not exist
+     * Returns:
+     *  An instance of the requested controller
+     */
     public static Controller create(char[] plugin, char[] subClass, char[][] args, ushort code=200)
     {
         char[] cname = plugin ~ ".controllers." ~ subClass ~ '.' ~ subClass;
             throw new ControllerNotFoundException("Controller not found: " ~ cname);
         }
         auto controller = cast(typeof(this))ci.create();
+        controller.mLog = Log.getLogger(cname);
         controller.mArguments = args;
         controller.mHeaders = new Headers;
         controller.mHeaders["Content-Type"] = "text/html; charset=utf-8";
         return controller;
     }
 
+    /**
+     * Check if the given controller exists
+     *
+     * Params:
+     *  plugin   = Name of the plugin containing the controller
+     *  subClass = Name of the controller
+     * Returns:
+     *  true if the controller exists
+     */
     public static bool find(char[] plugin, char[] subClass)
     {
         char[] cname = plugin ~ ".controllers." ~ subClass ~ '.' ~ subClass;
         return ci !is null;
     }
 
+    /**
+     * Register the given controller
+     *
+     * This should not be called directly, this is handled by registerController!(T)
+     * Params:
+     *  ci = ClassInfo of the controller to register
+     * Throws:
+     *  InvalidControllerException when the given controller does not extend
+     *  Controller
+     */
     public static void register(ClassInfo ci)
     {
         if (!isDerived(ci, this.classinfo))
         mControllers[ci.name] = ci;
     }
 
+    /**
+     * Return the logger for the current controller
+     *
+     * Examples:
+     * ----
+     *  void myMethod()
+     *  {
+     *      if (log.info) log.info("myMethod()");
+     *  }
+     * ----
+     * Returns:
+     *  Instance of Logger for the current controller
+     */
+    protected Logger log()
+    {
+        return mLog;
+    }
+
+    /**
+     * Get the HTTP response code of the given controller
+     *
+     * Returns:
+     *  HTTP response code for this controller
+     */
     public ushort getResponseCode()
     {
         return mResponseCode;
     }
 
+    /**
+     * Set the HTTP response code for this controller
+     *
+     * Examples:
+     * ----
+     *  HtmlDocument view()
+     *  {
+     *      ...
+     *      /// An error occurred
+     *      setResponseCode(500);
+     *      ...
+     *  }
+     * ----
+     */
     protected void setResponseCode(ushort code)
     {
         mResponseCode = code;
     }
 
+    /**
+     * Return a list of headers set by this controller
+     *
+     * Returns:
+     *  The headers set by this controller
+     */
     public Headers getHeaders()
     {
         return mHeaders;
     }
 
+    /**
+     * Set HTTP headers for this controller
+     *
+     * Examples:
+     * ----
+     *  HtmlDocument view()
+     *  {
+     *      ...
+     *      /// May as well expire the content at this point
+     *      setHeader("Expires", "Fri, 21 Dec 2012 00:00:00 GMT");
+     *      ...
+     *  }
+     * ----
+     */
     protected void setHeader(char[] name, char[] value)
     {
         mHeaders[name] = value;
     }
 
+    /**
+     * All controllers should implement this method
+     *
+     * Examples:
+     * ----
+     *  class MyController : Controller
+     *  {
+     *      mixin registerController!(MyController);
+     *      HtmlDocument view()
+     *      {
+     *          auto doc = new HtmlDocument;
+     *          // Do something here
+     *          return doc;
+     *      }
+     *  }
+     * ----
+     */
     abstract public HtmlDocument view();
 }

File serenity/Log.d

View file
  • Ignore whitespace
 import tango.util.log.LayoutDate;
 import tango.util.log.Log : TangoLog = Log;
 public import tango.util.log.Log : Logger;
+
 static class Log
 {
     /// Logging style

File serenity/Serenity.d

View file
  • Ignore whitespace
 import serenity.Controller;
 import serenity.Dispatcher;
 import serenity.HtmlPrinter;
-import serenity.Log;
 import serenity.Request;
 
+public import serenity.Log;
 public import serenity.Router;
 
 import tango.core.Variant;
 import tango.io.stream.Format;
 import tango.sys.Environment;
 import tango.text.convert.Layout;
+import tango.time.StopWatch;
 
 class Serenity : FastCGIApplication
 {
     private static char[][] mArgs;
     private static Dispatcher mDispatcher;
+    private static Logger log;
     private static ThreadPool!(Variant[]) mPool;
     private static size_t mPoolThreads = 1;
     private static size_t mFcgiThreads = 0;
     private FormatOutput!(char) mStdout;
     private FormatOutput!(char) mStderr;
 
+    /**
+     * Create a new instance of the application
+     *
+     * Used by FastCGId, should not be used by applications
+     */
     this (int id, FastCGIRequest r)
     {
         auto layout = new Layout!(char)();
         
         mStdout = new FormatOutput!(char)(layout, r.stdout);
         mStderr = new FormatOutput!(char)(layout, r.stderr);
-
-        Log.setStreams(mStdout, mStderr);
     }
 
+    /**
+     * Append a new job to Serenity's thread pool
+     *
+     * Examples:
+     * ----
+     *  void doExpensiveTask(int id)
+     *  {
+     *      assert(id == 4);
+     *      // Do something intensive here
+     *  }
+     *  /// Call doExpensiveTask(4) in a seperate thread
+     *  Serenity.poolAppend(&doExpensiveTask, 4);
+     * ----
+     * Params:
+     *  dg = delegate to run on a separate thread
+     *  args = list of arguments to pass to the delegate
+     */
     public static void poolAppend(U...)(void delegate(U) dg, U args)
     {
         Variant[] dgArgs;
         mFcgiThreads = fcgi;
     }
 
+    /**
+     * Execute Serenity
+     *
+     * This method is used to execute Serenity
+     * Examples:
+     * ----
+     *  int main(char[][] args)
+     *  {
+     *      /// Serenity should be configured here
+     *
+     *      /// Execute Serenity
+     *      return Serenity.exec(args);
+     *  }
+     * ----
+     * See_Also:
+     *  bootstrap.d
+     * Params:
+     *  args = Command line arguments
+     * Returns:
+     *  Zero (0) on successful termination, non-zero on error 
+     */
     public static int exec(char[][] args)
     {
         mDispatcher = new Dispatcher;
         mArgs = args;
+        log = Log.getLogger("serenity.Serenity");
         mPool = new ThreadPool!(Variant[])(mPoolThreads);
         return typeof(super).loop!(typeof(this))(null, false, mFcgiThreads == 0 ? threadsPerCPU() : mFcgiThreads);
     }
 
-    public static char[][] getCliArgs()
-    {
-        return mArgs.dup;
-    }
-
+    /**
+     * Handle a request
+     *
+     * This is the main entry point for any request handled by Serenity. This
+     * can be called multiple times simoultaneously depending on how many
+     * FastCGI threads have been allocated using Serenity.setNumberOfThreads().
+     *
+     * Returns:
+     *  Zero (0) on success, one (1) otherwise
+     */
     public int run()
     {
+        StopWatch sw;
         try
         {
+            if (log.info) log.info("Accepting new request");
+            sw.start;
+            
+            /// Handle the request
             auto request = new Request(request().args(), mArgs);
             auto response = mDispatcher.dispatch(request);
             response.send(mStdout);
+            
+            sw.stop;
+            if (log.info) log.info("Request complete in {}µs", sw.microsec);
             return 0;
         }
         catch (Exception e)
         {
+            sw.stop;
             e.writeOut((char[] str) { mStderr(str); });
             return 1;
         }