Search This Blog

Friday 4 October 2013

Headers in SOAP

What is a soap header ?
From w3schools:
The optional SOAP Header element contains application-specific information (like 
authentication, payment, etc) about the SOAP message. If the Header element is 
present, it must be the first child element of the Envelope element. All immediate 
child elements of the Header element must be namespace-qualified.
So the soap message would be something like :

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header>
      <!-- The header elements -->
   </soap:Header>
   <soap:Body>
      <!-- The body elements -->
   </soap:Body>
</soap:Envelope>
I have never had to use headers in my code. Until recently. While working with CXF, I was needed to add a soap header for the response.
I decided to extend my wsdl first example to try the same.
The first step was to define a header in the xsd:
<xs:element name="RandomHeader">
      <xs:complexType>
         <xs:all>
            <xs:element name="hostName" type="xs:string"
               maxOccurs="1" minOccurs="1" />
         </xs:all>
      </xs:complexType>
   </xs:element>
This is just a normal element.
Executing a wsdl2java command on the xsd resulted in a simple RandomHeader class.
The next step was to modify the server endpoint to add the header to the request.
As I was working with the JAX WS interfaces (or JAXWS frontend), I used the WebServiceContext class:
private static final QName HEADER_QNAME = new QName("http://ws.com/Service/xsd/random-schema", "RandomHeader");
  @Autowired
  private WebServiceContext wsContext;

  private void addNameHeader() throws JAXBException {
    final RandomHeader rHeader = new RandomHeader();
    rHeader.setHostName("WhoCares !!");

    // will be specific to each thread/ request
    final MessageContext messageContext = this.wsContext.getMessageContext();

    final List<Header> headers = new ArrayList<Header>();
    headers.add(new Header(HEADER_QNAME, rHeader, new JAXBDataBinding(RandomHeader.class)));
    messageContext.put(Header.HEADER_LIST, headers);
  }

  @Override
  public GetRandomResponse random(final GetRandomRequest getRandomRequest) {
    LOG.info("Executing operation random");
    System.out.println(getRandomRequest);

    try {
      this.addNameHeader();
      final GetRandomResponse _return = new GetRandomResponse();
      _return.setValue((int) (Math.random() * 175));
      return _return;
    } catch (final java.lang.Exception ex) {
      ex.printStackTrace();
      throw new RuntimeException(ex);
    }
  }
The WebServiceContext was a bean injected into the class:
<bean id="wsContext" class="org.apache.cxf.jaxws.context.WebServiceContextImpl"/>
The WebServiceContext is a JAXWS interface which we have injected with a CXF implementation. The code involved the following steps:
  1. Get a reference to the MessageContext - which is nothing but a map of key value pairs. 
  2. To this map we add a list of header objects. 
  3. When this response is translated to SOAP, all the headers in the list are written to the SOAP header element.
An interesting question here is that since the WebServiceContext is not a local variable, but is used in all the request calls, how does the WebServiceContext know which header list is associated with which response. Or more specifically which MessageContext is associated with which thread ?
I looked up the WebServiceContext interface:
WebServiceContext makes it possible for a web service endpoint implementation class to 
access message context and security information relative to a request being served
The interface indicates that the message context is :
  1. Associated with a request ant not a response. 
  2. A different and unique message context must be bound to every request object. When a response to this request is needed to be returned, the data from the message context is written to the response. 
This behavior has been achieved in CXF using ThreadLocal variables.
public class WebServiceContextImpl implements WebServiceContext {

  private static ThreadLocal<MessageContext> context = new ThreadLocal<MessageContext>();
  private final MessageContext localCtx;

  public WebServiceContextImpl() {
    localCtx = null;
  }

  public WebServiceContextImpl(MessageContext c) {
    localCtx = c;
  }

  // Implementation of javax.xml.ws.WebServiceContext
  public final MessageContext getMessageContext() {
    return localCtx == null ? context.get() : localCtx;
  }
  // ... remaining code

}
If I were to make a call to the webservice then the response is:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header>
      <RandomHeader xmlns="http://ws.com/Service/xsd/randomschema">
         <hostName>WhoCares !!</hostName>
      </RandomHeader>
   </soap:Header>
   <soap:Body>
      <GetRandomResponse xmlns="http://ws.com/Service/xsd/randomschema">
         <value>73</value>
      </GetRandomResponse>
   </soap:Body>
</soap:Envelope>

No comments:

Post a Comment