Implementing the Server
The wwServer class serves as the entry point of your application. It handles access via the various mechanisms of File Based, COM and ASP. It sets up the Request object for the request and knows how to deal with the outgoing HTTP stream.
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.
The wwServer class implentation
The wwServer class handles all of this conversion. Each application you build has to subclass the wwServer class with your own custom version. This is required so you can configure the application for your environment and your application classes. Actually the New Project Wizard creates a wwServer subclass for you and the generated code looks something like this (plus a few customizations):
#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:
OnInit
This method fires very early in the creation process of the server and is used to configure server settings that are crucial to how the server loads itself. Here you should override server properties and file locations like the startup INI file if you need to override it. This is a key feature actually - reading the application's INI file determines most of the startup values the server uses. The startup INI file is read after this method completes. You can override the settings loaded if desired in OnLoad().
OnLoad
OnLoad() fires after the server has initialized itself with the configuration settings from the INI file and internally has set its environment. When OnLoad() fires the server is basically ready to receive requests. This is your hook to set application specific settings in the server environment and override any Server properties that were loaded by default from the INI file or otherwise are set on the server.
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.
Process
The Process method is called on every hit of the server and is responsible for routing requests to the appropriate application class. This method handles routing by looking at the URL. It looks for routing info by default in two locations:
- The first positional parameter in a ~ separated list
- The 'page' extension
In the example above a first parameter of wwDemo and .wwd extension would route to the same class so the following URLs are actually doing the same thing:
- /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
Server Configuration Object
For every server created a custom Configuration class is created. In the code above we have a wcDemo
server which has a wcDemoConfig
class that inherits from wwServerConfig
. The Server Config has a set of default properties that get persisted into the wcDemo.ini
file (where wwDemo
is the name of the application) and are updated from the file when the server loads.
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
Process Configuration Object
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 for a wwStore
Process class it would look like this:
lcSqlConn = Server.oConfig.owwStore.cStoreSqlConnection
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"
*** This property is what's shown above
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.
Here's what the wcDemo.ini
looks like with the [main]
section and the [wwStore]
section:
[Main]
Tempfilepath=.\temp\
Template=WC_
Timerinterval=200
Scriptmode=1
Debugmode=On
LiveReloadEnabled=On
# ... more settings
[Wwstore]
#... more settings
Storename=West Wind Technologies
StoreSqlconnection=driver={sql server};server=(local);database=WebStore;
You can force the configuration file to write out settings via the Status Form and Save Settings button. This will write out all values into the
.ini
file even if they are empty or otherwise not set.
© West Wind Technologies, 1996-2024 • Updated: 07/02/20
Comment or report problem with topic