SOAP Headers are used in many WS-* standards (for an example WS-Security) to transmit information related to the QOS aspects of the service. So only the SOAP engines which process these QOS parameters are supposed to see these headers. Web Service Developer or Consumer need to care only what is in the SOAP payload (which is what inside the SOAP body element) and not SOAP headers.

But that is theory, In practice we see many places where people uses SOAP headers to transmit what they should have easily sent through SOAP Body. And many SOAP stacks also provide APIs to allow people to send/receive custom headers and standards like WSDL too provide ways to describe services which accept custom headers. So I thought it might worth to discuss how you describe custom headers in a service from a WSDL and how you can consume and serve in real world with SOAP stacks like Axis2/C and WSF/PHP.

In a WSDL 1.1 you first declare your headers in the binding section itself which is the section that is used to provide the messaging and transport protocol information.

  <binding name="RetHeaderBinding" type="tns:RetHeaderPortType">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
    <operation name="echoString">
      <soap:operation soapAction="http://soapinterop.org/" style="document"/>
      <input>
        <soap:body use="literal"/>
        <soap:header message="tns:Header1" part="Header1" use="literal"/>
        <soap:header message="tns:Header2" part="Header2" use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
        <soap:header message="tns:Header1" part="Header1" use="literal"/>
        <soap:header message="tns:Header2" part="Header2" use="literal"/>
      </output>
    </operation>
  </binding>

There you can see we have Header1 and Header2 in both in input and output of the operation. It refers to the message section with the specific part name that represent the header. Here is how message section will look like.

  <message name="Header1">
    <part name="Header1" element="types:Header1"/>
  </message>
  <message name="Header2">
    <part name="Header2" element="types:Header2"/>
  </message>

In this section it refers to the schema element that actually describe the element structure/schema. Here I put a string and an int in each header.

  <s:element name="Header1">
      <s:complexType>
        <s:sequence>
          <s:element name="string" type="s:string"/>
          <s:element name="int" type="s:int"/>
        </s:sequence>
      </s:complexType>
  </s:element>
  <s:element name="Header2">
      <s:complexType>
        <s:sequence>
          <s:element name="int" type="s:int"/>
          <s:element name="string" type="s:string"/>
        </s:sequence>
      </s:complexType>
  </s:element>

Here is a valid SOAP message for the WSDL description.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header>
      <ns1:Header1 xmlns:ns1="http://soapinterop.org/xsd">
         <ns1:string>test1</ns1:string>
         <ns1:int>5</ns1:int>
      </ns1:Header1>
      <ns1:Header2 xmlns:ns1="http://soapinterop.org/xsd">
         <ns1:int>8</ns1:int>
         <ns1:string>test2</ns1:string>
      </ns1:Header2>
   </soapenv:Header>
   <soapenv:Body>
      My payload
   </soapenv:Body>
</soapenv:Envelope>

If you are using Apache Axis2/C WSDL2C Codegen tool (which is shipped with Axis2/Java) you will be able to directly use the API provided with the generated code to deal with custom headers. Here is how the client API is generated for the above WSDL.

         /**
          * auto generated method signature
          * for "echoString|http://ws.dimuthu.org/blog/headers" operation.
          *
          * @param _echoStringParam
          * @param _header1
          * @param _header2
          *
          * @param dp_header10 - output header
          * @param dp_header21 - output header
          * @return adb_echoStringReturn_t*
          */
         adb_echoStringReturn_t*
         axis2_stub_op_RetHeaderService_echoString( axis2_stub_t *stub, const axutil_env_t *env,
                                              adb_echoStringParam_t* _echoStringParam,
                                              adb_Header1E0_t* _header1,
                                              adb_Header2E1_t* _header2,
                                              adb_Header1E0_t** dp_header10 /* output header double ptr*/,
                                              adb_Header2E1_t** dp_header21 /* output header double ptr*/)

Note that the first parameter is the environment variable passed in every function in Axis2/c. The second parameters is the payload. Input headers (input headers for server side) are in 3rd and 4th parameters, you can build them similar to how you build payload object. 5th and 6th are for the output headers which you only need to provide some pointers and the generated code will build you the objects from the response data.

You service will also have a similar API. There you have to create adb objects for output headers (like you build output payload) and assign the pointer to these output header double pointers.

             adb_Header2_t* arg_Header1 = adb_Header1_create(env);
             adb_Header1_set_string(arg_Header1, env, "I m response Header 1");
             adb_Header1_set_int(arg_Header1, env, 1);
             adb_Header10_t* _header1 = adb_Header10_create(env);
             adb_Header10_set_Header1(_header1, env, arg_Header1);
             *dp_header10 = _header1;

Now how about PHP, can you provide the same API form the PHP as well. Yes, PHP has references similar to C pointers. WSF/PHP already have the custom headers support in the SVN and to be released with the 2.0.
Here is the generated API for the service business logic for the above WSDL.

/**
 * Service function echoString
 * @param string $input
 * @param object of Header1 $header_in0 input header
 * @param object of Header2 $header_in1 input header
 * @param reference object of Header1 $header_out0 output header
 * @param reference object of Header2 $header_out1 output header
 * @return string
 */
function echoString($input, $header_in0, $header_in1, &$header_out0, &$header_out1) {

...
}

These tool provides APIs to consume and provide data through custom headers in same easiness you send the data in the payload. Anyway it is really recommended to not to use custom SOAP headers in your Service and try to avoid them as much as possible. Whenever you think there is valid reason to put a custom header (it is relevant to QOS aspects), just try to integrate the header processing logic to the SOAP engine. In Axis2 C or Java you can write Axis2 modules for that. That will really speed up the processing of your service and give consumers a well-designed API.

In a valid XML you can only have text and you can not have binary characters. SOAP which in fact an XML is also having this issue. So it is not staright forward to send binary data in SOAP. But with WSO2 WSF/PHP 1.3.2 It is really easy to send binaries. Check the online WSF/PHP Mtom Sample sources to understand the few lines that needed to send and serve binaries.

To understand what is happening inside WSF/PHP to send your message we will first take a simple demo. To understand the messages I will be sending text data rather than binary so you can clearly see how your data goes through the wire.

Here is my code to send the binary. (in fact a test in this percular case)

<?php
$requestPayloadString = <<<XML
<ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
  <ns1:fileName>test.txt</ns1:fileName>
  <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">

    <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="cid:myid1"></xop:Include>
  </ns1:image>
</ns1:upload>
XML;

try {
    $f = file_get_contents("test.txt");

    $requestMessage = new WSMessage($requestPayloadString,
        array("to" => "http://localhost/my_mtom_service.php",
        "attachments" => array("myid1" => $f)));

    $client = new WSClient(array("useMTOM" => "FALSE"));

    $responseMessage = $client->request($requestMessage);

    echo $responseMessage->str;

} catch (Exception $e) {
    if ($e instanceof WSFault) {

        printf("Soap Fault: %s\\n", $e->Reason);
    } else {

        printf("Message = %s\\n",$e->getMessage());
    }
}

?>

And test.txt has the following text

TEXT as BINARY

In here the ‘xop:include’ element is where you going to send either the binary data or some reference to binary data.
“useMTOM” => FALSE
Note that in there in the “useMTOM” option for the WSClient I have given the value FALSE. That mean don’t use the MTOM(Message Transmission Optimization Mechanism) to serialize the message.There the generated soap message (over HTTP) will look like this,

POST /samples/mtom/mtom_upload_service.php HTTP/1.1
User-Agent: Axis2C/1.5.0
Content-Length: 404
Content-Type: application/soap+xml;charset=UTF-8
Host: 127.0.0.1:8080

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
   <soapenv:Header/>
   <soapenv:Body>
      <ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
         <ns1:fileName>test.jpg</ns1:fileName>
         <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">VEVYVCBhcyBCSU5BUlkK</ns1:image>
      </ns1:upload>
   </soapenv:Body>
</soapenv:Envelope>

The value “VEVYVCBhcyBCSU5BUlkK” is base64 encoded value for the “TEXT as BINARY” text.

<?php
echo base64_encode("TEXT as BINARY");
?>

In fact it is a standard that map each 6 bit of your binary file to an 8 bit (valid character to send through XML), so you can send the encoded value in the soap message. But no doubt you see the drawback. The encoded value is 4/3rd of the original data size. So this is not the most optimized way to send binary over SOAP.
“useMTOM” => TRUE
Setting this option TRUE will generate the following soap message over http.

POST /samples/mtom/mtom_upload_service.php HTTP/1.1
User-Agent: Axis2C/1.5.0
Transfer-Encoding: chunked
Content-Type: multipart/related; boundary=MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d; type="application/xop+xml"; start="<0.594038e2-6d95-1dd1-2b73-00197e732d4d@apache.org>"; start-info="application/soap+xml"; charset="UTF-8"
Host: 127.0.0.1:8080

32
--MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d
2

b2
content-transfer-encoding: binary
content-id: <0.594038e2-6d95-1dd1-2b73-00197e732d4d@apache.org>content-type: application/xop+xml;charset=UTF-8;type="application/soap+xml";21ff
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Header/>
      <soapenv:Body>
         <ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
            <ns1:fileName>test.jpg</ns1:fileName>
            <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">
               <xop:Include href="cid:1.594037a2-6d95-1dd1-2b71-00197e732d4d@apache.org" xmlns:xop="http://www.w3.org/2004/08/xop/include"/>
            </ns1:image>
         </ns1:upload>
      </soapenv:Body>
   </soapenv:Envelope>32--MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d27econtent-transfer-encoding: binarycontent-id:
   <1.594037a2-6d95-1dd1-2b71-00197e732d4d@apache.org>content-type: text/txtfTEXT as BINARY234--MIMEBoundary594038a6-6d95-1dd1-2b72-00197e732d4d--0

Note that in the last line you see the data you send as it is. Here the binary is sent as an attachment and the element which suppose to keep the binary data refers to the attachment using the content id (check “href” attribute). It is obvious for a large binary data this is really optimized way of sending binaries.

“useMTOM” => “SWA”

WSF/PHP supports 1.3.2 supports SWA (Soap with Attachment) as well. you can enable this by setting “useMTOM” => “swa”. It is also send the binary as an attachment.  But it is not a recommended approach since you can’t secure the message when using SWA, because it can not be represented in the XML infoset. Here is the message generated enabling the “SWA”,

POST /samples/mtom/mtom_upload_service.php HTTP/1.1
User-Agent: Axis2C/1.5.0
Transfer-Encoding: chunked
Content-Type: multipart/related; boundary=MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d; type="application/xop+xml"; start="<0.bc636c40-6d96-1dd1-31c1-00197e732d4d@apache.org>"; start-info="application/soap+xml"; charset="UTF-8"
Host: 127.0.0.1:8080

32
--MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d
2

b2
content-transfer-encoding: binary
content-id: <0.bc636c40-6d96-1dd1-31c1-00197e732d4d@apache.org>content-type: application/xop+xml;charset=UTF-8;type="application/soap+xml";21b3
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Header/>
      <soapenv:Body>
         <ns1:upload xmlns:ns1="http://php.axis2.org/samples/mtom">
            <ns1:fileName>test.jpg</ns1:fileName>
            <ns1:image xmlmime:contentType="text/txt" xmlns:xmlmime="http://www.w3.org/2004/06/xmlmime">1.bc6368a8-6d96-1dd1-31bf-00197e732d4d@apache.org</ns1:image>
         </ns1:upload>
      </soapenv:Body>
   </soapenv:Envelope>

32
--MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d
2

7e
content-transfer-encoding: binary
content-id:
   <1.bc6368a8-6d96-1dd1-31bf-00197e732d4d@apache.org>content-type: text/txtfTEXT as BINARY234--MIMEBoundarybc636bdc-6d96-1dd1-31c0-00197e732d4d--0