使用JAX-WS跟踪XML请求/响应

有没有一种简单的方法(即:不使用代理)来访问使用JAX-WS参考实现(JDK 1.5和更高版本中包含的实现)发布的Web服务的原始请求/响应XML? 能够通过代码做到这一点是我需要做的。 只要通过巧妙的日志记录配置将其记录到文件中就很好了,但这就足够了

我知道还有其他更复杂、更完整的框架可以做到这一点,但我想让它尽可能简单,而Axis、CXF等都会增加相当大的开销,这是我想避免的。

谢谢!

288915 次浏览

You could try to put a ServletFilter in front of the webservice and inspect request and response going to / returned from the service.

Although you specifically did not ask for a proxy, sometimes I find tcptrace is enough to see what goes on on a connection. It's a simple tool, no install, it does show the data streams and can write to file too.

One way to do is not using your code but use network packet sniffers like Etheral or WireShark which can capture the HTTP packet with the XML message as payload to it and you can keep logging them to a file or so.

But more sophisticated approach is to write your own message handlers. You can have a look at it here.

You need to implement a javax.xml.ws.handler.LogicalHandler, this handler then needs to be referenced in a handler configuration file, which in turn is referenced by an @HandlerChain annotation in your service endpoint (interface or implementation). You can then either output the message via system.out or a logger in your processMessage implementation.

See

http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/twbs_jaxwshandler.html

http://java.sun.com/mailers/techtips/enterprise/2006/TechTips_June06.html

Here is the solution in raw code (put together thanks to stjohnroe and Shamik):

Endpoint ep = Endpoint.create(new WebserviceImpl());
List<Handler> handlerChain = ep.getBinding().getHandlerChain();
handlerChain.add(new SOAPLoggingHandler());
ep.getBinding().setHandlerChain(handlerChain);
ep.publish(publishURL);

Where SOAPLoggingHandler is (ripped from linked examples):

package com.myfirm.util.logging.ws;


import java.io.PrintStream;
import java.util.Map;
import java.util.Set;


import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;


/*
* This simple SOAPHandler will output the contents of incoming
* and outgoing messages.
*/
public class SOAPLoggingHandler implements SOAPHandler<SOAPMessageContext> {


// change this to redirect output if desired
private static PrintStream out = System.out;


public Set<QName> getHeaders() {
return null;
}


public boolean handleMessage(SOAPMessageContext smc) {
logToSystemOut(smc);
return true;
}


public boolean handleFault(SOAPMessageContext smc) {
logToSystemOut(smc);
return true;
}


// nothing to clean up
public void close(MessageContext messageContext) {
}


/*
* Check the MESSAGE_OUTBOUND_PROPERTY in the context
* to see if this is an outgoing or incoming message.
* Write a brief message to the print stream and
* output the message. The writeTo() method can throw
* SOAPException or IOException
*/
private void logToSystemOut(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean)
smc.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);


if (outboundProperty.booleanValue()) {
out.println("\nOutbound message:");
} else {
out.println("\nInbound message:");
}


SOAPMessage message = smc.getMessage();
try {
message.writeTo(out);
out.println("");   // just to add a newline
} catch (Exception e) {
out.println("Exception in handler: " + e);
}
}
}

There are various ways of doing this programmatically, as described in the other answers, but they're quite invasive mechanisms. However, if you know that you're using the JAX-WS RI (aka "Metro"), then you can do this at the configuration level. See here for instructions on how to do this. No need to mess about with your application.

Before starting tomcat, set JAVA_OPTS as below in Linux envs. Then start Tomcat. You will see the request and response in the catalina.out file.

export JAVA_OPTS="$JAVA_OPTS -Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true"

Following options enable logging of all communication to the console (technically, you only need one of these, but that depends on the libraries you use, so setting all four is safer option). You can set it in the code like in example, or as command line parameter using -D or as environment variable as Upendra wrote.

System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
System.setProperty("com.sun.xml.ws.transport.http.HttpAdapter.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dumpTreshold", "999999");

See question Tracing XML request/responses with JAX-WS when error occurs for details.

// This solution provides a way programatically add a handler to the web service clien w/o the XML config

// See full doc here: http://docs.oracle.com/cd/E17904_01//web.1111/e13734/handlers.htm#i222476

// Create new class that implements SOAPHandler

public class LogMessageHandler implements SOAPHandler<SOAPMessageContext> {


@Override
public Set<QName> getHeaders() {
return Collections.EMPTY_SET;
}


@Override
public boolean handleMessage(SOAPMessageContext context) {
SOAPMessage msg = context.getMessage(); //Line 1
try {
msg.writeTo(System.out);  //Line 3
} catch (Exception ex) {
Logger.getLogger(LogMessageHandler.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}


@Override
public boolean handleFault(SOAPMessageContext context) {
return true;
}


@Override
public void close(MessageContext context) {
}
}

// Programatically add your LogMessageHandler

   com.csd.Service service = null;
URL url = new URL("https://service.demo.com/ResService.svc?wsdl");


service = new com.csd.Service(url);


com.csd.IService port = service.getBasicHttpBindingIService();
BindingProvider bindingProvider = (BindingProvider)port;
Binding binding = bindingProvider.getBinding();
List<Handler> handlerChain = binding.getHandlerChain();
handlerChain.add(new LogMessageHandler());
binding.setHandlerChain(handlerChain);

In runtime you could simply execute

com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump = true

as dump is a public var defined in the class as follows

public static boolean dump;

I am posting a new answer, as I do not have enough reputation to comment on the one provided by Antonio (see: https://stackoverflow.com/a/1957777).

In case you want the SOAP message to be printed in a file (e.g. via Log4j), you may use:

OutputStream os = new ByteArrayOutputStream();
javax.xml.soap.SOAPMessage soapMsg = context.getMessage();
soapMsg.writeTo(os);
Logger LOG = Logger.getLogger(SOAPLoggingHandler.class); // Assuming SOAPLoggingHandler is the class name
LOG.info(os.toString());

Please note that under certain circumstances, the method call writeTo() may not behave as expected (see: https://community.oracle.com/thread/1123104?tstart=0 or https://www.java.net/node/691073), therefore the following code will do the trick:

javax.xml.soap.SOAPMessage soapMsg = context.getMessage();
com.sun.xml.ws.api.message.Message msg = new com.sun.xml.ws.message.saaj.SAAJMessage(soapMsg);
com.sun.xml.ws.api.message.Packet packet = new com.sun.xml.ws.api.message.Packet(msg);
Logger LOG = Logger.getLogger(SOAPLoggingHandler.class); // Assuming SOAPLoggingHandler is the class name
LOG.info(packet.toString());

am I correct in understanding that you want to change/access the raw XML message?

If so, you (or since this is five years old, the next guy) might want to have a look at the Provider interface that is part of the JAXWS. The client counterpart is done using the "Dispatch" class. Anyway, you don't have to add handlers or interceptors. You still CAN, of course. The downside is this way, you are COMPLETELY responsible for building the SOAPMessage, but its easy, and if that's what you want(like I did) this is perfect.

Here is an example for the server side(bit clumsy, it was just for experimenting)-

@WebServiceProvider(portName="Provider1Port",serviceName="Provider1",targetNamespace = "http://localhost:8123/SoapContext/SoapPort1")
@ServiceMode(value=Service.Mode.MESSAGE)
public class Provider1 implements Provider<SOAPMessage>
{
public Provider1()
{
}


public SOAPMessage invoke(SOAPMessage request)
{ try{




File log= new File("/home/aneeshb/practiceinapachecxf/log.txt");//creates file object
FileWriter fw=new FileWriter(log);//creates filewriter and actually creates file on disk


fw.write("Provider has been invoked");
fw.write("This is the request"+request.getSOAPBody().getTextContent());


MessageFactory mf = MessageFactory.newInstance();
SOAPFactory sf = SOAPFactory.newInstance();


SOAPMessage response = mf.createMessage();
SOAPBody respBody = response.getSOAPBody();
Name bodyName = sf.createName("Provider1Insertedmainbody");
respBody.addBodyElement(bodyName);
SOAPElement respContent = respBody.addChildElement("provider1");
respContent.setValue("123.00");
response.saveChanges();
fw.write("This is the response"+response.getSOAPBody().getTextContent());
fw.close();
return response;}catch(Exception e){return request;}




}
}

You publish it like you would an SEI,

public class ServerJSFB {


protected ServerJSFB() throws Exception {
System.out.println("Starting Server");
System.out.println("Starting SoapService1");


Object implementor = new Provider1();//create implementor
String address = "http://localhost:8123/SoapContext/SoapPort1";


JaxWsServerFactoryBean svrFactory = new JaxWsServerFactoryBean();//create serverfactorybean


svrFactory.setAddress(address);
svrFactory.setServiceBean(implementor);


svrFactory.create();//create the server. equivalent to publishing the endpoint
System.out.println("Starting SoapService1");
}


public static void main(String args[]) throws Exception {
new ServerJSFB();
System.out.println("Server ready...");


Thread.sleep(10 * 60 * 1000);
System.out.println("Server exiting");
System.exit(0);
}
}

Or you can use an Endpoint class for it. Hope that has been helpful.

And oh, if you want you needn't deal with headers and stuff, if you change the service mode to PAYLOAD(You'll only get the Soap Body).

Inject SOAPHandler to endpoint interface. we can trace the SOAP request and response

Implementing SOAPHandler with Programmatic

ServerImplService service = new ServerImplService();
Server port = imgService.getServerImplPort();
/**********for tracing xml inbound and outbound******************************/
Binding binding = ((BindingProvider)port).getBinding();
List<Handler> handlerChain = binding.getHandlerChain();
handlerChain.add(new SOAPLoggingHandler());
binding.setHandlerChain(handlerChain);

Declarative by adding @HandlerChain(file = "handlers.xml") annotation to your endpoint interface.

handlers.xml

<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
<handler-chain>
<handler>
<handler-class>SOAPLoggingHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>

SOAPLoggingHandler.java

/*
* This simple SOAPHandler will output the contents of incoming
* and outgoing messages.
*/




public class SOAPLoggingHandler implements SOAPHandler<SOAPMessageContext> {
public Set<QName> getHeaders() {
return null;
}


public boolean handleMessage(SOAPMessageContext context) {
Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (isRequest) {
System.out.println("is Request");
} else {
System.out.println("is Response");
}
SOAPMessage message = context.getMessage();
try {
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
message.writeTo(System.out);
} catch (SOAPException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}


public boolean handleFault(SOAPMessageContext smc) {
return true;
}


// nothing to clean up
public void close(MessageContext messageContext) {
}


}

Actually. If you look into sources of HttpClientTransport you will notice that it is also writing messages into java.util.logging.Logger. Which means you can see those messages in your logs too.

For example if you are using Log4J2 all you need to do is the following:

  • add JUL-Log4J2 bridge into your class path
  • set TRACE level for com.sun.xml.internal.ws.transport.http.client package.
  • add -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager system property to your applicaton start command line

After these steps you start seeing SOAP messages in your logs.

Set the following system properties, this will enabled xml logging. You can set it in java or configuration file.

static{
System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
System.setProperty("com.sun.xml.ws.transport.http.HttpAdapter.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dumpTreshold", "999999");
}

console logs:

INFO: Outbound Message
---------------------------
ID: 1
Address: http://localhost:7001/arm-war/castService
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: xml
--------------------------------------
INFO: Inbound Message
----------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml; charset=UTF-8
Headers: {content-type=[text/xml; charset=UTF-8], Date=[Fri, 20 Jan 2017 11:30:48 GMT], transfer-encoding=[chunked]}
Payload: xml
--------------------------------------

The answers listed here which guide you to use SOAPHandler are fully correct. The benefit of that approach is that it will work with any JAX-WS implementation, as SOAPHandler is part of the JAX-WS specification. However, the problem with SOAPHandler is that it implicitly attempts to represent the whole XML message in memory. This can lead to huge memory usage. Various implementations of JAX-WS have added their own workarounds for this. If you work with large requests or large responses, then you need to look into one of the proprietary approaches.

Since you ask about "the one included in JDK 1.5 or better" I'll answer with respect to what is formally known as JAX-WS RI (aka Metro) which is what is included with the JDK.

JAX-WS RI has a specific solution for this which is very efficient in terms of memory usage.

See https://javaee.github.io/metro/doc/user-guide/ch02.html#efficient-handlers-in-jax-ws-ri. Unfortunately that link is now broken but you can find it on WayBack Machine. I'll give the highlights below:

The Metro folks back in 2007 introduced an additional handler type, MessageHandler<MessageHandlerContext>, which is proprietary to Metro. It is far more efficient than SOAPHandler<SOAPMessageContext> as it doesn't try to do in-memory DOM representation.

Here's the crucial text from the original blog article:

MessageHandler:

Utilizing the extensible Handler framework provided by JAX-WS Specification and the better Message abstraction in RI, we introduced a new handler called MessageHandler to extend your Web Service applications. MessageHandler is similar to SOAPHandler, except that implementations of it gets access to MessageHandlerContext (an extension of MessageContext). Through MessageHandlerContext one can access the Message and process it using the Message API. As I put in the title of the blog, this handler lets you work on Message, which provides efficient ways to access/process the message not just a DOM based message. The programming model of the handlers is same and the Message handlers can be mixed with standard Logical and SOAP handlers. I have added a sample in JAX-WS RI 2.1.3 showing the use of MessageHandler to log messages and here is a snippet from the sample:

public class LoggingHandler implements MessageHandler<MessageHandlerContext> {
public boolean handleMessage(MessageHandlerContext mhc) {
Message m = mhc.getMessage().copy();
XMLStreamWriter writer = XMLStreamWriterFactory.create(System.out);
try {
m.writeTo(writer);
} catch (XMLStreamException e) {
e.printStackTrace();
return false;
}
return true;
}


public boolean handleFault(MessageHandlerContext mhc) {
.....
return true;
}


public void close(MessageContext messageContext) {    }


public Set getHeaders() {
return null;
}
}

(end quote from 2007 blog post)

Needless to say your custom Handler, LoggingHandler in the example, needs to be added to your Handler Chain to have any effect. This is the same as adding any other Handler, so you can look in the other answers on this page for how to do that.

You can find a full example in the Metro GitHub repo.

with logback.xml configuration files, you can do :

<logger name="com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe" level="trace" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>

That will log the request and the response like this (depending on your configuration for the log output) :

09:50:23.266 [qtp1068445309-21] DEBUG c.s.x.i.w.t.h.c.HttpTransportPipe - ---[HTTP request - http://xyz:8081/xyz.svc]---
Accept: application/soap+xml, multipart/related
Content-Type: application/soap+xml; charset=utf-8;action="http://xyz.Web.Services/IServiceBase/GetAccessTicket"
User-Agent: JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e
<?xml version="1.0" ?><S:Envelope xmlns:S="http://www.w3.org/2003/05/soap-envelope">[CONTENT REMOVED]</S:Envelope>--------------------


09:50:23.312 [qtp1068445309-21] DEBUG c.s.x.i.w.t.h.c.HttpTransportPipe - ---[HTTP response - http://xyz:8081/xyz.svc - 200]---
null: HTTP/1.1 200 OK
Content-Length: 792
Content-Type: application/soap+xml; charset=utf-8
Date: Tue, 12 Feb 2019 14:50:23 GMT
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">[CONTENT REMOVED]</s:Envelope>--------------------

I had been trying to find some framework library to log the web service soap request and response for a couple days. The code below fixed the issue for me:

System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
System.setProperty("com.sun.xml.ws.transport.http.HttpAdapter.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dump", "true");

There are a couple of answers using SoapHandlers in this thread. You should know that SoapHandlers modify the message if writeTo(out) is called.

Calling SOAPMessage's writeTo(out) method automatically calls saveChanges() method also. As a result all attached MTOM/XOP binary data in a message is lost.

I am not sure why this is happening, but it seems to be a documented feature.

In addition, this method marks the point at which the data from all constituent AttachmentPart objects are pulled into the message.

https://docs.oracle.com/javase/7/docs/api/javax/xml/soap/SOAPMessage.html#saveChanges()

If you happen to run a IBM Liberty app server, just add ibm-ws-bnd.xml into WEB-INF directory.

<?xml version="1.0" encoding="UTF-8"?>
<webservices-bnd
xmlns="http://websphere.ibm.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-ws-bnd_1_0.xsd"
version="1.0">
<webservice-endpoint-properties
enableLoggingInOutInterceptor="true" />
</webservices-bnd>

Solution for Glassfish/Payara

Add the following entries to the logger settings (log level FINER):

  • com.sun.xml.ws.transport.http.client.HttpTransportPipe
  • com.sun.xml.ws.transport.http.HttpAdapter

Found here.