Introduction

CDuce_WS is a library for Web Services prototyping. It can be used alone for the creation of clients programs, and with OcCDuce for servers.

Basically, CDuce_WS is a CDuce representation of the SOAP protocol structures (SOAP Envelope, SOAP Encoding, etc.) with some helper functions to ease the programmation of Web Services. Additionally, a WSDL structure is also provided, as well as some functions (adapted from OCSoap) to extract useful information from a WSDL file.

Installation

CDuce_WS requires:

Alternatively, you can use GODI to compile and install all the dependencies.

You must first download the archive file of version 0.1 and untar it in a temporary directory.

To compile and install it, executes make and make install in the base directory.

You should look in the examples directory and try to compile and install them, once CDuce_WS is installed.

First example

This example corresponds to the example in the directory: examples/echo/.
It is a very simple service that receives an integer from a client and sends it back.
The first file, common.cd, contains all the declarations used by the client and the server, and is included by all other files. The type Msg corresponds to the information that a client sends to the server, while the type Answer embeds the response from the server to the client.

(* file common.cd *)

namespace on;;
using SoapEnv = "soapenv";;
namespace ns = "urn:echo"

type Msg = <ns:echoInteger>[
  <inputInteger>Latin1
];;

type Answer = <ns:echoIntegerResponse> [
  <return>Latin1
];;

The next file, ws_echo.cd, contains the code necessary to register a web service in Ocsigen, via OcCduce.
The call to Lib.register_webservice registers 2 services in Ocsigen:

  • wsEcho, which is called when a client requests the URL with the POST method. The first argument corresponds to the POST parameters, the second argument to the GET parameters and the third one is the content of the request. POST and GET parameters are empty in case of a web service. The content of the request is the SOAP message as a string (actually a Latin1 string).
  • wsError, which is called if the client uses the GET method (hopefully, it should not happen).

NOTE1: it is not possible to load more than once the same CDuce program in OcCDuce. Therefore, you should never give the same name to the source files of different services if you plan to load them simultaneously.

NOTE2: Sometimes, the namespaces used by the client and the server do not match. This problem can be avoided by using 3 functions: Cduce_ws.load_xml_subst, Cduce_ws.print_xml_subst, and Occduce_lib.set_subst_uri. Both load_xml_subst and print_xml_subst are equivalent to load_xml and print_xml with an additional parameter, a list of pairs of URIs. For each pair of URIs, the first one is the original (i.e. used in the in the other parameter), and the second URI is the new URI. Every original URI will be substituted by the new one in the returned value.
set_subst_uri also takes a list of pairs of URIs as a parameter, and it must be used before returning the XML value in the server. The namespaces of the XML value returned will be converted before it is sent to the client.

(* file ws_echo.cd *)

include "../common.cd"
using Echo = "echo";;
using Lib = "occduce_lib";;

let wsError (_ : []) : AnyXml =
  SoapEnv.std_soap_fault;;

let wsEcho (_ : []) (_ : []) (content : Latin1) : AnyXml =
  Occduce.debug_string "wsEcho\n";
  try
    (let a = Echo.echo content in
     Occduce.set_subst_uri 
       [("http://schemas.xmlsoap.org/soap/envelope",
	 "http://schemas.xmlsoap.org/soap/envelope/")]; 
     a)
  with err & Latin1 ->
    Occduce.debug_string [ 'Cduce exception: ' !err '\n'];
    exit 2;;

let ws_echo = 
  Lib.register_webservice
    { path = ["ws_echo"]; serviceName = "wsEcho" }
    wsEcho wsError

The next file, echo.cd, contains the processing of the SOAP message and the generation of the response message.
load_xml_subst converts the string containing the SOAP message in a SOAP XML structure after the substitution of the URIs (obviously, using namespaces substitution in the server and client code is not useful).
The body of the SOAP structure, a list of AnyXml, is extracted with the function SoapEnv.get_body, while the function SoapEnv.hd returns the first XML structure in the list.
After the processing of the message and the creation of the response message, SoapEnv.add_envelope embeds it in a SOAP envelope.

(* file echo.cd *)

include "../common.cd"

let echo (s : Latin1) : SoapEnv.Envelope =
  let xml = Cduce_ws.load_xml_subst [ 'string:' !s] 
    [("http://schemas.xmlsoap.org/soap/envelope",
      "http://schemas.xmlsoap.org/soap/envelope/")] 
  in 
  let res = SoapEnv.hd (SoapEnv.get_body xml) in
  let tmp :? Msg = res in
  let number = (match tmp with <ns:echoInteger>[<inputInteger>n] -> n)
  in
  let msg = 
    <ns:echoIntegerResponse>[
      <return> number 
    ] in
  SoapEnv.add_envelope msg;;

The last file, client.cd, executes the following steps:

  • it creates a message of type Msg.
    NOTE: Since we need to use load_xml or load_xml_subst to convert the string in XML, it is not possible to use primitive types such as integer in the types transmitted between the client and the server. We can only use Latin1 strings
  • the message is embedded in a SOAP envelope using SoapEnv.add_envelope, and converted as a string by print_xml_subst.
  • the string message is sent to the server with Cduce_ws.send, where the parameters are: the message string, the host address, the soap action, and the content type.
    The returned value of send is a Latin1 string with the response message in SOAP.
  • the response message is converted back in XML by load_xml_subst.
  • the body part of the SOAP response is extracted using SoapEnv.get_body, and printed.
(* file client.cd *)

include "../common.cd"
let msg : Msg = 
<ns:echoInteger>[
  <inputInteger>['1234567890']
];;

let _ =
  let string_msg = Cduce_ws.print_xml_subst (SoapEnv.add_envelope msg) 
    [("http://schemas.xmlsoap.org/soap/envelope/",
    "http://schemas.xmlsoap.org/soap/envelope")] 
  in
  let answer_string = Cduce_ws.send string_msg
    "http://localhost/ws/ws_echo" "" "text/xml; charset=utf-8" in
  print [ 'CDuce: ' !answer_string '\n'];
  let xml = Cduce_ws.load_xml_subst [ 'string:' !answer_string] 
    [("http://schemas.xmlsoap.org/soap/envelope",
    "http://schemas.xmlsoap.org/soap/envelope/")] 
  in 
  print (string_of (SoapEnv.get_body xml));;

A more advanced example

This example corresponds to the example in the directory: examples/calc/ in the CDuce_WS distribution.
It is a simple calculator over integer values that receive two values with an operator from a client and return the result.
The first file, common.cd, contains all the declarations used by the client and the server, and is included by all other files. The type Operation corresponds to all possible operations that a client can send to the server, while the type Response embeds the result sent by the server to the client.

(* file common.cd *)

namespace on;;
namespace ns = "urn:calc"
using SoapEnv = "soapenv";;

type Params = [ <a>Latin1 <b>Latin1 ];;
type AddIn = <ns:add>Params;;
type SubIn = <ns:sub>Params;;
type MulIn = <ns:mul>Params;;
type DivIn = <ns:div>Params;;
type Result = <result>Latin1;;
type AddOut = <ns:addResponse>[Result];;
type SubOut = <ns:subResponse>[Result];;
type MulOut = <ns:mulResponse>[Result];;
type DivOut = <ns:divResponse>[Result];;
type Operation = AddIn | SubIn | MulIn | DivIn;;
type Response = AddOut | SubOut | MulOut | DivOut;;

The next file, ws_calc.cd, contains the code necessary to register a web service in Ocsigen, via OcCduce.
It is quite similar to the code from the previous example.

(* file ws_calc.cd *)

include "../common.cd"
using Calc = "calc";;
using Lib = "occduce_lib";;

let wsError (_ : []) : AnyXml =
  SoapEnv.std_soap_fault;;

let wsCalc (_ : []) (_ : []) (content : Latin1) : AnyXml =
  Occduce.debug_string "wsCalc\n";
  try (Calc.calc content)
  with err & Latin1 -> 
    Occduce.debug_string [ 'Cduce exception: ' !err '\n'];
    exit 2;;

let ws_calc = 
  Lib.register_webservice
    { path = ["ws_calc"]; serviceName = "wsCalc" }
    wsCalc wsError

The next file, calc.cd, contains the processing of the SOAP message and the generation of the response message.
load_xml converts the string containing the SOAP message in a SOAP XML structure which is passed to SoapEnv.get_body and SoapEnv.hd to extract the content of the body of the SOAP structure.
The function compute processes the message and returns a result in a response message, which is embedded in a SOAP envelope by SoapEnv.add_envelope.

(* file calc.cd *)

include "../common.cd"
using SoapEnv = "soapenv";;

(* For a finer-grained type-checking, declare the 
   function compute with the following type declaration: 

  let compute ( AddIn -> AddOut,
                SubIn -> SubOut,
                MulIn -> MulOut,
                DivIn -> DivOut )
    |  <ns:add>[<a>a <b>b] -> 
	<ns:addResponse>[<result>(string_of ((int_of a) + (int_of b)))]
    | <ns:sub>[<a>a <b>b] -> 
        <ns:subResponse>[<result>(string_of ((int_of a) - (int_of b)))]
    | <ns:mul>[<a>a <b>b] -> 
        <ns:mulResponse>[<result>(string_of ((int_of a) * (int_of b)))]
    | <ns:div>[<a>a <b>b] -> 
        <ns:divResponse>[<result>(string_of ((int_of a) div (int_of b)))];;
*)

let compute (op : Operation) : Response =
  match op with 
      <ns:add>[<a>a <b>b] -> 
	<ns:addResponse>[<result>(string_of ((int_of a) + (int_of b)))]
    | <ns:sub>[<a>a <b>b] -> 
      <ns:subResponse>[<result>(string_of ((int_of a) - (int_of b)))]
    | <ns:mul>[<a>a <b>b] -> 
      <ns:mulResponse>[<result>(string_of ((int_of a) * (int_of b)))]
    | <ns:div>[<a>a <b>b] -> 
      <ns:divResponse>[<result>(string_of ((int_of a) div (int_of b)))];;

let calc (s : Latin1) : SoapEnv.Envelope =
  let xml = load_xml [ 'string:' !s]  in 
  let res = SoapEnv.hd (SoapEnv.get_body xml) in
  let op :? Operation  = res in
  let answer = compute op in
  SoapEnv.add_envelope answer;;

The last file, client.cd, executes the following steps:

  • it creates a message of type Operation.
  • the message is embedded in a SOAP envelope using SoapEnv.add_envelope
  • it is then converted as a string by print_xml and sent to the server with Cduce_ws.send, where the parameters are: the message string, the host address, the soap action, and the content type.
    The returned value of send is a Latin1 string with the response message in SOAP.
  • the response message is converted back in XML by load_xml.
  • the body part of the SOAP response is extracted using SoapEnv.get_body, and printed.
(* file client.cd *)

include "../common.cd"
using SoapEnv = "soapenv";;

let msg :? Operation = <ns:add>[ <a>"24" <b>"18" ];;

let _ = 
  let msg_soap = SoapEnv.add_envelope msg in
  let answer_string = Cduce_ws.send (print_xml msg_soap) 
    "http://localhost/ws/ws_calc" "" "text/xml; charset=utf-8" in
  print [ 'CDuce: ' !answer_string '\n'];
  let xml = load_xml [ 'string:' !answer_string ] in
  let soap_answer :? SoapEnv.Envelope = xml in
  print (string_of (SoapEnv.get_body xml));;

WSDL

You can extract some useful information from WSDL files using functions from the WSDL module.

  • wsdl_load "file.wsdl": loads a WSDL file in a Wsdl structure.
  • wsdl_port_types: extracts the port types from a Wsdl structure.
  • show_port_type: prints all the messages of a port type.
  • extract_schema: save the xsd schema part of a Wsdl structure in a file.

The following program, wsdl_test.cd, prints the messages from a wsdl file calc.wsdl (you can download it from the gsoap website), and save the xsd part in a file "calc_sch.xsd".

(* wsdl_test.cd *)
using WSDL = "wsdl"

let _ = 
  let w = WSDL.wsdl_load "calc.wsdl" in
  let port_types = WSDL.wsdl_port_types w in
  transform port_types with (name,pt) -> WSDL.show_port_type pt name;
  WSDL.extract_schema w "calc_sch.xsd";;

You need to compile and execute wsdl_test.cd with the following command:

cduce --compile -I `ocamlfind query cduce_ws` wsdl_test.cd
cduce --run -I `ocamlfind query cduce_ws` wsdl_test.cdo 

The output of wsdl_test.cd is:

calcPortType
<ns:add>[ <a>double <b>double ]
 -> <ns:addResponse>[ <result>double ]

<ns:sub>[ <a>double <b>double ]
 -> <ns:subResponse>[ <result>double ]

<ns:mul>[ <a>double <b>double ]
 -> <ns:mulResponse>[ <result>double ]

<ns:div>[ <a>double <b>double ]
 -> <ns:divResponse>[ <result>double ]

<ns:pow>[ <a>double <b>double ]
 -> <ns:powResponse>[ <result>double ]

Which has been translated in the following CDuce code from the calc example:

namespace ns = "urn:calc"

type Params = [ <a>Latin1 <b>Latin1 ];;
type AddIn = <ns:add>Params;;
type SubIn = <ns:sub>Params;;
type MulIn = <ns:mul>Params;;
type DivIn = <ns:div>Params;;
type Result = <result>Latin1;;
type AddOut = <ns:addResponse>[Result];;
type SubOut = <ns:subResponse>[Result];;
type MulOut = <ns:mulResponse>[Result];;
type DivOut = <ns:divResponse>[Result];;
type Operation = AddIn | SubIn | MulIn | DivIn;;
type Response = AddOut | SubOut | MulOut | DivOut;;

Apart from the conversion of double in Latin1, the translation is quite straightforward.