Posted:
6-July-2006 12:22
HowTo Build a SOAP Service
This howto explains the steps to follow to build a SOAP
web-service on NetKernel.
Specification
The service will be exposed at a URL endpoint of:
http:/localhost:8080/howto/soap/service
The service will have one port that echos back the SOAP message
it recieves. The port will have the SOAP action URI:
urn:org:ten60:howto:soap:service:echo
Recipe
-
Create a module and configure the mapping section like this:
<mapping>
<this>
<match>ffcpl:/etc.*</match>
<match>ffcpl:/resources/.*</match>
</this>
<import>
<uri>urn:org:ten60:netkernel:mod:ws</uri>
</import>
<import>
<uri>urn:org:ten60:netkernel:ext:dpml</uri>
</import>
<super
/>
</mapping>
The <this>
section makes sure that resources in the /etc and /resources
directories may be found in the module's address space. The imports hook-in the folloing
libraries:
| urn:org:ten60:netkernel:mod:ws |
- provides the soap mapper and message processing tools |
| urn:org:ten60:netkernel:ext:dpml |
- provides the DPML runtime for scripting the echo
service. |
- Import your module into the Front-End fulcrum so that it is
exposed to the HTTP transport running on port 8080.
-
Edit your module definition to export a public interface:
<export>
<uri>
<match>service:(wsSOAPServer|wsdlGenerator|wsDocumentation).*?/howto/soap/service.*</match>
</uri>
</export>
This rule will capture all pre-processed SOAP service requests
coming from the HTTPBridge in the fulcrum module that match our
service endpoint /howto/soap/service.
The HTTPBridge will automatically treat any URL request with a
path containing /soap/ as being a SOAP service request using the
HTTP protocol binding. In this 'soap' mode the HTTPBridge
automatically extracts the soap message from the body of the HTTP
request together with the endpoint and action HTTP headers. The
HTTPBridge collects all this information and presents it as a
uniform active URI. For our service the Bridge will pre-process the
HTTP request and issue an internal active URI request:
| service:SOAPServer
|
|
|
+endpoint@http://localhost:8080/howto/soap/service
|
|
+action@urn:org:ten60:howto:soap:service:echo
|
|
+message@[somemessage]
|
[Don't worry about the other service request types,
wsdlGenerator and wsDocumentation - we'll show how these can be
wired up to provide WSDL and human readable service descriptions
below.]
-
Now add this rewrite rule to your module definition.
<rewrite>
<rule>
<match>service:((wsSOAPServer|wsdlGenerator|wsDocumentation).*)</match>
<to>active:$1+config@ffcpl:/etc/SOAPServiceMap.xml</to>
</rule>
</rewrite>
This rule will turn the service:wsSOAPServer request into an
active:wsSOAPServer request and in the process attaches a config
argument that tells active:wsSOAPServer where the SOAP service map
is located. active:sSOAPServer is a specialist mapper service, its
job is to route the external SOAPy requests to an internal active
URI request for a service implementation.
-
Create the SOAP service map at /etc/SOAPServiceMap.xml
<SOAPServiceMap>
<service>
<name>HowTo SOAP Service</name>
<endpoint>/howto/soap/service</endpoint>
</service>
<port>
<name>Echo</name>
<type>doc</type>
<endpoint>/howto/soap/service</endpoint>
<action>urn:org:ten60:howto:soap:service:echo</action>
<int>active:dpml+operand@ffcpl:/resources/echo.idoc</int>
</port>
</SOAPServiceMap>
Here the service tag contains a named endpoint matching our
public endpoint URL. The port tag contains all the information
needed by the wsSOAPServer to locate the internal service
implementation of the echo service. So the port name is Echo, its
type is 'doc' - meaning we expect document/literal soap messages
(not RPC), the endpoint and action tags match our service endpoint
and action specification. Finally the int tag provides the URI call
to the internal implementation for the service - in this case it is
a call to the DPML runtime to run /resources/echo.idoc. The
wsSOAPServer will issue a request for the int URI with the addition
of an argument called message containing the incoming
SOAP message. Within the implementing script the message can be
processed with the usual "this:param:message" syntax.
The service map has one service tag per endpoint. Each endpoint
may have any number of ports and each port must have a unique
action.
-
Implement the echo service at /resources/echo.idoc by copying
and pasting this script:
<idoc>
<seq>
<instr>
<type>copy</type>
<operand>this:param:message</operand>
<target>this:response</target>
</instr>
</seq>
</idoc>
This script echos the message it receives back as its response.
You could of course use any of NetKernels supported languages to
implement this service. Just change the int service implementation
URI appropriately.
-
The service is finished. Now write a test soap client. Copy the
following script into the workbench module at a location
/howto/testClient.idoc
<idoc>
<seq>
<instr>
<type>wsSOAPClient</type>
<endpoint>http://localhost:8080/howto/soap/service</endpoint>
<action>urn:org:ten60:howto:soap:service:echo</action>
<message>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<p:message xmlns:p="urn:org:ten60:howto">Hello World</p:message>
</soapenv:Body>
</soapenv:Envelope>
</message>
<target>this:response</target>
</instr>
</seq>
</idoc>
This script uses the wsSOAPClient accessor to call the soap echo
service. You can run this script at http://localhost:8080/workbench/howto/testClient.idoc
Useful patterns and tools
This howto shows how to connect an external SOAP service to an
internal NetKernel implementation. The typical pattern for the
internal implementation is to extract a message body part. Process
it. Construct a new SOAP message. Set some body value and issue the
message as the service's response.
The mod-ws module provides many utility tools for slicing and
dicing SOAP messages without the pain of dealing with the XML
directly. See for example wsSOAPGetBody for an 'extract by example'
tool to get at a SOAP body part.
Documenting the Service
If you make a browser request for
http://localhost:8080/howto/soap/service?DOC - you will see an
auto-generated service description. When you call the endpoint with
a ?DOC query parameter the HTTPBridge knows that this is a request
for documentation and so it issues a request for
service:wsDocumentation+[....]. Earlier we created a rewrite rule
that mappted service:wsDocumentation to the active:wsDocumentation
engine. With no arguments, this accessor simply styles the SOAP
service map to a human readable HTML form.
You can serve human readable documentation for each port by
adding a doc tag to the port specification. Like int, this should
provide the internal URI request to a script or service that
provides an HTML document describing the port. Note this *must* be
an active URI since, just like the wsSOAPServer, the
wsDocumentation server issues the internal request for the URI but
adds the action and endpoint arguments.
If you have a strong stomach you can add a WSDL description by
adding a 'wsdl' tag to the main service description tag. This
should point to the internal URI of the WSDL document. A request
for http://localhost:8080/howto/soap/service?WSDL will serve this
WSDL service description for the endpoint. WSDL has the unique
distinction amongst standards as being a service metadata format
that is invariably neither human nor machine readable - well done.
Call me old fashioned but I think that a well written human
readable HTML engineering specification can't be beaten.
Manualy configuring the HTTPBridge to use SOAP mode
You might prefer to control the public URL endpoint interface
more specifically than relying on the default /soap/ match we used
above. Here's how:
- On the public interface of your module export
ffcpl:/etc/HTTPBridgeConfig.xml
-
In your module create a file /etc/HTTPBridgeConfig.xml with the
following contents
<HTTPBridgeConfig>
<!---->
<zone>
<match>.*/mymodule/path1/path2/myservice.*</match>
<soap />
</zone>
</HTTPBridgeConfig>
Any URL request that matches this zone will be processed using
the SOAP HTTP protocol binding rules - you'll have to take care of
the endpoint matching and mapping in your module to work with the
new soap service path.
|