17.1.14.Custom Soap Server Support

Virtuoso allows any VSP page to act as a SOAP endpoint. This permits preprocessing of the SOAP requests to extract additional information - such as one placed for ebXML - and conversion of the SOAP replies to put any additional information in them. SOAP messages with attachments can also be processed this way.

SOAP extensions, such as the ones required for ebXML, can be programmed as VSP services that can handle the additional information contained in the SOAP requests. The xpath_eval() function is useful here. The SOAP server could be called after removing extension information; this removal could be done with an XSL transformation. After the SOAP request is processed, additional information can be placed in the result by another XSL transformation.

Having a SOAP server outside the /SOAP physical path allows a greater degree of control over what procedures are executed by providing a list of mappings. Having this suite of functions allows SOAP requests to be processed outside an HTTP context (for example after doing mime_tree() over an e-mail) and sending the SOAP replies as SMTP messages.

The following built-in functions are relevant in this context:

soap_server()

soap_make_error()

soap_box_xml_entity()

soap_print_box()

http_body_read()

Example17.11.Sample SOAP 1.1 server

<?vsp
        dbg_obj_print ('vspsoap called');
        declare content_type, soap_method, soap_xml varchar;
        declare payloads any;

        -- get the encoding to find out where the SOAP request should be searched for

        content_type := http_request_header (lines, 'Content-Type');
        if (isstring (content_type))
           content_type := lower (content_type);

        -- get the SOAP method name to execute

        soap_method := http_request_header (lines, 'SOAPAction');
        soap_xml := NULL;
        payloads := NULL;

        -- get the SOAP request
        if (content_type = 'multipart/related')
          {
            -- as in SOAP messages with attachments
            declare attrs any;
            declare inx integer;
            declare start_req varchar;

            -- the SOAP body is in the root part
            -- so get the root part's name
            start_req := http_request_header (lines, 'Content-Type', 'start');

            -- loop over the parts and get the root one.
            -- Others are placed in the payload array

            inx := 1;
            soap_xml := null;
            attrs := vector (1);
            while (isarray (attrs))
             {
               declare content_id varchar;

               -- get the part's MIME header
               attrs := get_keyword (sprintf ('attr-mime_part%d', inx), params);

               if (isarray (attrs))
                 {
                   -- extract the Content-ID from it
                   content_id := get_keyword ('Content-ID', attrs);
                   dbg_obj_print ('cont-id', content_id);

                   if (isstring (content_id))
                     {
                       -- if it is the root part (SOAP request) parse it.
                       if (content_id = start_req)
                         soap_xml := xml_tree_doc (xml_tree (
                      get_keyword (sprintf ('mime_part%d', inx), params)));
                       else
                         {
                           -- otherwise consider it a payload and store a info about the payload
                           -- for later retrieval by get_keyword () VSE based on Content-ID
                           if (payloads is null)
                             payloads := vector (vector (content_id, inx));
                           else
                             payloads := vector_concat (payloads, vector (content_id, inx));
                         }
                     }
                 }
               inx := inx + 1;
             }
          }
        else if (content_type = 'text/xml')
          {
            -- it's a SOAP request without attachments
            -- so get the POST body and parse it.
            soap_xml := xml_tree_doc (xml_tree (http_body_read ()));
          }
        else
          signal ('42000', 'unsupported encoding');

        -- the things retrieved so far
        dbg_obj_print ('vspsoap message', soap_xml);
        dbg_obj_print ('vspsoap payloads', payloads);

        -- execute the message

        -- catch any subsequent SQL error and generate and return SOAP reply XML for it.

        declare exit handler for SQLSTATE '*' {
          dbg_obj_print ('vspsoap in error handler for ', __SQL_MESSAGE);
          declare err_msg varchar;
          err_msg := soap_make_error ('300', __SQL_STATE, __SQL_MESSAGE);
          dbg_obj_print ('vspsoap error', err_msg);
          http (err_msg);

          -- note the SOAP SQL state - this is required since based on this value the
          -- HTTP server will not generate any additional reply if the SQL state starts with SOAP
          -- and this way the client will get a properly formatted reply
          resignal 'SOAP';
        };

        -- now check what is required and act accordingly
        if (soap_method = 'ebXML')
          {
            signal ('42000', 'ebXML not implemented yet');
          }
        else if (soap_method in ('fake#test'))
          {
            declare res any;

            -- note the mapping here : the SOAP call to fake:test will result in a
            -- call to DB.DBA.SOAPTEST PL procedure and it's results returned.

            res := soap_server (soap_xml, soap_method, lines, 11,
                    vector ('fake:test', 'DB.DBA.SOAPTEST'));

            dbg_obj_print ('vspsoap result', res);
            http (res);
          }
        else
          {
            -- simple signal will do as this will be cached by the handler
          -- and formatted as an SOAP error XML
            signal ('42000', concat ('Procedure ', soap_method, ' not defined'));
          }
?>