3.5 SOAP Messages and MIME Headers

   

3.5 SOAP Messages and MIME Headers

When a SOAP message is wrapped for transmission in HTTP (or another protocol, such as SMTP), suitable MIME headers must be created. At minimum, the wrapper must include the Content-Type and Content-Length headers along with the SOAPAction header that was described in Section 3.3.2, earlier in this chapter. Similarly, when a message is received, any MIME headers that accompany it must be extracted from the protocol-specific wrapper and be made part of the created SOAPMessage . The MIME headers associated with a SOAPMessage are held in an object of type javax.xml.soap.MimeHeaders .

In terms of the structure of an encapsulated SOAP message, the MIME headers logically appear outside the envelope (as shown in Figure 3-1), where they form the protocol-specific wrapper. In the case of a SOAP message with attachments, in addition to the headers in the outer wrapper, the SOAP message part and each of the attachment parts have their own collection of MIME headers that are distinct from those of the wrapper, as shown in Figure 3-2.

3.5.1 The MimeHeader and MimeHeaders Classes

MimeHeader and MimeHeaders are two of four concrete classes in the java.xml.soap package. The MimeHeader class represents a single MIME header and contains the header name and its associated value, which are set at construction time and cannot subsequently be changed.

MimeHeaders is a collection of MimeHeader objects. When it is created, a MimeHeaders object is empty; headers can be added using one of the following methods :

public void addHeader(String headerName, String headerValue)

Creates a MimeHeader object with the given header name and value, and adds it to the collection of headers.

public void setHeader(String headerName, String headerValue)

If the MimeHeaders object does not contain a header with the given name, this method behaves in the same way as addHeader( ) . Otherwise, the value for the first header in the collection that has the given name is replaced with the given value, and all other MimeHeader entries with the same name are removed.

As implied by the description of the setHeader( ) method, it is possible to have more than one header with the same name in a MimeHeaders object. The following code results in two header entries, both with the name HeaderName :

 MimeHeaders headers = new MimeHeaders(  );
headers.addHeader("HeaderName", "Value1");
headers.addHeader("HeaderName", "Value2"); 

Calling setHeader( ) method results in exactly one header with the supplied name being present in the collection, no matter how many there were before the call was made. The following line:

 headers.setHeader("HeaderName", "NewValue"); 

replaces the MimeHeader with name HeaderName ; it replaces value Value1 with the value NewValue ; and it removes the MimeHeader for the entry with value Value2 . Note that this method, like all of the methods of MimeHeaders , uses case-insensitive comparison when searching for headers by name, so that the names HeaderName and HEADERNAME are equivalent.

There are a number of methods that can be used to query the content of a MimeHeaders object:

public Iterator getAllHeaders( )

Gets an Iterator over all of the headers in the collection. Each item returned by the Iterator is of type MimeHeader .

public String[] getHeader(String headerName)

Gets all of the values associated with headers whose names match the given name. Using the MimeHeaders object just constructed as an example, the method call getHeaders("HeaderName") would return an array of two strings containing the values Value1 and Value2 .

public Iterator getMatchingHeaders(String[] headerNames)

Gets an Iterator that returns all of the MimeHeader objects that have a name that matches those in the given array.

public Iterator getNonMatchingHeaders(String[] headerNames)

Gets an Iterator that returns all of the MimeHeader objects that have a name that does not match those in the given array.

Finally, there are two methods that remove entries from the collection:

public void removeAllHeaders( )

Removes all of the MimeHeader objects, leaving an empty collection.

public void removeHeader(String headerName)

Removes all MimeHeader objects whose name matches the supplied argument.

3.5.2 Setting MIME Headers When a SOAP Message Is Transmitted

When a SOAPMessage is created using the no-argument createMessage( ) method of MessageFactory , it has an empty MimeHeaders object associated with it. You can add headers at any time by retrieving this object from the SOAPMessage and using the addHeader( ) or setHeader( ) method:

 message.getMimeHeaders(  ).addHeader("Header1", "Value1"); 

The protocol-specific wrapper for a protocol such as HTTP requires that the Content-Length , Content-Type , and SOAPAction headers be included. These headers can be generated automatically by calling the SOAPMessage saveChanges( ) or writeTo( ) methods. The SOAPAction method can be explicitly set by application code and is not overwritten if it already has a value; the other two headers are always set by the saveChanges( ) and writeTo( ) methods. The SOAPMessage class also has a convenience method called setContentDescription( ) that allows the optional Content-Description method to be set. The content description is treated as comment only.

If a SOAP message is transmitted using the call( ) method of SOAPConnection , the saveChanges( ) method is automatically called to create the appropriate headers, and the content of the resulting MimeHeaders object is copied to the outgoing HTTP message, as shown in Figure 3-6.

Figure 3-6. Mapping of MIME headers to HTTP headers
figs/jwsn_0306.gif

On the other hand, if you are implementing a server in a servlet environment and sending a reply message, you won't have a SOAPConnection , and therefore you cannot use its call( ) method to wrap the SOAPMessage in the HTTP reply. In this case, you have to manually insert the MIME headers in the HTTP reply and then write the content of the SOAPMessage to the servlet's output stream. Much of the code to handle this was shown in the doPost( ) method in Example 3-1. Note that it is first necessary to ensure that the MimeHeaders object is actually populated before copying the headers to the HTTP reply, by calling the saveChanges( ) method. Once this is done, it is a simple matter to install the headers in the reply. The code for the SAAJServlet setHttpHeaders( ) method is shown in Example 3-11. This method is invoked as follows :

 // Copy the MIME headers to the HTTP response
setHttpHeaders(reply.getMimeHeaders(  ), response); 

where response is the HttpServletResponse object passed to the doPost( ) method.

Example 3-11. Copying MIME headers to an HTTP message
 private void setHttpHeaders(MimeHeaders mimeHeaders, 
     HttpServletResponse response) {
    Iterator iter = mimeHeaders.getAllHeaders(  );
    while (iter.hasNext(  )) {
        MimeHeader mimeHeader = (MimeHeader)iter.next(  );
        String headerName = mimeHeader.getName(  );
        String[] headerValues = mimeHeaders.getHeader(headerName);
        
        int count = headerValues.length;
        StringBuffer buffer = new StringBuffer(  );
        for (int i = 0; i < count; i++) {
            if (i != 0) {
                buffer.append(',');
            }
            buffer.append(headerValues[i]);
        }            
        response.setHeader(headerName, buffer.toString(  ));
    }        
} 

This method uses the SOAPMessage getAllHeaders( ) method to get an Iterator over all of the MimeHeader objects in the set of headers that is passed, and it uses the HttpServletResponse setHeader( ) method to install the header name and value corresponding to each MimeHeader . As a space optimization, if there is more than one header with the same name, this method gathers all of their values together and writes a single header in which all of the values are comma-separated. This is so that, for example, if a header with name HeaderName appears twice with values Value1 and Value2 , the result is a single HTTP header that looks like this:

 HeaderName: Value1,Value2 

3.5.3 Obtaining MIME Headers When a SOAP Message Is Received

When a SOAPMessage is created from an HTTP request received by a servlet, the MessageFactory createMessage( ) method is used, as shown in Example 3-1. This method requires two arguments:

 public SOAPMessage createMessage(MimeHeaders headers, InputStream
     inputStream); 

The MimeHeaders argument supplies the headers to be installed in the SOAPMessage , while the InputStream is used to read the XML that makes up the SOAP envelope and, in the case of a message with attachments, the attachments themselves . The createMessage( ) method associates the supplied MimeHeaders object with the SOAPMessage so that it can be retrieved later by calling the getMimeHeaders( ) method. The Content-Type header from the collection is also used by createMessage( ) to determine whether the SOAP message is in the format shown in Figure 3-1 (where the content type is text/xml ) or has attachments as shown in Figure 3-2 (and content type Multipart/Related ).

The servlet has to create an appropriate MimeHeaders object from the HTTP wrapper before it can call createMessage( ) . The code used by SAAJServlet to do this is shown in Example 3-12.

Example 3-12. Creating a MIMEHeaders object from an HTTP message
 private MimeHeaders getMIMEHeaders(HttpServletRequest request) {
    MimeHeaders mimeHeaders = new MimeHeaders(  );
    Enumeration enum = request.getHeaderNames(  );
    while (enum.hasMoreElements(  )) {
        String headerName = (String)enum.nextElement(  );
        String headerValue = request.getHeader(headerName);
        StringTokenizer st = new StringTokenizer(headerValue, ",");
        while (st.hasMoreTokens(  )) {
            mimeHeaders.addHeader(headerName, st.nextToken(  ).trim(  ));
        }
    }
    return mimeHeaders;
} 

In this code, the HTTP headers are obtained from the HttpServletRequest object. In most cases, one MimeHeader will be created from each header in the HTTP message. However, if a header contains multiple comma-separated values, such as:

 HeaderName: Value1,Value2 

then a separate MimeHeader will be added for each of the values.