I have created SOAP client in pure java. Also I have implemented SOAPHandler to trace the SOAP requests and responses. But when I see the sysouts the header part is missing in the request.
Note that I am calling an enterprise service and that requires a mandatory security header for the service to be called. I am getting proper response with my security header back in the response and also is being sysout.
What could be the issue for blank header trace from SOAPHandler ? Below is my handler code:
public class SOAPLoggingHandler implements SOAPHandler<SOAPMessageContext> {
private static final Logger log = LoggerFactory.getLogger(SOAPLoggingHandler.class);
public boolean handleMessage(SOAPMessageContext context) {
log.info("handleMessage");
logToSystemOut(context);
return true;
}
public boolean handleFault(SOAPMessageContext context) {
log.info("SOAP Fault Occur");
logToSystemOut(context);
return true;
}
public void close(MessageContext context) {
log.info("close");
}
public Set<QName> getHeaders() {
log.info("getHeaders");
return null;
}
protected void logToSystemOut(SOAPMessageContext smc) {
final Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
final String message = !outboundProperty.booleanValue() ? "Web Service Response\n" : "Web Service Request\n";
logMessage(smc, message);
}
protected void logToSystemOut(SOAPMessageContext smc, String message) {
logMessage(smc, message);
}
private void logMessage(SOAPMessageContext smc, String message) {
try {
final ByteArrayOutputStream oStream = new ByteArrayOutputStream();
final Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(new DOMSource(smc.getMessage().getSOAPPart()), new StreamResult(oStream));
oStream.close();
log.info(message + new String(oStream.toByteArray()));
} catch (final Exception e) {
log.warn("Failed to log web service message", e);
}
//some custom logging going on below - could be ignored
SomeLogger someLoggerObj = SomeLogger.getInstance();
try {
someLoggerObj.logSomeMessage(smc, message);
} catch (SOAPException e) {
log.error("SOAP Exception occurred :", e);
} catch (TransformerException e) {
log.error("TransformerException Exception occurred :", e);
}
}
}
My Header is seen as:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
I've experienced this week exactly the same issue, I have a chain of 5 SOAP calls with also TLS and SAML, and a mix of services implemented with AXIS and CXF, running aunder the same Tomcat 9 on OpenJDK 11. The issue happened only on production server (RedHat server) and I was not able to reproduce it on my development machine, the header was completely dropped by the java client and it was not possible to debug on the server.
I ended up installing on the target server a dedicated Tomcat 8.5 on JDK 1.8 to run CXF service separated from AXIS ones.
Hope this helps if someone has such a complex configuration, it's not a solution but a workaround.
Sorry for multiple edits.Should have reviewed before posting
I am using the getRequest method provided by MessageContext (org.springframework.ws.context.MessageContext) to retrieve the backend soap request in an interceptor that extends ClientInterceptor. This is beind done within handleRequest method. I am fetching this to log the request into a file. When I do this , the security section within the header which has the user id and password is also getting logged. I would like to remove this before logging. Are there any available mechanisms to remove this element or should I manipulate the String in order to take the element out?
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
getPayloadFromSoapMessage((SoapMessage) messageContext.getRequest());
}
protected String getPayloadFromSoapMessage(SoapMessage message) {
String payload = "Error parsing";
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
message.writeTo(bos);
payload = bos.toString();
} catch (IOException e) {
LOG.error("Error parsing the SoapMessage", e);
}
return payload;
}
Using CXF interceptor, I want to alter the response body which is being sent to the client as response. (I want to add some more nodes)
My interceptor is configured as an outgoing interceptor, is being invoked but the body is empty.
public class AlterOutBodyInterceptor extends AbstractSoapInterceptor {
private SAAJOutInterceptor saajOut = new SAAJOutInterceptor();
public AlterOutBodyInterceptor() {
super(Phase.PRE_MARSHAL);
getBefore().add(SAAJOutInterceptor.class.getName());
}
public void handleMessage(SoapMessage message) throws Fault {
SOAPMessage doc = message.getContent(SOAPMessage.class);
if (doc == null) {
saajOut.handleMessage(message);
doc = message.getContent(SOAPMessage.class);
// it is executed
}
try {
SOAPBody body = doc.getSOAPBody();
// here, body doesn't contain anything that the client gets
} catch (SOAPException e) {
e.printStackTrace();
}
}
}
I am doing trial-error with getBefore - getAfter and Phases, but without luck. Thanks in advance!
The solution is as here:
(we checked cxf sources because it contains a lot of interceptors, we got the ideas from there)
public class Interceptor extends AbstractPhaseInterceptor<Message> {
private ObjectFactory objectFactory = new ObjectFactory();
public Interceptor() {
super(Phase.PRE_LOGICAL);
addBefore(HolderOutInterceptor.class.getName());
}
#Override
public void handleMessage(Message message) {
MessageContentsList outObjects = MessageContentsList.getContentsList(message);
Assert.isTrue(outObjects.size() == 1);
Object outObject = outObjects.get(0);
// object is our soap response
}
}
I have a SOAP web service implementation on Jboss 4.2.3. I want to add a version number check for the service. Whenever a client makes a call, I will pass the client version number. I will write an interceptor at the server that would check the client version number. If it is a client with a different version number, I would not process the request.
What I want to know is if there is a way to pass the version number from the client in some context parameter other than adding it in the web service method signature?
In general, if I want to pass some custom META-DATA from client to server, how do I do it ?
In general, if I want to pass some custom META-DATA from client to
server, how do I do it ?
This can be achieved through SOAP Message Handlers both side (Client and Server ) in Jax-WS .
Client Side:
The custom-meta-data , like version number, UUID , Signature information can be added via SOAP Headers.
1..Write a VersionNumberHandler as shown below.
public class VersionNumberHandler implements SOAPHandler<SOAPMessageContext> {
private static final String LoggerName = "ClientSideLogger";
private Logger logger;
private final boolean log_p = true; // set to false to turn off
public VersionNumberHandler() {
logger = Logger.getLogger(LoggerName);
}
public boolean handleMessage(SOAPMessageContext ctx) {
if (log_p)
logger.info("handleMessage");
// Is this an outbound message, i.e., a request?
Boolean request_p = (Boolean) ctx
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
// Manipulate the SOAP only if it's a request
if (request_p) {
// Get the Version Number from some property file ,
// to place in the message header.
String versionNumber = "v1.0";
try {
SOAPMessage msg = ctx.getMessage();
SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
SOAPHeader hdr = env.getHeader();
// Ensure that the SOAP message has a header.
if (hdr == null)
hdr = env.addHeader();
QName qname = new QName("http://ticket.example.com/",
"versionnumber");
SOAPHeaderElement helem = hdr.addHeaderElement(qname);
// In SOAP 1.2, setting the actor is equivalent to
// setting the role.
helem.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT);
helem.setMustUnderstand(true);
helem.addTextNode(versionNumber);
msg.saveChanges();
// For tracking, write to standard output.
msg.writeTo(System.out);
} catch (SOAPException e) {
System.err.println(e);
} catch (IOException e) {
System.err.println(e);
}
}
return true; // continue down the chain
}
public boolean handleFault(SOAPMessageContext ctx) {
if (log_p)
logger.info("handleFault");
try {
ctx.getMessage().writeTo(System.out);
} catch (SOAPException e) {
System.err.println(e);
} catch (IOException e) {
System.err.println(e);
}
return true;
}
public Set<QName> getHeaders() {
if (log_p)
logger.info("getHeaders");
return null;
}
public void close(MessageContext messageContext) {
if (log_p)
logger.info("close");
}
2..Mention this class in the Handler-Chain.xml.
<javaee:handler>
<javaee:handler-class>
com.example.client.handler.VersionNumberHandler
</javaee:handler-class>
</javaee:handler>
3..Add the handler-chain in the client (Stub) also.
#WebServiceClient(name = "TicketWSImplService", targetNamespace = "http://ticket.example.com/", wsdlLocation = "http://localhost:8080/ticket?wsdl")
#HandlerChain(file = "handler-chain.xml")
public class TicketWSImplService extends Service {
#WebMethod
public void method(){
}
Here, we are adding a new header element "versionnumber" and mustunderstand=true, which means the server/intermediaries has to process this element, otherwise Jax-WS-Runtime will throw SOAP Fault exception to the client. Now we need to write a Validator(SOAP Handler) at the server side to validate this version number which is being passed by the clients.
Server Side:
1..Write a VersionNumberValidator as shown below.
public class VersionNumberValidator implements SOAPHandler<SOAPMessageContext> {
#SuppressWarnings("unused")
#Override
public boolean handleMessage(SOAPMessageContext ctx) {
// Is this an inbound message, i.e., a request?
Boolean response_p = (Boolean) ctx
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
// Manipulate the SOAP only if it's incoming.
if (!response_p) {
try {
SOAPMessage msg = ctx.getMessage();
SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
SOAPHeader hdr = env.getHeader();
// Ensure that the SOAP message has a header.
if (hdr == null) {
generateSOAPFault(msg, "No message header.");
return true;
}
Iterator mustUnderstandHeaders = msg.getSOAPHeader()
.examineMustUnderstandHeaderElements(
"http://schemas.xmlsoap.org/soap/actor/next");
String value = null;
while (mustUnderstandHeaders.hasNext()) {
Node next = (Node) mustUnderstandHeaders.next();
System.out.println("mustUnderstandHeaders name:"
+ next.getValue());
if (next.getNodeName().equalsIgnoreCase("versionnumber"))
value = next.getValue();
if (value != null && !value.equalsIgnoreCase("v1.0")) {
generateSOAPFault(msg, "Version Number Mismatch");
}
}
// For tracking, write to standard output.
msg.writeTo(System.out);
} catch (SOAPException e) {
System.err.println(e);
} catch (IOException e) {
System.err.println(e);
}
}
return true; // continue down the chain
}
#Override
public boolean handleFault(SOAPMessageContext ctx) {
return true; // do continue down the chain
}
// For now, no-ops.
#Override
public Set<QName> getHeaders() {
Set<QName> headers = new HashSet<QName>();
QName qName = new QName("http://ticket.example.com/", "versionnumber");
headers.add(qName);
return headers;
}
#Override
public void close(MessageContext messageContext) {
}
private void generateSOAPFault(SOAPMessage msg, String reason) {
try {
SOAPBody body = msg.getSOAPBody();
SOAPFault fault = body.addFault();
QName fault_name = new QName(
SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE, "UltimateReceiver");
fault.setFaultCode(fault_name);
fault.setFaultRole("http://ticket.example.com/versionNumber_validator");
fault.addFaultReasonText(reason, Locale.US);
} catch (SOAPException e) {
}
}
2..Mention this class in the Handler-Chain-server.xml.
<javaee:handler>
<javaee:handler-class>
com.example.client.handler.VersionNumberValidator
</javaee:handler-class>
</javaee:handler>
3..Publish the webservices.
Now, the every client request will be having "version number =v1.0", At the server side , you will be validating this value is correct or not. If it is not correct, SOAPFaultException will be thrown.
You could add it to the http-headers but that would mean your client would need to do this which also means they can change it and give you wrong numbers causing issues on the server. It's only as reliable as the messages being sent in.
Either way, this isn't the right way to restrict access to your Web Service, you should use http basic authentication or if it's version differences then you should create multiple version endpoints giving clients access to the versions they need.
Also, JBoss 4.2.3 is so old it might not even work. See [1]
Mus
[1] https://community.jboss.org/message/534711
It's a bad idea to try to add out-of-band metadata to a web service. Just pick a new URL for each version if the data structures are incompatible. If they are compatible, put the version number inside the request.
This way you can still support interoperation with all different libraries and not require your clients to find a new hoop to jump through for each toolkit.
I am hosting a web service. I wanted to handle the scenario, in case the client sends a mal-formed XML
Here is the handler I have created
public boolean handleMessage(SOAPMessageContext smc)
{
Boolean outboundProperty =
(Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
SOAPMessage message = null;
try
{
// This handling iss required in case a mal-formed XML is sent.
message = smc.getMessage();
}
catch(Throwable t )
{
// I want to log the XML in the database
// But the problem is I don't know how to get the XML
// as message is null.
}
}
I get to land into the catch(Throwable t) block where I have no information about the XML that was sent.
What I can log into the error log table is just that a mal-formed XML has been sent by client.
Actual Requirement:
To log and store the malformed XML for tracking purposes.
Please use the following code for getting the exact line number and exception in soap message for logging purposes:
public boolean handleMessage(SOAPMessageContext mc) {
try {
final SOAPMessage message = mc.getMessage();
String i = convertToString(message);
System.out.println(i);
return true;
} catch (Exception e) {
System.out.println(e.getMessage());
// e.printStackTrace();
return false;
}
}
This Code will take care of the any malformed SOAP Message