A stateful service is a service that maintains a context between a web services client and server. It enables the service to keep trace of previous requests from that context, in order to manage different states in the web service server.
GWS supports two kinds of stateful services:
It is up to the 4GL programmer to create, store and remove the service state in the database.
The SOAP engine is responsible for:
A stateful service based on WS-Addressing uses the WS-Addressing EndpointReference type as state variable and is independent from the transport layer used. (See WS-Addressing 1.0 EndpointReferenceType). The session state is conveyed from the client to the server as WS-Addressing 1.0 reference parameters.
Perform these steps to create a WS-Addressing stateful service:
This record MUST have:
DEFINE EndpointReferenceState RECORD ATTRIBUTE(W3CEndpointReference) address STRING, # Mandatory ref RECORD # Sub-record Reference parameters containing one or more state variables OpaqueID String ATTRIBUTE(XMLName="OpaqueID"), # Unique ID to identify the service state in the database Expiration DATE ATTRIBUTE(XMLName="Expiration",XMLNamespace="http://tempuri.org") # Session state expiration date END RECORD END RECORD
You can use a unique ID of a database table to manage the web services sessions in place of OpaqueID.
The Genero Web Service extension provides a new Web service constructor called CreateStatefulWebService() to perform stateful services. This function works as the stateless constructor, but expects a W3CEndpointReference record as parameter.
Example:
DEFINE serv com.WebService LET serv = com.WebService.CreateStatefulWebService("StatefulWSAddressingService","http://4js.com/services",EndpointReferenceState) # Create a stateful service with a W3CEndpointReference state variable CALL serv.setFeature("WS-Addressing1.0","REQUIRED") # enable support of WS-Addressing 1.0
You must define which web service operation will initiate the session on your service and return the W3CEndpointReference state variable.
All other web service operations (not defined as session initiator) will return an error if they don't get reference parameters defined in the W3CEndpointReference state variable as WS-Addressing 1.0 headers.
Example:
DEFINE op com.WebOperation LET op = com.WebOperation.CreateDocStyle("GetInstance","GetInstance",NULL,EndpointReferenceState) CALL op.initiateSession(true) CALL serv.publishOperation(op,NULL)There is no restriction regarding the input parameter of the web service initiator function, but the output parameter must be the same W3CEndpointReference record passed to the service creation constructor.
It is not required to have a web operation which initiate the session in the same service, but then you have to return the same W3CEndpointReference record in another web service to instantiate the session, such as a Factory service that instantiates all sessions for other stateful services.
In your 4GL function declared as session initiator, you have to :
Example:
FUNCTION GetInstance() LET EndpointReferenceState.address = NULL # Use default end point location LET EndpointReferenceState.ref.OpaqueID = com.Util.CreateUUIDString() # Generate an unique string (can come from a database table id) LET EndpointReferenceState.ref.Expiration = CURRENT + INTERVAL HOUR TO HOUR (1) # Create expiration date in one hour to discard request after that date ... Store OpaqueID into database or use directly a database table entry to hold the session END FUNCTION
In any publish 4GL web function, the SOAP engine deserializes the WS-Addressing 1.0 reference parameter headers into the W3CEndpointReference sub-record so that you can retrieve the session from the state variable.
Example:
FUNCTION MyFunction() IF EndpointReferenceState.ref.OpaqueID IS NULL THEN CALL com.WebServiceEngine.SetFaultString("Invalid session id") RETURN ELSE ... Restore the service session based on the OpaqueID state variable from the database END IF ... Process the operation END FUNCTION
Perform these steps to communicate with a stateful web service based on WS-Addressing 1.0:
Use the fglwsdl tool as usual. It will detect that the service returns a W3CEndpointReference and generate the appropriate code.
The WSDL imports the WS-Addressing 1.0 schema, so the fglwsdl tool requires an access to the W3C server. Use
the option -proxy if you need to connect via a proxy server.
Example:
$ fglwsdl -o ws_stub http://localhost:8090/StatefulWSAddressingService?WSDLThe generated .inc file contains a variable of type tWSAGlobalEndpointType to be used to transmit the WS-Addressing 1.0 reference parameters.
Example of a global variable name:
DEFINE StatefulWSAddressingService_StatefulWSAddressingServicePortTypeEndpoint tGlobalWSAEndpointType
In your main application:
Example:
IMPORT XML # Import the XML library required for WS-Addressing 1.0 GLOBALS "ws_stub.inc" # Import service global definition TYPE InstanceType DYNAMIC ARRAY OF xml.DomDocument # End point WSA reference parameters DEFINE instance1,instance2,instance3 InstanceType # Store the different sessions the client will have to manage MAIN ... END MAIN
Call the 4GL function generated from the WSDL that is defined as session initiator on the server. This function returns a W3CEndpointReference parameter that contains the WS-Addressing 1.0 reference parameters representing the new instance created on server side.
If your application handles several instances, you will have to copy and store those parameters in your application to identify a service instance for further requests.
As the WS-Addressing 1.0 reference parameters are defined as any XML document, they are represented as a dynamic list of xml.DomDocument in 4GL.
Example:
DISPLAY "Creating a new instance ..." LET wsstatus = GetInstance_g() # call the service session initiator web function IF wsstatus == 0 THEN FOR ind=1 TO ns1GetInstanceResponse.return.ReferenceParameters._LIST_0.getLength() LET instance1[ind]=ns1GetInstanceResponse.return.ReferenceParameters._LIST_0[ind].clone() # copy the service returned WS-Addressing 1.0 reference parameters END FOR ELSE ... handle soap errors END IFWhen creating a new instance, ensure that the Parameters member of the generated global variable of type tWSAGlobalEndpointType has been set to NULL, otherwise the server will complain.
Before calling any web service operation, you must set the WS-Addressing 1.0 reference parameters returned by a session initiator function to identify the session to the server.
Example:
LET StatefulWSAddressingService_StatefulWSAddressingServicePortTypeEndpoint.Address.Parameters.* = instance1.* # assign WS-Addressing 1.0 reference parameters dynamic array by reference CALL MyFunction("Hello") RETURNING wsstatus,ret # Call web operation MyFunction of instance 1
A stateful service based on HTTP cookies uses the HTTP transport protocol and its ability to convey cookies, used as session context. Notice that it works only if the communication path between the client and the server is performed in HTTP, otherwise it is recommended to use WS-Addressing stateful services.
Perform the following steps to create an HTTP cookie based stateful service.
Example:
DEFINE ServiceState STRING # Unique ID to identify the service state in the databaseFor instance, you can use a unique ID of a database table to manage the web services sessions.
The Genero Web Service extension provides a new Web service constructor called CreateStatefulWebService() to perform stateful services. This function works as the stateless constructor, but expects a simple state variable as parameter.
Example:
DEFINE serv com.WebService LET serv = com.WebService.CreateStatefulWebService("StatefulCookieService","http://4js.com/services",ServiceState) # Create a stateful service with a simple 4GL variable as state variable
Define which web service operation will initiate the session on your service and instantiate a new session.
All other web service operations (not defined as session initiator) will return an error if they don't get an HTTP cookie called GSESSIONID.
Example:
DEFINE op com.WebOperation LET op = com.WebOperation.CreateDocStyle("GetInstance","GetInstance",NULL,NULL) CALL op.initiateSession(true) CALL serv.publishOperation(op,NULL)There is no restriction on the web service session initiator function regarding to the input and output parameters.
In your 4GL function declared as session initiator, you must:
Example:
FUNCTION GetInstance() LET ServiceState = com.Util.CreateUUIDString() # Generate an unique string (can come from a database table id) ... Store ServiceState value into database or use directly a database table entry to hold the session END FUNCTION
In any publish 4GL web function, the SOAP engine deserializes the HTTP Cookie called GSESSIONID from the HTTP layer into the state variable.
You can then retrieve the session in 4GL via that state variable.
Example:
FUNCTION MyFunction() IF ServiceState IS NULL THEN CALL com.WebServiceEngine.SetFaultString("Invalid session id") RETURN ELSE ... Restore the service session based on the ServiceState variable from the database END IF ... Process the operation END FUNCTION
When deploying stateful web services based on HTTP cookies, the complete server path will be added into the cookie when first instantiated, so you must pay attention to that URL. In other words, you MUST always call the service via the complete URL containing the service name inside. For instance if your service is named MyService and if you GAS configuration file is called Server.xcf, the stateful service is accessible at URL : http://localhost:6394/ws/r/group/Server/MyService.
Perform the following steps to communicate with a stateful web service based on HTTP cookies.
Use the fglwsdl tool as usual.
Example:
$ fglwsdl -o ws_stub http://localhost:8090/StatefulCookieService?WSDLThe generated .inc file contains a variable of type tGlobalEndpointType to be used to transmit the HTTP Cookie.
Example of a global variable name:
DEFINE StatefulCookieService_StatefulCookieServicePortTypeEndpoint tGlobalEndpointType
In your main application:
Example:
GLOBALS "ws_stub.inc" # Import service global definition DEFINE instance1,instance2,instance3 String # Store the different sessions the client will have to manage in a string MAIN ... END MAIN
Call the 4GL function generated from the WSDL that was defined as session initiator on the server.
This function returns a new HTTP Cookie saved into the Binding.Cookie member of the global service variable of type tGlobalEndpointType.
If your application handles several instances, you will have to copy and store that cookie in your application to identify a service instance for further requests.
Example:
DISPLAY "Creating a new instance ..." LET wsstatus = GetInstance_g() # call the service session initiator web function IF wsstatus == 0 THEN LET instance1 = StatefulCookieService_StatefulCookieServicePortTypeEndpoint.Binding.Cookie # copy the service returned HTTP cookie ELSE ... handle soap errors END IFWhen creating a new instance, ensure that the Binding.Cookie member of the generated global variable of type tGlobalEndpointType has been set to NULL, otherwise the server will complain.
Before calling any web service operation, set the HTTP cookie returned by a session initiator function to identify the session to the server.
Example:
LET StatefulCookieService_StatefulCookieServicePortTypeEndpoint.Binding.Cookie = instance1 # use instance1 CALL MyFunction("Hello") RETURNING wsstatus,ret # Call web operation MyFunction of instance 1
If your Genero application doesn't set the HTTP cookie when accessing a stateful service via the GAS, it is possible that you didn't use the complete URL when accessing the service.
For instance if your service is named MyService and if you GAS configuration file is called Server.xcf, the stateful service is accessible at URL : http://localhost:6394/ws/r/group/Server/MyService.