WSB provides a set of functions that enable you to create a server application that acts as a host and broker for web service requests sent from client applications and processed by service applications. Note the distinction between the roles of the client application, the server application and the service application:
Note: The WSB library will not run with OmniMark Single Processing Engine or with the OmniMark Fast VM under OmniMark Studio for Eclipse (the "Execute Project" option).
The WSB library provides functions that can be used by the server application to host services, route requests, and process requests for which no service exists. It also provides functions that can be used by service applications to implement a service and to receive and interpret a request. The WSB library does not provide functions for use in web service clients. However, the functions required by clients to communicate with servers and to interpret responses are provided by OmniMark's TCP/IP library and by OmniMark's built in XML processing capability.
The WSB library runs service applications in threads, allowing the WSB server to handle multiple simultaneous requests.
Web services may use a variety of communication protocols (SOAP, XML-RPC, etc) and a variety of transport mechanisms (HTTP, Mail, etc.). The current version of the Web Services Broker library supports SOAP 1.1, XML-RPC, and user-defined protocols over HTTP.
Here is an example of a WSB application using the SOAP protocol. It consists of four programs, the server, the service, a client, and a "die" client, designed to shut down the server remotely. The example implements a time service.
This example, as well as examples using XML-RPC and a user-defined protocol, are included with your OMWSB installation. Please note that these samples are designed to illustrate the operation of the Web Service Broker and should not be taken as complete or robust implementation of the SOAP or XML-RPC protocols.
This is the code for the server.
import 'omwsb.xmd' prefixed by wsb. process local wsb.server server ; set up the server set server to wsb.create-server on 3556 of-type wsb.soap-service ; build the service map wsb.add-service to server named "http://omnimark.com/TIME" handled-by "times.xvc" ; start the server wsb.start-servicing-requests on server put #error "OMWSB Server started on port 3556%n" repeat local wsb.request request ; Wait for requests. Requests not in map handled here. set request to wsb.wait-for-request on server do when (wsb.request-name of request) = "http://omnimark.com/DIE" local stream responder open responder as wsb.writer of request put responder "HTTP/1.0 200 OK%13#%10#" || "Content-Type: text/xml; charset=%"utf-8%"%13#%10#" || "Content-Length: 0" || "%13#%10#%13#%10#" close responder log-message "DIE action received. Shutting down.%n" exit else ;else report bad request log-message "Unknown request received." log-message "Header: " || wsb.request-header of request log-message "Body: " || wsb.reader of request done catch #external-exception identity catch-id message catch-msg location catch-loc log-message '%g(catch-id) : %g(catch-msg)%n%g(catch-loc)' ; exit if server has fatal error rethrow when catch-id = "OMWSB000" again
Here is an example of a WSB service application. It implements a time service. The server program above is designed to act as host and broker for this service. Note that this program must be compiled to create "times.xvc" and that the "times.xvc" file must be in the current working directory of the server program.
; times.xom ; a time service import 'omwsb.xmd' prefixed by wsb. include 'omdate.xin' global stream the-time declare catch parse-error reason value string message process repeat local wsb.request request local stream responder local stream response ; Parse the request set request to wsb.wait-for-request using group process-soap do xml-parse scan wsb.reader of request suppress catch parse-error reason message open responder as wsb.writer of request put responder "HTTP/1.1 404 Invalid request%13#%10#%13#%10#" put responder "XML error in request:%n" || message || "%n" close responder exit done ; Build the response set response to "<SOAP-ENV:Envelope%13#%10#" || " xmlns:SOAP-ENV=%"http://schemas.xmlsoap.org/soap/envelope/%"%13#%10#" || " SOAP-ENV:encodingStyle=%"http://schemas.xmlsoap.org/soap/encoding/%"%13#%10#" || " xmlns:time=%"http://schemas.omnimark.com/time%">%13#%10#" || " <SOAP-ENV:Body>%13#%10#" || " <time:current-time>%13#%10#" || the-time || " </time:current-time>%13#%10#" || " </SOAP-ENV:Body>%13#%10#" || "</SOAP-ENV:Envelope>%13#%10#" open responder as wsb.writer of request put responder "HTTP/1.0 200 OK%13#%10#" || "Content-Type: text/xml; charset=%"utf-8%"%13#%10#" || "Content-Length: " || "d" % length of response || "%13#%10#%13#%10#" || response close responder catch #external-exception identity catch-id rethrow when catch-id = "OMWSB000" again ;ensure a clean exit catch #program-error code error-code message error-message location error-location log-message ('An error occurred in the TIME service:%n' || '%d(error-code): %g(error-message)%n%g(error-location)%n') ;rules for processing the SOAP packet group process-soap element #base "Envelope" suppress element #base "Body" suppress element #base "request" suppress element #base "zone" local stream zone initial {"%c"} set the-time to ymdhms-to-arpadate ymdhms-adjust-time-zone now-as-ymdhms to-be zone markup-error throw parse-error reason (#message || " At line %d(#line-number)") group #implied
Here is a client that uses the time service.
import 'omtcp.xmd' prefixed by tcp. include "omdate.xin" global integer port initial {3556} global stream hostname initial {"localhost"} define string source function my-time-zone as output now-as-ymdhms drop any{14} ;default to local time zone global stream zone initial {my-time-zone};{now-as-ymdhms drop any{14}} define string source function generate-soap-request action value string soap-action as local stream contents set contents to "<SOAP-ENV:Envelope%13#%10#" || " xmlns:SOAP-ENV=%"http://schemas.xmlsoap.org/soap/envelope/%"%13#%10#" || " SOAP-ENV:encodingStyle=%"http://schemas.xmlsoap.org/soap/encoding/%"%13#%10#" || " xmlns:time=%"http://schemas.omnimark.com/time%">%13#%10#" || " <SOAP-ENV:Body>%13#%10#" || " <time:request>%13#%10#" || " <time:zone>" || zone || "</time:zone>%13#%10#" || " </time:request>%13#%10#" || " </SOAP-ENV:Body>%13#%10#" || "</SOAP-ENV:Envelope>%13#%10#" output "POST /SOAPServer HTTP/1.0%13#%10#" || "Host: www.soapserver.com%13#%10#" || "Content-Type: text/xml; charset=%"utf-8%"%13#%10#" || "SOAPAction: " || soap-action || "%13#%10#" || "Content-Length: " || "d" % length of contents || "%13#%10#%13#%10#" || contents process local tcp.connection conn ;check for valid time zone format do unless zone matches (["+-"] digit{4}) log-message ('Incorrect time zone format "' || #args[1] || '"%n' || "Use '-'/'+' hhmm format. E.g. +0200") halt with 1 done set conn to tcp.connect to hostname on port tcp.put-string to conn from (generate-soap-request action "http://omnimark.com/TIME") ; receive response do scan tcp.reader of conn ; See if valid response. If so, decode data. Otherwise, report error match (any** "HTTP/" digit+ "." digit+ blank+ digit{3} => response-code) (any** "Content-Type:" blank+ [\ " ;"]+ => content-type)? (any** 'charset="' any++ => charset '"')? (any** "Content-Length:" blank* digit+ => content-length)? (any** "%13#%10#%13#%10#")? do when response-code = "200" do xml-parse scan #current-input suppress done else log-message ("HTTP error. Code=" || response-code) done match lookahead any log-message ("Invalid response received:%n" || #current-input) halt with 1 done ;------------------------------------------------------- ; SOAP Processing rules ; element #base "Envelope" suppress element #base "Body" suppress element #base "current-time" log-message "%nThe time is: " || "%sc" drop white-space* || "%n"
It is important that you shut down a WSB server by sending it a message telling it to shut down. If you shut it down by killing the server process, (for example by typing Ctrl-C on the command line) the service programs hosted by the server will not have an opportunity to clean up any resources they are using. Here is a client that will send a shutdown command to the server application shown above.
import 'omtcp.xmd' prefixed by tcp. global integer port initial {3556} global stream hostname initial {"localhost"} define string source function generate-soap-request action value string soap-action as output "POST /SOAPServer HTTP/1.0%13#%10#" || "Host: www.soapserver.com%13#%10#" || "Content-Type: text/xml; charset=%"utf-8%"%13#%10#" || "SOAPAction: " || soap-action || "%13#%10#" || "Content-Length: 0" || "%13#%10#%13#%10#" process local tcp.connection conn set conn to tcp.connect to hostname on port tcp.put-string to conn from (generate-soap-request action "http://omnimark.com/DIE")