Web Service Handlers and Message Processing
During the design of the framework, the VisualAge Smalltalk Group collaborated with members of the Open Source and SOAP communities. As a result, part of the framework design is based loosely on the Apache AXIS implementation. IBM customers that also utilize AXIS technology for interoperability should find themselves comfortable with the conceptual similarities.
Messages are passed through a decorator pattern represented by a series of handlers. Each handler supports a simple, common, API that allows developers to produce complex functionality by chaining together simple parts. Utilizing this pattern, developers are free to create and plug-in any handlers necessary to customize their Web services environment. The container uses the Handler Factory to manager handlers.
Note:
Decorator pattern is a concept developed on page 175 of Design Patterns: Elements of Reusable Object-Oriented Software.
Web Service Message Processing Handlers
Although handlers can be used anywhere during message processing, in practice, they are broadly classified into four categories: Global, Service, Transport, and Chains. Each handler category serves a different purpose during message processing. When defining your own handlers, it is important to recognize where they will fit in the larger framework. Below is a general description of each category:
Global - Manage tasks that are required for all Web services messages. Global handlers should be considered valid for all messages. For example, in the current design of the framework, global chains are responsible for the parsing and dispatching of SOAP message headers. Another use of global handlers would be to log every message processed.
Service - These handlers facilitate operation level definition of handlers. Continuing with the logging example, using service level handlers facilitates logging only for specific messages.
Transport - These handlers are responsible for binding a specific transport to the invocation framework. For example, for incoming messages, the HTTP transport handlers ensure that there is a SOAP Action present. For outgoing messages, these handlers place the proper content type and return code in the HTTP header.
Chains - Handlers are typically built to perform very granular tasks. Interrelated handlers can be logically grouped into constructs called chains. Handler chains implement the same interface as standard handlers; however, they combine the functionality of multiple handlers into a single unit.
SstWSSimpleChain new
addHandler: MyInitializationHandler new;
addHandler: MyServiceHandler new
In the example above, when the #invoke: method of the SstWSSimpleChain instance is invoked, the two declared handlers are sequentially invoked. The first handler (MyInitializationHandler) might perform an initialization function to prepare the message context for the invocation of the second handler (MyServiceHandler).
Since handler chains function like standard chains, it is possible to define a handler chain that contains other handler chains.
SstWSSimpleChain new
addHandler: MyInitializationHandler new;
addHandler: ( SstWSSimpleChain new
addHandler: MyAccountQueryHandler new;
addHandler: MyAccountResultHandler new )
Handlers and chains should be built as reentrant objects so that each handler instance can be reused.
The combination of deployment descriptors, the handler factory, handler chains, and the pool dictionary, provide the developer with a wide set of options for customizing the handler framework.
Each Web service handler is placed in the containers handler registry under a known name and namespace. Global handlers and transport handlers are placed in the handler factory using predefined keys in the container's global namespace. Service handlers are registered with these keys in the service's namespace. The service's namespace is defined in the implementation WSDL.
Table1 presents the keys used to retrieve handlers from the registry along with their default handler class. Remember, the handler factory will attempt to create these if none are initially found. For example, see implementers of wsGlobalResponseServerHandler.
Constants for these keys have been defined in the pool SstWSConstants. The constant is the string value with the 'ws' capitalized i.e. 'wsGlobalResponseServerHandler' is WSGlobalResponseServerHandler.
| |
wsGlobalRequestServerHandler | SstWSHeaderProcessor |
wsGlobalResponseServerHandler | SstWSNoOperationHandler |
wsGlobalFaultServerHandler | SstWSSimpleChain ; SstWSSoapGlobalFaultHandler ; SstWSSoapEnvelopeGenerationHandler ; SstWSSoapFaultPayloadGenerationHandler |
wsServiceRequestServerHandler | SstWSBodyProcessor |
wsServiceResponseServerHandler | SstWSNoOperationHandler |
wsServiceFaultServerHandler | SstWSNoOperationHandler |
wsGlobalClientInputHandler | SstWSNoOperationHandler |
wsGlobalResponseClientHandler | SstWSHeaderProcessor |
wsGlobalFaultClientHandler | SstWSNoOperationHandler |
wsServiceRequestClientHandler | SstWSTransportDeterminationHandler |
wsServiceResponseClientHandler | SstWSNoOperationHandler |
wsServiceFaultClientHandler | SstWSNoOperationHandler |
Table 1
SstWSBodyProcessor
Process any body elements that appear in the request. The default algorithm for handling SOAP body messages is the same as SstWSHeaderProcessor.
• Check the handler registry for a handler with name/namespace that matches the body element. If a handler is found, invoke it.
• If no handler is found, then retrieve a method mapping form the service manager using the name and namespace of the body element, and invoke the method.
• Results of executing the message are written to the message context using the addResult: API. The results are serialized as the payload of the SOAP Body element in the response message.
SstWSHeaderProcessor
Process any headers that appear in the message. The default algorithm for handling SOAP headers is as follows:
• Check the handler registry for a handler with name and namespace that matches the header element. If a handler is found, invoke it.
• If no handler is found, then check for a service and method mapping in the service manager using the name and namespace of the header element. If the service exists, but no method name was specified, use the default method, process:context:. Invoke the method passing the header element and the message context as arguments.
• Results of executing a header message are written to the message context using the addResultHeader: API. The results are passed back as SOAP header entries in the response stream.
SstWSNoOperationHandler
This handler is an embodiment of the null object pattern. It is included as a placeholder to retain logical flow. This handler appears repeatedly in both client and server flows when no specific action is required.
SstWSTransportDeterminationHandler
Interrogates the WSDL document and attempts to determine which binding should be used to invoke a Web service. Based upon this information, the correct transport key is set in the Web services message context for future use by the transport handler.
Transport Handlers
Because transports are pluggable, an extra level of indirection is needed to specify the transport handler. Specifically, three additional transport identifier keys have been defined that are used in conjunction with the message context and the message processing engines. The additional keys are
• WSTransportResponseIdentifier
• WSTransportRequestIdentifier
• WSTransportFaultIdentifier
These keys are used by the transport endpoints. For example, when the SstWSServlet receives a request it asks the container for a new message context for that message. It then places the names of the transport handlers e.g.WSHttpServerRequest, in the context using the known transport identifier keys. When the transport handlers are to be invoked, the invocation framework gets the proper name from the message context, and combined with the global namespace, looks for the handler in the Handler Factory.
aMessageContext
addProperty: WSHttpServerRequest
named: WSTransportRequestIdentifier;
addProperty: WSHttpServerResponse
named: WSTransportResponseIdentifier;
addProperty: WSHttpServerResponse
named: WSTransportFaultIdentifier.
| |
wsHttpServerRequestHandler | SstWSHttpSoapActionHandler |
wsHttpServerResponseHandler | SstWSHttpResponseHandler |
wsHttpServerFaultHandler | SstWSHttpResponseHandler |
wsHttpClientRequestHandler | SstWSHttpDispatchHandler |
wsHttpClientResponseHandler | SstWSNoOperationHandler |
wsHttpClientFaultHandler | SstWSNoOperationHandler |
SstWSHttpSoapActionHandler
Check for the presence of the SOAPAction HTTP header. Issues a Client fault if the SOAPAction header is not found. Currently, the SOAPAction value is not used by VAST Web services implementation.
SstWSHttpDispatchHandler
This handler is responsible for managing the request response nature of messages sent over HTTP. Currently, this handler will make a request and block until the request is received.
SstWSHttpResponseHandler
Writes HTTP response headers including return code and content type for the response.
Serialization Handlers
Unlike message and transport handlers, serialization handlers do not participate in the processing flow. Instead, they are used by the message context to create and serialize the proper types of messages. In this manner, a level of pluggability is achieved as new message types may be plugged into the framework.
| |
wsServerInputMessageConstructor | SstWSSoapDeserializationHandler |
wsServerOutputMessageConstructor | SstWSSimpleChain ; ;SstWSSoapEnvelopeGenerationHandler ; SstWSSoapOutputPayloadGenerationHandler |
wsClientInputMessageConstructor | SstWSSimpleChain ; SstWSSoapEnvelopeGenerationHandler ; SstWSSoapInputPayloadGenerationHandler |
wsClientOutputMessageConstructor | SstWSSoapDeserializationHandler |
SstWSSoapEnvelopeGenerationHandler
This handler is responsible for creating the top-level object representation of the SOAP envelope.
SstWSSoapFaultPayloadGenerationHandler
This handler is used as an adapter which converts an incoming exception into a SOAP fault. When the exception is signaled, it will have two arguments, the fault name, and the fault itself. The fault is used as the result to build messages for the wire.
SstWSSoapInputPayloadGenerationHandler
This handler utilizes the input message part when building the payload for the SOAP message.
SstWSSoapOutputPayloadGenerationHandler
This handler utilizes the output message part when building the payload for the SOAP message.
Message Processing
Fundamentally, managing the process of sending a remote message can be broken down into three broad steps; prepare the message for transport (object serialization), send the message over a transport and wait for a reply (in the case of RPC), and lastly, prepare and return the results (deserialization).
The process for receiving a remote message is not much different. Information is taken off the transport and converted into objects (deserialization), behavior is invoked on the receiver, and results are collected and placed back on the wire (serialization).
Regardless of sending or receiving a message, the Web services framework will automatically serialize and deserialize directly into objects.
In order to provide the Web services developer with the highest degree of flexibility, handlers are used to control the steps outlined above. There is significant overlap in the general approach used for defining handlers to host a service and defining handlers to invoke a service. The section below illustrates each of the basic parts of the handler flow and describes the default handlers that are invoked during processing at that stage.
Message Processing, A Sever Perspective
There is a logical flow to message processing that is controlled by the server engine. The Message Processing Server figure illustrates how messages pass through the framework. Notice that the handler groups are further broken down into those that handle the incoming message, and those that handle the outgoing message. Although this is a logical separation, this does not place a restriction that separate handlers be used. The behavior of each handler is triggered by sending the invoke: method. This method takes an instance of SstWsMessageContext as its argument. In the server, the process originates at the actual network endpoint. As the information is passed through the handler chains, it is ultimately sent to the user application through an instance of the class SstWSService.
Figure: Message Processing Server During message processing, the framework must make a decision that it is no longer processing input, but preparing output. When this occurs, we say that the message context has past the pivot from input to output (sometimes correlated to request/response). On the server, the pivot is passed when the SstWebService returns from the method send into the user application. In the scenario above, the pivot is passed between service handler invocation.
Soap Headers
SOAP defines a construct called a SOAP Header which allows the message contents to be extended to include additional information. In order to process SOAP Headers, a Web service must define and register custom message handlers. The handler name and namespace must match the header element name and namespace.
While executing, the handler can access the active header element using the message context reserved key SstWSConstants::WSCustomHandlerElement (##wsCustomHandlerElement).
Header elements that are included in the WSDL abstract response message can be saved in the message context using the method
#addResultHeader:. During serialization, the result header collection is added to the SOAP Header of the outgoing message. The
Web services insurance example contains an operation that demonstrates this behavior.
Message Processing, A Client Perspective
Messaging from a clients perspective occurs in much the same manner as on the server. In contrast, however, the transport handlers are at the pivot point. This is because the message must be sent over the transport, and a handler must coordinate receiving the response (currently the receiver blocks until the endpoint returns).
Figure: Message Processing Client In either case, it is the instance of SstWSService that acts as a façade for the user application. This reinforces the notion of location transparency and allows the application architect to freely move services around the network.
The message context object protocol propertyNamed:, and addProperty:named:, enable simple storage and retrieval of vital information during message processing. In addition, the message context is used to encapsulate the serialization and deserialization of Web services messages through the currentMessage API. Any required information that needs to be shared among handlers should be stored in the message context.
Exception Processing
Exception handling during message processing follows the standard Smalltalk semantics. From the clients perspective, a SOAP fault received as the result of a remote message sends will cause a Smalltalk exception to be raised. In the development environment, this manifests itself as a walkback in the debugger. This allows application developers to wrap remote invocation inside normal when:do: exception blocks.
Conforming to the standard Smalltalk exception semantics also simplifies development of hosted services. For example, to return a SOAP fault, the application only needs to raise an exception. The framework will automatically convert it into a SOAP fault and serialize it correctly on the outbound transport.
Depending on the perspective of the message send, processing begins with an instance of either SstWSClientEngine, or SstWSServerEngine. Since these are handlers, the entry API is invoke:
SstWSBasicHandler>>invoke: aMessageContext
[ self basicInvoke: aMessageContext ]
when: ExError
do: [:signal | self handleException: signal with: aMessageContext.]
The implementation is to call basicInvoke: wrapped by an exception handler. Subclasses of SstWSEngine implement basicInvoke: to lookup and call the handlers defined in the container. The default exception handler looks up the defined fault handlers and invokes them. Because message processing is inside an exception block by default, to participate in exception processing, a handler need only implement the invoke: method.
Note:
Server Fault message construction
When a Web services client receives a SOAP fault response from the server, standard Web services handler chains are traversed prior to signalling an exception to report the SOAP fault. Therefore, fault chains are traversed after standard client processing has already completed. Custom client chains may need to be aware of the possibility that the message result could be a fault. Ideally, the Web services support should circumvent standard processing and branch immediately into the fault handlers after discovering a SOAP fault in the output message.
Client handlers can use the message #isSoapFault to verify that message results contain expected content.
fault := anSstWSMessageContext results
detect: [:each | each isSoapFault]
ifNone: [ ].
Custom Exception Processing
Users are free to implement their own exception processing routines. Generally, there are two techniques that apply. The chosen technique should be determined by the circumstances of each individual application.
1. Custom handler assumes complete control. In this scenario, a custom handler has been deployed into the container. This handler implements the following methods; basicInvoke:, handleException:with:. This indicates to the message processing framework that the exception has been completely handled. It is up to the user to ensure all messages return properly to the ultimate sender. It is recommended that only advanced users adopt this approach.
2. Custom handler 'participates' in exception processing. This approach is similar to the one above in that a handler is introduced that implements basicInvoke:, handleException:with:. However, upon completion of the handleException: method, an exception is signaled again. This could be the same exception that is passed as an argument to the method, or, it can be a new exception created by the handler. The only caveat is that it must be a child of ExAll. When the exception is signaled, it will be caught by the 'outer' exception block in the engine and handled appropriately. This is the recommended approach.
Errors During Exception Processing
The default exception processing routine implemented in the engine handlers is to add the signal to the message context and invoke the appropriate fault handlers, for example,
SstWSServerEngine>>handleException: signal
with: anSstWSMessageContext
anSstWSMessageContext addProperty: signal named: WSSignal.
self invokeFaultHandlers: anSstWSMessageContext.
In an attempt to provide maximum protection against unforeseen problems the implementation of invokeFaultHandlers: has been wrapped inside of an exception handler block, e.g.
SstWSServerEngine>> invokeFaultHandlers: anSstWSMessageContext
[ self invokeGlobalFaultHandlers: anSstWSMessageContext.
self invokeServiceFaultHandlers: anSstWSMessageContext.
self invokeTransportFaultHandlers: anSstWSMessageContext.]
when: ExError
do::signal |
self handleFatalException: signal with: anSstWSMessageContext]
Because of the drastic nature of when handleFatalException:with:, it only attempts to report the error to the user and recover as gracefully as possible. It is the user's responsibility to determine the proper corrective course.
Adding headers to extend message behavior
For some cases, it is necessary to extend the behavior of a message. SOAP enables extension of messages using the SOAP Header element.
Using the VAST Web services support, there are two ways to include SOAP Header entries in messages.
• Modify WSDL to include header extension information
• Add header elements using custom Web service handlers
Both of these techniques for extending the message behavior are demonstrated by the
Web services insurance example which is discussed in a later chapter.
Modify WSDL to include header extension information
Modification of the WSDL for a service is only practical if your organization controls the interface for that service. To construct SOAP headers automatically during processing of an outgoing Web service message:
• Add an argument to the abstract WSDL message which will represent the outbound header.
• In the WSDL binding, add a header extension to the SOAP input definition of the operation that will pass the header.
• Modify the body extension of the SOAP input definition to list the parts that should be included in the SOAP Body. If the parts attribute is absent from the body extension, the VAST Web services support assumes that all parts within the abstract message are intended for the SOAP body.
When invoking an operation that contains header arguments, include the header arguments in the argument list for the operation. VAST Web services support always expects messages to model the appearance of the abstract WSDL operation. Passed arguments must appear in the order that they are defined in the abstract message. During serialization, the Web services framework places the provided message arguments in the correct block within the SOAP message.
Add header elements using custom Web service handlers
Applications can also add header entries to outbound SOAP messages using custom handlers. This may be desirable when including common information in all outgoing messages. Handlers can add elements to the outgoing SOAP header using the #addHeaderElement: method of the SstSoapEnvelope. Be aware that all added header elements must be capable of properly rendering themselves as XML.
Deployment Descriptors
There are many aspects of a Web service environment that rely on external files. WSDL documents are an obvious example, which themselves rely on XML schema. Often, the contents of these files are resolved at runtime. In order to provide an external mechanism for describing Web services, the concept of a deployment descriptor has been introduced. This section will detail deployment and describe how to effectively utilize the deployment descriptor.
A deployment descriptor is an XML file that conforms to the schema defined in the sstwsdep.xsd file. These files are used for both containers and services. Although one file can deploy both containers and services, it is common practice to have a separate deployment descriptor file for each set of these related artifacts.
The deployment descriptors used by VAST Platform (VA Smalltalk) can be divided into two categories: those that specify container information and those that specify service specific information. Container deployment descriptors define the configuration for the container, while service descriptors will include additional information regarding service provision.
For the sake of simplicity, collections of elements are contained in a top-level tag. For example, <imageComponentUrls> is a top-level tag that represents a collection of <imageComponentUrl> tags. While reviewing this section it may be helpful to browse the sample files SstWSSampleContainer.xml and SstWSInsurancePolicyInterface.xml regardless of whether the deployment descriptor is describing a container or a service, all deployment descriptors define the following meta-information:
Deployment Descriptor Version - urn:VASTWebServiceDeployment600 represents the URI of the VAST Web services deployment descriptor.
Image components - This entry specifies the URLs that point to the ICs to load as the first step in service deployment. For ICs only, these must resolve to a local file. It is expected that all code necessary to support deployment be contained in these ICs.
Note:
It is recommended that image components be used strictly as a runtime architecture. Image component usage within a development environment is not recommended. Further, ICs are not available on every platform. Please see the IC section of the VAST Platform documentation.
Mapping Specifications - This section specifies a list of resolvable URLs, e.g. http://myhost/myMappingSpec, that are utilized by the XML parser to map class names to tag names. See the XML guide for additional information about mapping specifications.
Handlers - This section is used to define the handlers that will be utilized by the framework during message processing. Each handler is defined with the key that will be used to find it in the Handler Factory. For container handlers, the Handler Factory utilizes the global namespace attribute when caching entries in its registry.
When defining a handler, the Smalltalk class that implements the desired behavior, or another handler may be specified. When specifying a class, the class attribute is utilized. When specifying a reference to another handler, the type attribute is used.
Container Specific Deployment Information
As mentioned above, deployment descriptors are broken down into two broad sections, one for container information and one for service information. The deployment descriptor uses two enclosing tags to represent the internal separation of these segments, <container> and <service> respectively.
There are many ways of deploying a service, most of which rely on resolving information from external locations. Up to now, we have only discussed service deployment. However, container deployment works in the same manner with a few considerations.
For the most part, the deployment descriptor is simply the serialized form of many common Web services constructs. An example of this is the <configuration> tag. As its name implies, this tag represents a container configuration object (SstWSContainerConfiguration). In fact, both the serialization and deserialization of the configuration object utilizes XML schema based mapping support.
The container configuration is the object that will specify the global namespace, the SOAP actor roles, all factories and managers, and any services to be deployed. In the configuration, individual managers, those that reside in an instance variable in the container, are specified in their own tag. An example is the Deployment Manager, which is described in the <deploymentManager> tag.
The managers collection may also be specified in the deployment descriptor. The SstWSSampleContainer.xml file illustrates how to specify the pre-defined managers, e.g. the Port Manger. However, this is not a finite list. Framework users may include additional managers of their design simply by following the pattern in the example deployment descriptor. At runtime, the custom managers may be retrieved from the container by using the managerNamed: protocol on the container.
The final container specific section of the deployment descriptor indicates any services to be loaded when the container starts up. Each entry in this section should point to a resolvable URL that can describe a Web service, typically a WSDL document or a service specific deployment descriptor. This section is provided as an option to including a separate services section.
Service Specific Deployment Information
Service deployment works in the same manner as container deployment. All information relating to a collection of services is included in an enclosing XML tag named <services>. Each individual entry has its own <service> tag.
One of the most important aspects of describing the service is the name and namespace. The service name and namespace are the mechanism used to link together deployment information and information specified in the WSDL implementation document. The entries in these documents MUST BE THE SAME, otherwise, the Web services framework will not be able to determine which service corresponds to the parsed in WSDL information.
The next piece of information specified by the service deployment descriptor is the service interface class. This is the class that the framework utilizes as the link between the framework and the user application. For remote services, this class acts as a proxy and is the start of the invocation chain for network messages. Hosted services utilize this class as a thin layer to wrap the application interface, referred to as the provider class. The provider class is the user application class representing the business logic interface. An instance creation method attribute can be provided to facilitate specialized instance creation.
The final aspect of service deployment is mapping WSDL operation names to Smalltalk selectors. Currently, the framework requires that all operations inside of the WSDL document be mapped. Below is an example operation mapping from the SstWSInsurancePolicyInterface.xml document.
<operation name="ratePolicy:" qName="swsipi:ratePolicy"></operation>
This maps the Smalltalk selector ratePolicy: to the fully qualified service operation swsipi:ratePolicy. In the WSDL document for the service, the swsipi prefix maps to the namespace urn:SstWSInsurancePolicyInterface which is defined as the namespace of the serialized soap:body. By default, the Web services framework will dispatch the incoming message and place the results in the message context.
However, under certain circumstances, the user application may need to assume complete control of the incoming or outgoing messages. In this scenario, an operation flow handler should be used. Here is an example of how the flow handler may be defined:
<operationHandler name="companyInfoHandler"
class="SstWSServiceOperationHandler">
<input name="companyInfoInputHandler"
class="SstWSSampleCompanyInfoHandler"/>
<output name="companyInfoOutputHandler"
class="SstWSNoOperationHandler"/>
<fault name="companyInfoOutputHandler"
type="vastwsc:wsServiceFaultServerHandler"/>
</operationHandler>
This handler from the insurance examples deployment descriptor defines the getCompanyInfo method utilizing an operation handler.
<operation qName="swsipi:getCompanyInfo"
flow="swsipi:companyInfoHandler"></operation>
With one exception, operation handlers work in the same manner as all other handlers. During service invocation, this handler is found and its invoke: API is called. Internal to this invocation, the handler will determine the current state of the message context and invoke the corresponding nested handler, e.g. the input, output, or fault handler.
Service Undeployment
Taking a service off-line is known as undeployment. In the VAST Web services framework, the same artifacts used to deploy the service are also utilized during service undeployment. It is important to note that undeployment is not a complete undo of deployment. In some circumstances, specific relics of your service may still reside in the image. An example where information may linger in the image is image components. If a service is deployed using image components, no attempt is made to unload these components during undeployment.
Although the recommended practice when undeploying a Web service is to utilize the same artifact that deployed the service, advanced users may substitute a compatible artifact. For example, deployment descriptor A specifies three services, X, Y, and Z. It may be desirable to undeploy only service Z. To accomplish this, a deployment descriptor for Z is created that will be used by the framework to remove the artifacts associated with this service.