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

209

有没有一种简单的方法(即不使用代理)可以获取使用JAX-WS参考实现(包含在JDK 1.5及更高版本中)发布的Web服务的原始请求/响应XML?

我需要通过代码来实现这一点。

只需通过 clever logging 配置将其记录到文件中就可以了,但这样做可能不够好。

我知道存在其他更复杂和完整的框架,可以完成此任务,但我想保持尽可能简单,而axis、cxf等都会增加相当大的开销,我希望避免这种情况。

谢谢!


6
注意:JAX-WS是一个标准,CXF实现了这个标准。 - Bozho
设置Java系统属性和环境变量,请参见:<br> https://dev59.com/fmw05IYBdhLWcg3wwEUS - DaveX
20个回答

346

以下选项允许将所有通信记录到控制台(实际上,您只需要其中之一,但这取决于您使用的库,因此设置所有四个选项是更安全的选择)。您可以像示例中那样在代码中设置它,也可以使用-D作为命令行参数或作为环境变量,就像Upendra所写的那样。

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");

详细信息请参见问题 Tracing XML request/responses with JAX-WS when error occurs


7
谢谢,这是我在解决这个问题中找到的最好答案。 - M Smith
6
当客户端在Tomcat上运行时,这对我无效。只有 -D 部分有效。我相信这是由于Tomcat中的类加载器结构所致。 - Rop
3
System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true"); 是适用于JDK7捆绑的JAX-WS 2.2 RI并默认使用的正确设置。 - Glenn Bech
2
为了在Tomcat中进行此项工作,您需要将以下命令添加到catalina.sh中的JAVA_OPTS中。例如,在第一行添加:JAVA_OPTS=" -Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true -Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true -Dcom.sun.xml.ws.transport.http.HttpAdapter.dump=true -Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=true" 之后,您可以检查catalina.out并查看输出结果。 - Reece
5
同时添加 System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dumpTreshold", "999999"); 以尽量避免请求和响应输出被截断。 - 8bitme
显示剩余5条评论

97

以下是原始代码的解决方案(由stjohnroe和Shamik提供):

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

SOAPLoggingHandler是什么(从链接示例中提取):

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);
        }
    }
}

8
如果您仍然没有看到上面代码的响应/请求XML,请查看以下链接:https://dev59.com/qnE85IYBdhLWcg3wYSVk#2808663 - ian_scho
4
这取决于存在一个SOAPMessage对象,因此如果您收到服务器响应格式不正确的情况,它将失败(只会打印异常信息,而不是跟踪)。如果即使有错误发生时您需要一份跟踪记录,请检查我的答案。 - Mr. Napik
在上面的代码片段中,关于最后一行ep.publish(publishURL);publishURL是什么(在我的代码中,wsdl url包含在服务本身中;我没有任何外部url。我错过了什么吗?) - badera
如果您想在所有接口上发布它,则publishUrl类似于此(hltp = http):“hltp://0.0.0.0:8080/standalone/service”。在这种特殊情况下,您可以访问“hltp://127.0.0.1:8080/standalone/service/yourService”处的服务,其中“yourService”是wsdl中定义的wsdl端口位置。 - riskop
这个解决方案的一个问题是,SOAPHeader元素似乎没有被记录,如果您关心HTTP Headers,它们也不会被记录。 - f1dave
显示剩余2条评论

57

在启动Tomcat之前,在Linux环境下将JAVA_OPTS设置如下。然后启动Tomcat。您将在catalina.out文件中看到请求和响应。

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

3
太棒了。在我看来,这是最好的答案。 - Pablo Santa Cruz
由于某种原因,对我来说是:-Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true - tibo
由于某种原因,这仅适用于我的3个Web服务中的一个(我在Tomcat Web应用程序中有3个JAX-WS Web服务)。有任何想法为什么它在所有3个上都不能工作吗? - David Brossard
对我来说很好用,可以看到为什么我的测试失败了(在我的测试的“运行配置”中设置选项为“VM参数”)。 - MrSmith42
你刚刚用最好的答案震撼了互联网。 - vikingsteve

20

SOAPHandler 注入端点接口,我们可以跟踪 SOAP 请求和响应。

使用编程方式实现 SOAPHandler

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);

声明式可以通过在您的端点接口上添加@HandlerChain(file = "handlers.xml")注释来实现。

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) {
    }

}

我正在完全按照这个步骤进行。我在对头部进行修改后打印出消息,但是我没有看到这些更改。似乎消息直到离开handleMessage方法之后才会发生变化。 - Iofacture
如果我调用两次打印消息,第二次会有更新。非常奇怪。 - Iofacture
1
在我的 JBoss 7.3 上运行良好。谢谢。 - Heiner

18

设置以下系统属性,这将启用XML日志记录。您可以在Java或配置文件中进行设置。

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");
    }

控制台日志:

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
--------------------------------------

11
有多种编程方式可以实现这一点,正如其他答案中所描述的那样,但它们都是相当侵入性的机制。然而,如果你知道你正在使用JAX-WS RI(也称为“Metro”),那么你可以在配置级别上进行此操作。请参考这里的说明了解如何操作。无需对你的应用程序进行任何修改。

2
Metro = JAX-WS RI + WSIT(即JAX-WS RI != Metro) - Pascal Thivent
@Pau:已修复。你知道吗,与其因此对我投反对票,不如花点功夫提出一个替代链接建议。 - skaffman
1
如果我找到了,我肯定会放上去的。不要把它当成个人攻击。已经取消了踩的评价 ;) - Pau
链接又挂了(java.net怎么了???)。我认为这是新链接:http://metro.java.net/nonav/1.2/guide/Logging.html - sdoca
看起来他们已经转移到了GitHub Pages;我已经用最新的链接更新了答案。 - undefined

9
// 这个解决方案提供了一种在没有XML配置的情况下以编程方式向Web服务客户端添加处理程序的方法
// 详细文档请参见:http://docs.oracle.com/cd/E17904_01//web.1111/e13734/handlers.htm#i222476 // 创建一个实现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) {
}
}

// 以编程方式添加 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);

5

我发布了一个新答案,因为我没有足够的声望来评论由Antonio提供的答案(参见:https://dev59.com/UHI-5IYBdhLWcg3wQV8Z#1957777)。

如果你想要将SOAP消息打印到文件中(例如通过Log4j),你可以使用以下代码:

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());

请注意,在某些情况下,方法调用writeTo()可能不像预期的那样工作(请参见:https://community.oracle.com/thread/1123104?tstart=0https://www.java.net/node/691073),因此以下代码可以解决问题:
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());

3
这里列出的指导您使用SOAPHandler的答案是完全正确的。这种方法的好处在于它将与任何JAX-WS实现一起工作,因为SOAPHandler是JAX-WS规范的一部分。但是,SOAPHandler的问题在于它隐式地尝试在内存中表示整个XML消息。这可能会导致巨大的内存使用量。 JAX-WS的各种实现已经为此添加了自己的解决方法。如果您处理大型请求或响应,则需要查看专有方法之一。
既然您询问“JDK 1.5或更高版本中包含的一个”,我将针对正式称为JAX-WS RI(又名Metro)的内容进行回答,这是包含在JDK中的内容。
JAX-WS RI具有特定的解决方案,非常高效地使用内存。
请参见https://javaee.github.io/metro/doc/user-guide/ch02.html#efficient-handlers-in-jax-ws-ri。不幸的是,该链接现在已经失效,但您可以在WayBack Machine上找到它。以下是要点:
2007年,Metro团队引入了一种额外的处理程序类型MessageHandler<MessageHandlerContext>,它是Metro专有的。它比SOAPHandler<SOAPMessageContext>更有效,因为它不尝试进行内存中的DOM表示。
以下是原始博客文章中的关键文本:
MessageHandler: 利用JAX-WS规范提供的可扩展处理程序框架和RI中更好的消息抽象,我们引入了一种称为MessageHandler的新处理程序,以扩展您的Web服务应用程序。 MessageHandler类似于SOAPHandler,但实现可以访问MessageHandlerContext(MessageContext的扩展)。 通过MessageHandlerContext,可以访问消息并使用消息API对其进行处理。正如我在博客标题中所述,此处理程序使您可以在消息上工作,从而提供了访问/处理消息的高效方式,而不仅仅是基于DOM的消息。处理程序的编程模型相同,消息处理程序可以与标准逻辑和SOAP处理程序混合使用。我已经添加了一个示例,在JAX-WS RI 2.1.3中显示了使用MessageHandler记录消息的用法,以下是示例的片段:
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;
    }
}

不用说,您的自定义Handler(例如示例中的LoggingHandler)需要添加到处理程序链中才能生效。这与添加任何其他Handler相同,因此您可以查看此页面上的其他答案以了解如何执行此操作。

您可以在Metro GitHub存储库中找到一个完整示例


似乎连MesageHandler都处理解析后的XML响应,这意味着如果你收到损坏的响应,就无法进入处理程序。使用管道记录器(如com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe),你有机会看到损坏的响应。 - undefined

2
你需要实现一个javax.xml.ws.handler.LogicalHandler,然后在处理程序配置文件中引用此处理程序,该文件又被服务端点(接口或实现)中的@HandlerChain注释引用。然后你可以通过processMessage实现来输出消息,可以使用system.out或日志记录器。
请参阅:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接