To create a new application you create a new instance of the wwServer class. When running COM or ASP the server itself is the startup code since the COM object is directly invoked. When running File Based however a small loader program must first load the server into memory by creating the object and then waiting for incoming requests:
#INCLUDE WCONNECT.H PUBLIC glExitServer, goWCServer *** Load the Web Connection class libraries DO WCONNECT *** Load the server - wc3DemoServer class below goWCServer = CREATE("wcDemoServer") *** Make the server live - Show puts the server online and in polling mode READ EVENTS RETURN
This instantiates the server in File Based mode, and waits for incoming requests which are checked in a timer event. When a request comes in it's processed and the output returned. The server goes idle again then simply waiting at the READ EVENTS. The file based stub is the startup required for a file server since it can't 'stand on its own'.
Using COM the server needs no startup code since the server is called directly. Instead of a form's timer firing the ProcessHit() method is called directly and off the server goes. When the COM Server is done it returns to idle status in the pool waiting for the next request.
Web Connection can automatically manage figuring out how to run the server based on which mode the server is set up for.
#INCLUDE WCONNECT.H ************************************************************** DEFINE CLASS wcDemoServer AS WWC_SERVER OLEPUBLIC ************************************************************* *** Add any custom properties here ************************************************************************ * wcDemoServer :: OnInit ************************ PROTECTED FUNCTION OnInit *** Location of the startup INI file THIS.cAppIniFile = addbs(THIS.cAppStartPath) + "wcDemo.ini" THIS.cAppName = "Web Connection Demo" *** Main Config for this class. Class below in this PRG file THIS.oConfig = CREATE("wcDemoConfig") THIS.oConfig.cFileName = THIS.cAppIniFile SET CENTURY ON ENDFUNC * SetServerEnvironment ************************************************************************ * wcDemoServer :: OnLoad ************************ PROTECTED FUNCTION OnLoad *** Any settings you want to make to the server IF THIS.lShowServerForm THIS.oServerForm.Caption =THIS.cServerId + " - Web Connection " + WWVERSION ENDIF *** Add any application paths that I might to access *** Remeber these may not be relative in COM object *** hence the full path! DO PATH WITH THIS.cAppStartpath && Required when running as InProc COM object DO PATH WITH THIS.cAppStartPath + "CLASSES\" DO PATH WITH THIS.cAppStartPath +"WWDEMO\" DO PATH WITH THIS.cAppStartPath + "WEBCONTROLS\" *** Add any data paths - SET DEFAULT has already occurred so this is safe! DO PATH WITH THIS.cAppStartPath + "WWTHREADS\" SET PROCEDURE TO wwtClasses ADDITIVE SET PROCEDURE TO wwtList ADDITIVE SET CLASSLIB TO wwStore ADDITIVE ENDFUNC * SetServerProperties ************************************************************************ * wcDemoServer :: Process ************************* PROTECTED FUNCTION Process LOCAL lcParameter, lcExtension, lcPhysicalPath *** Retrieve first parameter lcParameter=UPPER(THIS.oRequest.Querystring(1)) *** Set up project types and call external processing programs: DO CASE CASE lcParameter == "WWTHREADS" DO wwThreads with THIS CASE lcParameter == "WWDEMO" DO wwDemo with THIS *** HTTP Client Demos CASE lcParameter == "HTTP" DO HTTP with THIS CASE lcParameter =="WWSTORE" DO WWSTORE WITH THIS *** SUB APPLETS ADDED ABOVE - DO NOT MOVE THIS LINE *** CASE lcParameter == "WWMAINT" DO wwMaint with THIS OTHERWISE *** Check for Script Mapped files for: .WC, .WCS, .FXP lcPhysicalPath=THIS.oRequest.GetPhysicalPath() lcExtension = Upper(JustExt(lcPhysicalPath)) DO CASE CASE lcExtension == "WWS" DO wwStore with THIS *** ADD SCRIPTMAP EXTENSIONS ABOVE - DO NOT MOVE THIS LINE *** CASE lcExtension = "WWT" DO wwThreads with THIS *** Web Connection Demo handling CASE lcExtension = "WWD" DO wwDemo with THIS CASE lcExtension = "WC" OR lcExtension == "FXP" DO wwScriptMaps with THIS OTHERWISE *** Error - No handler available. Create custom Response=CREATE([WWC_RESPONSESTRING]) Response.StandardPage("Unhandled Request",; "The server is not setup to handle this type of Request: "+lcParameter) IF THIS.oConfig.lAdminSendErrorEmail LOCAL loIP loIP = CREATE("wwIPStuff") loIP.cMailServer = THIS.oConfig.cAdminMailServer loIP.cSenderEmail = THIS.oConfig.cAdminEmail loIP.cRecipient = THIS.oConfig.cAdminEmail loIP.cSubject = "Web Connection Error Message - Unhandled request" loIP.cMessage = CRLF + ; "The request Query String is: " +THIS.oRequest.QueryString() + CR +; " DLL or Script: " +THIS.oRequest.ServerVariables("Executable Path") + CR+; " Server Name: " + THIS.oRequest.GetServerName() *** Send and immediately return loIP.SendMailAsync() ENDIF IF THIS.lCOMObject *** Simply assign to output property THIS.cOutput=Response.GetOutput() ELSE *** FileBased - must output to file File2Var(THIS.oRequest.GetOutputFile(),Response.GetOutput()) ENDIF ENDCASE ENDCASE RETURN * EOF wc3DemoServer::Process ENDDEFINE * EOC wcDemoServer DEFINE CLASS wcDemoConfig as wwServerConfig owwStore = .NULL. FUNCTION Init THIS.owwStore = CREATE("wwStoreConfig") ENDFUNC ENDDEFINE DEFINE CLASS wwStoreConfig as RELATION cHTMLPagePath = "d:\westwind\wwStore\" cDATAPath = ".\wwStore\" cXMLDocRoot = "wwstore" cAdminUser = "Any" ENDDEFINE
Most of this code is boilerplate, which means you can simply cut and paste it for each full application server. The New Project Wizard will set up a default configuration for your server with the appropriate parts filled in. The project should be ready to run when the Wizard completes.
The key things that you will change for each fully self contained server are:
The most common things to do in OnLoad() is to load class libraries and add additional paths to the FoxPro path so data and support files can be found.
/wconnect/wc.dll?wwDemo~TestPage
/wconnect/TestPage.wwd
The CASE statement tries to find a matching 'process' signature and the calls the appropriate Process class to actually handle the request. The Process Prg file contains a small stub that loads the appropriate process class and executes its Process() method which does all the work of creating the output. The Process class manages output generation. All of this is handled by this single line of code in the Process CASE statement:
DO MyProcess.prg with THIS
These custom config classes are added to the Server file so that you may extend the Config class with additional 'global' settings, simply by adding custom properties. These settings then get persisted to the INI file and are also read at startup. They are always accessible then as (from within a Process class):
Server.oConfig.cCustomProperty
In addition each Process class also creates a custom configuration object that is specific to each Process class. In the example below there's a custom configuration for a wwStore Process with a host of custom properties. By convention these custom objects get attached to the server's oConfig object with the name of the process prefixed by an o. So to use it would look like this:
lcSqlConn = Server.oConfig.owwStore.cSqlConnection
Here's what the config looks like for this scenario:
DEFINE CLASS wcDemoConfig as wwServerConfig owwStore = .NULL. FUNCTION Init THIS.owwStore = CREATE("wwStoreConfig") ENDFUNC ENDDEFINE DEFINE CLASS wwStoreConfig as RELATION cHTMLPagePath = "d:\westwind\wwStore\" cDATAPath = ".\wwStore\" cXMLDocRoot = "wwstore" cAdminUser = "Any" cVirtualPath = "/wwstore/" cStoreName = "West Wind Web Store" cStoreSqlConnection = "driver={Sql Server};server=(local);database=WebStore;" ENDDEFINE
This configuration object is very powerful - simply add a property and any settings are persisted to the INI file and can then be changed in the INI file for configuration purposes.