Working with SOAP headers

In this section, the simple web service that was introduced in Using SOAP services is extended to handle SOAP headers.

If you have followed the steps outlined in the previous section, you can skip steps 1 through 4 and go directly to step 5.

To create a web service server

  1. Create a database.

    dbinit ftc
  2. Start a server using this database.

    dbsrv11 -xs http(port=8082) -n ftc ftc.db
  3. Connect to the server using Interactive SQL.

    dbisql -c "UID=DBA;PWD=sql;ENG=ftc"
  4. Using Interactive SQL, create a web service.

    CREATE SERVICE FtoCService
    TYPE 'SOAP'
    FORMAT 'XML'
    AUTHORIZATION OFF
    USER DBA
    AS CALL FToCConvertor( :temperature );
  5. Define the stored procedure that this service is to call to perform the calculation needed to convert from a temperature expressed in degrees Fahrenheit to a temperature expressed in degrees Celsius. Unlike the example in the previous section, this one includes additional statements to process a special SOAP header. If you have already worked through the example in the previous section, change the CREATE below to ALTER since you are now going to modify the stored procedure.

    CREATE PROCEDURE FToCConvertor( temperature FLOAT )
    BEGIN
      DECLARE hd_key LONG VARCHAR;
      DECLARE hd_entry LONG VARCHAR;
      DECLARE alias LONG VARCHAR;
      DECLARE first_name LONG VARCHAR;
      DECLARE last_name LONG VARCHAR;
      DECLARE xpath LONG VARCHAR;
      DECLARE authinfo LONG VARCHAR;
      DECLARE namespace LONG VARCHAR;
      DECLARE mustUnderstand LONG VARCHAR; 
    header_loop:
      LOOP
        SET hd_key = NEXT_SOAP_HEADER( hd_key );
        IF hd_key IS NULL THEN
          -- no more header entries
          LEAVE header_loop;
        END IF;
        IF hd_key = 'Authentication' THEN
          SET hd_entry = SOAP_HEADER( hd_key );
          SET xpath = '/*:' || hd_key || '/*:userName';
          SET namespace = SOAP_HEADER( hd_key, 1, 
                                   '@namespace' );
          SET mustUnderstand = SOAP_HEADER( hd_key, 1, 
                                   'mustUnderstand' );
          BEGIN
            -- parse the XML returned in the SOAP header 
            DECLARE crsr CURSOR FOR 
              SELECT * 
              FROM OPENXML( hd_entry, xpath )
                WITH ( alias LONG VARCHAR '@*:alias',
                first_name LONG VARCHAR '*:first/text()',
                last_name LONG VARCHAR '*:last/text()' );
            OPEN crsr;
            FETCH crsr INTO alias, first_name, last_name;
            CLOSE crsr;
          END;
          -- build a response header
          -- based on the pieces from the request header
          SET authinfo = 
            XMLELEMENT( 'Authentication',
              XMLATTRIBUTES(
                namespace as xmlns,
                alias,
                mustUnderstand ),
                XMLELEMENT( 'first', first_name ),
                XMLELEMENT( 'last', last_name ) );
          CALL SA_SET_SOAP_HEADER( 'authinfo', authinfo );
        END IF;
      END LOOP header_loop;
      SELECT ROUND((temperature - 32.0) * 5.0 / 9.0, 5) 
      AS answer;
    END;

Headers in SOAP requests can be obtained using a combination of the NEXT_SOAP_HEADER and SOAP_HEADER functions. The NEXT_SOAP_HEADER function iterates through the SOAP headers included within a request and returns the next SOAP header name. Calling it with NULL causes it to return the name of the first header. Subsequent headers are retrieved by passing the name of the previous header to the NEXT_SOAP_HEADER function. This function returns NULL when called with the name of the last header. The SQL code that does the SOAP header retrieval in the example is this. It exits the loop when NULL is finally returned.

SET hd_key = NEXT_SOAP_HEADER( hd_key );
    IF hd_key IS NULL THEN
      -- no more header entries
      LEAVE header_loop;
    END IF;

Calling this function repeatedly returns all the header fields exactly once, but not necessarily in the order they appear in the SOAP request.

The SOAP_HEADER function returns the value of the named SOAP header field, or NULL if not called from an SOAP service. It is used when processing an SOAP request via a web service. If a header for the given field-name does not exist, the return value is NULL.

The example searches for a SOAP header named Authentication. When it finds this header, it extracts the value for entire SOAP header as well as the values of the @namespace and mustUnderstand attributes. The SOAP header value might look something like this XML string:

<Authentication xmlns="SecretAgent" mustUnderstand="1">
  <userName alias="99">
    <first>Susan</first>
    <last>Hilton</last>
  </userName>
</Authentication>

For this header, the @namespace attribute value would be:SecretAgent

Also, the mustUnderstand attribute value would be:1

The interior of this XML string is parsed with the OPENXML function using an XPath string set to /*:Authentication/*:userName.

SELECT * 
FROM OPENXML( hd_entry, xpath )
  WITH ( alias LONG VARCHAR '@*:alias',
  first_name LONG VARCHAR '*:first/text()',
  last_name LONG VARCHAR '*:last/text()' );

Using the sample SOAP header value shown above, the SELECT statement would create a result set as follows:

alias first_name last_name
99 Susan Hilton

A cursor is declared on this result set and the three column values are fetched into three variables. At this point, you have all the information of interest that was passed to the web service. You have the temperature in Fahrenheit degrees and you have some additional attributes that were passed to the web service in a SOAP header. So what could you do with this information?

You could look up the name and alias that were provided to see if the person is authorized to use the web service. However, this exercise is not shown in the example.

The next step in the stored procedure is to create a response in the SOAP format. You can build the XML response as follows:

SET authinfo = 
  XMLELEMENT( 'Authentication',
    XMLATTRIBUTES(
      namespace as xmlns,
      alias,
      mustUnderstand ),
      XMLELEMENT( 'first', first_name ),
      XMLELEMENT( 'last', last_name ) );

This builds the following XML string:

<Authentication xmlns="SecretAgent" alias="99" 
                            mustUnderstand="1">
  <first>Susan</first>
  <last>Hilton</last>
</Authentication>

Finally, to return the SOAP response to the caller, the SA_SET_SOAP_HEADER stored procedure is used:

CALL SA_SET_SOAP_HEADER( 'authinfo', authinfo );

As in the example in the previous section, the last step is the calculation that converts from degrees Fahrenheit to degrees Celsius.

At this point, you now have a SQL Anywhere web service server running that can convert temperatures from degrees Fahrenheit to degrees Celsius as in the previous section. The major difference, however, is that it can also process a SOAP header from the caller and send a SOAP response back to the caller.

This is only half of the picture. The next step is to develop an example client that can send SOAP requests and receive SOAP responses.

If you have followed the steps outlined in the previous section, you can skip steps 1 through 3 and go directly to step 4.

To send and receive SOAP headers

  1. Create another database for use with a second server.

    dbinit ftc_client
  2. Start the personal server using this database.

    dbeng11 ftc_client.db
  3. Connect to the personal server using another instance of Interactive SQL.

    dbisql -c "UID=DBA;PWD=sql;ENG=ftc_client"
  4. Using Interactive SQL, create a stored procedure.

    CREATE PROCEDURE FtoC( temperature FLOAT,
        INOUT inoutheader LONG VARCHAR,
        IN inheader LONG VARCHAR )
      URL 'http://localhost:8082/FtoCService'
      TYPE 'SOAP:DOC'
      SOAPHEADER '!inoutheader!inheader';

    The URL clause is used to reference the SOAP web service. The string 'http://localhost:8082/FtoCService' specifies the URI of the web service that is going to be used. This is a reference to the web server that is listening on port 8082.

    The default format used when making a web service request is 'SOAP:RPC'. The format chosen in this example is 'SOAP:DOC', which is similar to 'SOAP:RPC' but allows for a richer set of datatypes. SOAP requests are always sent as XML documents. The mechanism for sending SOAP requests is 'HTTP:POST'.

    The substitution variables in a SQL Anywhere client procedure (inoutheader, inheader) must be alpha-numeric. If the web service client is declared as a function, all its parameters are IN mode only (they cannot be assigned by the called function). Therefore, OPENXML or other string functions will have to be used to extract the SOAP response header information.

  5. You need a wrapper for the FtoC stored procedure so create a second stored procedure as follows. Unlike the example in the previous section, this one includes additional statements to create a special SOAP header, send it in a web service call, and process a response from the web server. If you have already worked through the example in the previous section, change the CREATE below to ALTER since you are now going to modify the stored procedure.

    CREATE PROCEDURE FahrenheitToCelsius( temperature FLOAT )
    BEGIN
      DECLARE io_header LONG VARCHAR;
      DECLARE in_header LONG VARCHAR;
      DECLARE result LONG VARCHAR;
      DECLARE err INTEGER;
      DECLARE crsr CURSOR FOR
        CALL FtoC( temperature, io_header, in_header );
      SET io_header =
        '<Authentication xmlns="SecretAgent" ' ||
                         'mustUnderstand="1">' ||
        '<userName alias="99">' ||
        '<first>Susan</first><last>Hilton</last>' ||
        '</userName>' ||
        '</Authentication>';
      SET in_header =
        '<Session xmlns="SomeSession">' ||
        '123456789' ||
        '</Session>';
    
      MESSAGE 'send, soapheader=' || io_header || in_header;
      OPEN crsr;
      FETCH crsr INTO result, err;
      CLOSE crsr;
      MESSAGE 'receive, soapheader=' || io_header;
      SELECT temperature, Celsius
      FROM OPENXML(result, '//tns:answer', 1, result)
             WITH ("Celsius" FLOAT 'text()');
    END;

    This stored procedure acts as a cover procedure for the call to the web service. The stored procedure has been enhanced from the example in the previous section. It creates two SOAP headers. The first one is this.

    <Authentication xmlns="SecretAgent" 
                                mustUnderstand="1">
      <userName alias="99">
        <first>Susan</first>
        <last>Hilton</last>
      </userName></Authentication>

    The second one is this.

    <Session xmlns="SomeSession">123456789</Session>

    When the cursor is opened, the SOAP request is sent to the web service.

    <Authentication xmlns="SecretAgent" alias="99" 
                                mustUnderstand="1">
    <first>Susan</first>
    <last>Hilton</last>
    </Authentication>

    The FtoC stored procedure returns a result set that this stored procedure will process. The result set will look something like this.

    <tns:rowset xmlns:tns="http://localhost/ftc/FtoCService">
     <tns:row>
      <tns:answer>100</tns:answer>
     </tns:row>
    </tns:rowset>

    The OPENXML function is used to parse the XML that is returned, extracting the value that is the temperature in degrees Celsius.

  6. Call the stored procedure in order to send the request and obtain the response:

    CALL FahrenheitToCelsius(212);

    The Fahrenheit temperature and the Celsius equivalent appears.

    temperature Celsius
    212.0 100.0

A SQL Anywhere web service client can be declared as either a function or a procedure. A SQL Anywhere client function declaration effectively restricts all parameters to in mode only (parameters cannot be assigned by the called function). Calling a SQL Anywhere web service function will return the raw SOAP envelope response whereas a procedure returns a result set.

A SOAPHEADER clause has been added to the create/alter procedure/function statements. A SOAP header can be declared as a static constant or can be dynamically set using the parameter substitution mechanism. A web service client function can define one or more in mode substitution parameters whereas a web service client procedure can also define a single inout or out substitution parameter. Therefore a web service client procedure can return the response SOAP header within an out (or inout) substitution parameter. A web service function must parse the response SOAP envelope to obtain the header entries.

The following example illustrates how a client can specify the sending of several header entries with parameters and receiving the response SOAP header data.

CREATE PROCEDURE SoapClient( 
    INOUT hd1 VARCHAR, 
    IN hd2 VARCHAR, 
    IN hd3 VARCHAR ) 
  URL 'localhost/some_endpoint' 
  SOAPHEADER '!hd1!hd2!hd3';
Notes
Limitations