C++ module API

From PhiWiki
Jump to: navigation, search

The C++ module API provides you to add your own JavaScript objects in the server environment. This documentation might be of interest for module programmers only. This C++ API is available since v1.5.0.

Intention

On server side it is often necessary to create APIs (Application Programming Interfaces) for special purpose. Instead of PHP or ASP (Active server pages) you program the server side logic in Phi completely in JavaScript (for example reading and processing POST variables, accessing databases, file IO, etc. - have a look at the already included Server modules). To be flexible as possible it was necessary to implement a plug-in based module system. This enables 3rd-party developers to create their own Serverscript.pngServerScript objects. A module itself is programmed in C++/Qt and as you will see it is pretty easy and straightforward.

Prerequisites

First of all you need the sources for Phi and the Qt toolkit. Please follow the Compiling Phi instructions to start with. As Qt is a cross-platform C++ library it doesn't matter if you compile on Windows, Mac OS X or Linux. You can freely choose your favorite system and compiler. If you are not familiar with Qt you find a lot of resources and documentation at http://qt-project.org.

After receiving the source via git the Phi modules directory will be located in phisketeer/src/libsrc/modules. Please point to this directory.

Example module

As an example we will create a helloworld module using Linux with the GNU-compiler gcc. Every module creation follows the same scheme - actually you have to deal with almost three classes: PHISModule, PHISScriptObj and PHISInterface. You need to derive from the first two and use the last one via PHIS_IF() to interface the server and client request data.

helloworld.pro

Qt uses project files for qmake to create an appropriate Makefile. Those project files end in .pro.

  • Create a directory named helloworld in the modules directory.
  • Change to this directory and create a file helloworld.pro with your favorite editor with this content:
 include( ../module.pri )
 HEADERS += helloworld.h
 SOURCES += helloworld.cpp
 TARGET = helloworld
 OTHER_FILES = helloworld.json

Note: the module.pri contains already all necessary settings for compiling Phi server modules. You should not change this file. If you need adaptions you can overwrite the related variables in your project file (helloworld.pro in this case).

helloworld.json

  • Create a file helloworld.json in the same directory containing:
{ "Keys": [ "helloworld" ] }

Note: the helloworld.json file is needed to provide the available keys for this module to the Phis server without loading the module itself. A module can provide more than one key (i.e. for creating different JavaScript objects in the same module).

helloworld.h

  • Create a file helloworld.h. Now we get to the interesting part: we define our HelloWorld module and our ServerScript object:

Source

 #ifndef HELLOWORLD_H
 #define HELLOWORLD_H
 #include "phismodule.h"
 
 class HelloWorldModule : public PHISModule
 {
    Q_OBJECT
    Q_PLUGIN_METADATA( IID "org.phisketeer.phis.module.helloworld" FILE "helloworld.json" )
    Q_CLASSINFO( "Author", "Marius Schumacher" )
    Q_CLASSINFO( "Url", "http://www.phisketeer.org" )
    Q_CLASSINFO( "Version", "1.0" )
    Q_CLASSINFO( "License", "LGPL" )
    Q_CLASSINFO( "Copyright", "2013 Phisys AG, 2013 Phisketeer Team" )
 
 public:
    virtual PHISScriptObj* create( const QString &key, const PHISInterface* ) const;
    virtual QStringList keys() const;
 };
 
 class HelloWorldObj : public PHISScriptObj
 {
    Q_OBJECT
    Q_PROPERTY( QString myname READ myname WRITE setMyname )
 
 public:
    explicit HelloWorldObj( const PHISInterface* );
    virtual ~HelloWorldObj();
 
 public slots:
    inline QString myname()  const { return _myname; }
    inline void setMyname( const QString &s ) { _myname=s; }
    void createHtml();
 
 private:
    QString _myname;
 };
 
 inline PHISScriptObj* HelloWorldModule::create( const QString &key, const PHISInterface *interface ) const
 {
    if ( key==QLatin1String( "helloworld" ) ) return new HelloWorldObj( interface );
    return 0;
 }
 
 inline QStringList HelloWorldModule::keys() const
 {
    return QStringList() << QStringLiteral( "helloworld" );
 }
 
 #endif // HELLOWORLD_H

Guide through

  1. the only include header we need for using modules for the Phis server is phismodule.h
  2. then we define our HelloWorldModule which derivates PHISModule
  3. as all classes are based on QObject we need the Q_OBJECT macro for moc (the Meta-Object-Compiler) - however you don't have to deal with those Qt special macros as Phi is resolving everything for you automatically
  4. Q_PLUGIN_METADATA needs to be set with your own IID which must be unique - best practice is to use a reverted domain name with the application and plug-in name (if you want to contribute your module officially to the Phisketeer distribution use org.phisketeer.phis.module.mod_name)
  5. you can add as much Q_CLASSINFO macros as you want: they use a simple "key", "value" pair scheme - the above mentioned keys are noticed by Phis and printed out in the log file at server launch
  6. the module must return a PHISScriptObj* for the virtual create() member, based on the given key and the PHISInterface object
  7. the PHISInterface object contains all request and server information and is described in detail in the subsection - it is the class you are interacting with the Phis server on a C++ base
  8. the virtual keys() member returns all keys the module provides as a string-list and should reflect the same keys used in the *.json file
  9. now we define our HelloWorldObj, the class which will provide our JavaScript object in the Serverscript environment - it derivates PHISScriptObj and is doing the important work of our module
  10. again we need the Q_OBJECT macro
  11. Qt offers the possibility to add properties to QObject based classes - what makes it very simple to provide them in the resulting JavaScript object
  12. you can define as many Q_PROPERTY as you want - in this case we define myname as a string property which enables us to write code like the following in the Serverscript environment:
    var n=helloworld.myname; helloworld.myname='new name';
  13. in the public: section, we define our constructor and destructor - the constructor must contain a const PHISInterface* pointer in its argument list, which is passed to the base class PHISScriptObj
  14. the public slots: section define our functions which will useable (and seen) by the JavaScript object - here we define our getter and setter for the myname property
  15. void createHtml() triggers our content writer which will be activated through
    helloworld.createHtml();
  16. now the implementations of HelloWorldModule::create() and HelloWorldModule::keys() follow (as inline functions) which should be self-explanatory

helloworld.cpp

  • Create an implementation file helloworld.cpp with the following content:

Source

 #include "helloworld.h"
 
 HelloWorldObj::HelloWorldObj( const PHISInterface *phisif ) : PHISScriptObj( phisif ) 
 { 
    _myname=QLatin1String( "unknown" );
 }
 
 HelloWorldObj::~HelloWorldObj() 
 {
    // All QObject based classes are deleted automatically if they have a parent
    // and you must never delete the PHISInterface* object!
 }
 
 void HelloWorldObj::createHtml()
 {
    QString content=QLatin1String( "<html><head><title>Hello world</title></head><body><h1>Hello " );
    content+=_myname+QLatin1String( "</h1></body></html>" );
    QString contentType=QLatin1String( "text/html; charset=utf-8" );
    PHIS_IF()->setContentType( contentType.toLatin1() );
    PHIS_IF()->setContent( content.toLatin1() );
 }

Guide through

  1. we include the helloworld.h header
  2. all Serverscript.pngServerScript objects must derive from PHISScriptObj
  3. the constructor initializes the property myname with the text unknown
  4. the createHtml() member produces the HTML output with the given name in the myname property
  5. PHIS_IF() returns a pointer to the PHISInterface class and is used to communicate with the Phis server, in this case the contentType and content itself are set
  • Compile the module now.

Installing the module

After compiling simply copy the libhelloworld.so (helloworld.dll on Windows, libhelloworld.dylib on Mac OS X) to the plugins/modules directory and restart the Phis server or the Apache daemon.

The log file should list the appropriate entries about the HelloWorld module.

Using the module

If a page wants to load the HelloWorld module, we can do this by adding

loadModule('helloworld');

in the Serverscript.pngServerScript section of our page properties. Now you can use the helloworld JavaScript object:

helloworld.myname='Charles';
helloworld.createHtml();

Save the page and load it in the Browser. Programming modules in Phi is a piece of cake - isn't it?

Look into the phisketeer/src/libsrc/modules directory and become familiar with the implementations of the request, email, server, ... modules. This is a good starting point for implementing modules for Phi.

Include header

Actually you only need to include the following header:

#include "phismodule.h"

PHISModule

Derive from this class to implement you own module and overwrite the pure virtual functions create() and keys():

class PHIS_EXPORT PHISModule : public QObject, public PHISModuleIF
{
    Q_OBJECT
    Q_INTERFACES( PHISModuleIF )

public:
    virtual PHISScriptObj* create( const QString &key, const PHISInterface* ) const=0;
    virtual QStringList keys() const=0;
};

PHISScriptObj

You have to use the PHISScriptObj as a base class for your own Serverscript.pngServerScript object:

class PHIS_EXPORT PHISScriptObj : public QObject
{
    Q_OBJECT
    Q_PROPERTY( QStringList properties READ properties )

public:
    explicit PHISScriptObj( const PHISInterface *interf )
        : QObject( interf->document() ), _if( interf ) {}
    inline virtual quint32 version() const { return 0x00010000; }
    virtual QScriptValue initObject( QScriptEngine *engine, const QString &key );

public slots:
    inline QStringList properties() const { return PHI::properties( this ); }

protected:
    inline const PHISInterface* PHIS_IF() const { return _if; }

private:
    const PHISInterface *_if;
};

PHISInterface

The following public members are available for querying the client request or server settings:

class PHIS_EXPORT PHISInterface : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY( PHISInterface )
    friend class PHIProcessor;
    friend class PHISGlobalScriptObj;

protected:
    explicit PHISInterface( const PHIRequest *req, PHIBasePage *page, const QSqlDatabase &db,
        PHISInterfacePrivate *ifp=0 ) : QObject( page ), _req( req ), _db( db ), _d( ifp ) {}

private:
    const PHIRequest *_req;
    QSqlDatabase _db;
    PHISInterfacePrivate *_d; // reserved

public:
    enum LogType { LTWarning, LTError, LTCritical, LTUser, LTDebug, LTTrace };
    inline QSqlDatabase database() const { return _db; }
    inline PHIBasePage* document() const { return qobject_cast<PHIBasePage*>(parent()); }
    void log( LogType, const char *file, int line, const QDateTime &dt, const QString &message ) const;
    void deprecated( const char *file, int line, const QDateTime &dt, const QString &message ) const;
    void setContentType( const QByteArray &contenttype ) const;
    void setContent( const QByteArray &content ) const;
    inline QString pageLanguage() const { return _req->currentLang(); }
    inline void setPageLanguage( const QString &l ) { _req->setCurrentLang( l ); }
    inline QString admin() const { return _req->admin(); }
    inline QString hostname() const { return _req->hostname(); }
    inline QString documentRoot() const { return _req->documentRoot(); }
    inline QString serverName() const { return _req->serverDescription(); }
    inline QString serverHost() const { return _req->serverHostname(); }
    inline QString serverDef() const { return _req->serverDefname(); }
    inline QHostAddress localAddress() const { return _req->localHostAddress(); }
    inline QHostAddress remoteAddress() const { return _req->remoteHostAddress(); }
    inline QString tempDir() const { return _req->tmpDir(); }
    inline QString today() const { return QDate::currentDate().toString( QString::fromLatin1( PHI::isoDateFormat() ) ); }
    inline QString nowUtc() const { return QDateTime::currentDateTime().toUTC().toString( QString::fromLatin1( PHI::dtFormat() ) ); }
    inline QDateTime utc() const { return QDateTime::currentDateTime().toUTC(); }
    inline qint32 port() const { return _req->port(); }
    inline qint32 keepAlive() const { return _req->keepAlive(); }
    inline QString localIP() const { return _req->localIP(); }
    inline QString remoteIP() const { return _req->remoteIP(); }
    inline QString contentType() const { return _req->contentType(); }
    inline QString user() const { return _req->user(); }
    inline QString password() const { return _req->password(); }
    inline QUrl url() const { return _req->url(); }
    inline QString method() const { return _req->method(); }
    inline QString postData() const { return _req->serverValue( QStringLiteral( "postdata" ) ); }
    inline QString fileName() const { return _req->canonicalFilename(); }
    inline QString agent() const { return _req->agent(); }
    inline QString accept() const { return _req->accept(); }
    inline QString scheme() const { return _req->scheme(); }
    inline QString self() const { return _req->serverValue( QStringLiteral( "self" ) ); }
    inline QDateTime started() const { return _req->started(); }
    inline QDateTime modified() const { return _req->lastModified(); }
    inline qint64 contentLength() const { return _req->contentLength(); }
    QStringList acceptedLanguages() const; // without qualifier ';q=x.x'
    inline quint8 osType() const { return _req->osType(); }
    inline void setOsType( quint8 type ) const { _req->setOSType( type ); }
    inline quint8 agentId() const { return _req->agentId(); }
    inline void setAgentId( quint8 aid ) const { _req->setAgentId( aid ); }
    inline quint8 agentEngine() const { return _req->agentEngine(); }
    inline void setAgentEngine( quint8 ae ) const { _req->setAgentEngine( ae ); }
    inline qint32 engineMajorVersion() const { return _req->engineMajorVersion(); }
    inline void setEngineMajorVersion( qint32 emv ) const { _req->setEngineMajorVersion( emv ); }
    inline qint32 engineMinorVersion() const { return _req->engineMinorVersion(); }
    inline void setEngineMinorVersion( qint32 emv ) const { _req->setEngineMinorVersion( emv ); }
    inline QStringList postKeys() const { return _req->postKeys(); }
    inline QStringList postValues( const QString &key ) const { return _req->postValues( key ); }
    inline QStringList getKeys() const { return _req->getKeys(); }
    inline QStringList getValues( const QString &key ) const { return _req->getValues( key ); }
    inline QStringList cookieKeys() const { return _req->cookieKeys(); }
    inline QString referer() const { return _req->referer(); }
    inline QString cookieValue( const QString &key ) const { return _req->cookieValue( key ); }
    inline QStringList uploadFileKeys() const { return _req->fileKeys(); }
    inline QString uploadFileName( const QString &key ) const { return _req->fileName( key ); }
    inline QString uploadTmpFile( const QString &key ) const { return _req->tmpFile( key ); }
    inline qint64 uploadFileSize( const QString &key ) const { return _req->fileSize( key ).toLongLong(); }
    inline QStringList headerKeys() const { return _req->headerKeys(); }
    inline QString headerValue( const QString &key ) const { return QString::fromUtf8( _req->headerValue( key.toLatin1() ) ); }
    inline void setFileName( const QString &p ) const { _req->responseRec()->setFileName( p ); }
    inline void setCookie( const QString &name, const QString &value, int maxage,
        const QString &path, const QString &domain, bool secure, bool discard ) const {
        _req->responseRec()->setCookie( name, value, maxage, path, domain, secure, discard );
    }
    inline void setCookie( const QString &name, const QString &value, const QDateTime &expires,
        const QString &path, const QString &domain, bool secure, bool discard ) const {
        _req->responseRec()->setCookie( name, value, expires, path, domain, secure, discard );
    }
    inline void setHttpHeader( const QString &name, const QString &value ) const {
        _req->responseRec()->setHeader( name.toLatin1(), value.toUtf8() );
    }
};