java CXF请求HTTP上下文未在2个请求之间刷新

nwlqm0z1  于 2023-05-27  发布在  Java
关注(0)|答案(3)|浏览(386)

**TL;DR:**我尝试创建一个测试,该测试对2个不同的SOAP操作(saveUser和getUser)执行2个CXF调用,但第二个失败,因为http请求中的SOAPAction被设置为“saveUser”而不是“getUser”。我找到了两个解决这个问题的方法,但我并不完全满意。

配置

以下是我的端点配置:

final JaxWsProxyFactoryBean jaxWsProxyFactory = new JaxWsProxyFactoryBean();
    jaxWsProxyFactory.setServiceClass(Internal.class);
    jaxWsProxyFactory.setAddress(backendUrl + "INTERNAL");
    jaxWsProxyFactory.setFeatures(getFeatures());
    final Internal portType = (Internal) jaxWsProxyFactory.create();
    ((BindingProvider) portType).getRequestContext().put("thread.local.request.context", "true");
    ((BindingProvider) portType).getRequestContext().put("schema-validation-enabled", "false");

以下是我的用法:

internalBackendG5.saveUser(getRequest);
internalBackendG5.getUser(saveRequest);

这两个操作被定义为(感谢wsdl 2 java):

  • (为了更简洁,我覆盖了一些包,下面的代码可能包含错别字)*
@WebMethod(action = "http://www.test.org/internal/saveUser")
@WebResult(name = "saveUserResponse", targetNamespace = "urn:test.com:xsd:internal:userpreferences", partName = "parameters")
public com.test.internal.type.SaveUserResponseType saveUser(
    @WebParam(partName = "parameters", name = "saveUser", targetNamespace = "urn:test.com:xsd:internal:userpreferences")
    com.test.internal.type.SaveUserRequestType parameters
);

@WebMethod(action = "http://www.test.org/internal/getUser")
@WebResult(name = "getUserResponse", targetNamespace = "urn:test.com:xsd:internal:userpreferences", partName = "parameters")
public com.test.internal.type.GetUserResponseType getUser(
    @WebParam(partName = "parameters", name = "getUser", targetNamespace = "urn:test.com:xsd:internal:userpreferences")
    com.test.internal.type.GetUserRequestType parameters
);

运行时

当我运行我的测试时,我在第二次调用时得到一个错误,它是从后端作为Soap异常抛出的:

javax.xml.ws.soap.SOAPFaultException: Unexpected element {urn:test.com:xsd:internal:userpreferences}getUser found.   Expected {urn:test.com:xsd:internal:userpreferences}saveUser.

当我查看服务器收到的SOAP请求时,我得到:
对于第一个请求(saveUser):

Headers: {[...] SOAPAction=["http://www.test.org/internal/saveUser"], user-agent=[Apache CXF 2.7.12]}
Payload: <soap:Envelope><soap:Body><saveUser>[...]</ns5:saveUser></soap:Body></soap:Envelope>

对于第二个请求(getUser):

Headers: {[...] SOAPAction=["http://www.test.org/internal/saveUser"], user-agent=[Apache CXF 2.7.12]}
Payload: <soap:Envelope><soap:Body><getUser>[...]</getUser></soap:Body></soap:Envelope>

当我只启动两个请求中的一个时,它们都工作。

找到的解决方案

经过一些研究,我发现Apache Cxf InterceptorSoapPreProtocolOutInterceptor是负责的。
当请求中已经存在SoapAction字符串时,它不会覆盖它。我的第二次调用(由于某些我无法解释的原因)重用了在处理第一次调用时计算的Cxf请求上下文!
我做了什么来克服这一点:- 在SoapPreProtocolOutInterceptor之前编写一个拦截器,强制删除头部中的SoapAction,以强制此拦截器重新计算动作。- 或者,我可以重置两个请求之间的请求上下文:

((BindingProvider) portType).getRequestContext().put(Header.HEADER_LIST, emptyList);

提问

所以我的问题是有CXF bug吗?(requestContext不应该在两个请求之间共享)或我错过了一些Cxf配置?
PS:我使用了这个依赖项:

group:'org.apache.cxf', name:'cxf-rt-transports-http-jetty', version:'2.7.5'
group: 'org.apache.cxf', name: 'cxf-rt-features-clustering', version:'2.7.12'
group:'org.apache.cxf', name:'cxf-rt-frontend-jaxws', version:'2.7.12'
group: 'org.apache.cxf', name: 'cxf-api', version: '2.7.12'
group: 'org.apache.cxf', name: 'cxf-rt-bindings-soap', version: '2.7.12'

先谢谢你了!

pqwbnv8z

pqwbnv8z1#

我遇到了同样的问题,经过一些调试,我想我已经找到了原因。
在调用任何操作之前,我在PROTOCOL_HEADERSMap上设置了自己的自定义http头:

Map<String, List<String>> headers = new HashMap<>();
headers.put("header-name", Arrays.asList("value"));
proxy.getRequestContext().put(Message.PROTOCOL_HEADERS, headers);

从您的示例中,我没有看到您正在设置PROTOCOL_HEADERS,但在我的示例中,这是根本原因,因为这是包含SOAPAction头的Map。也许你的代码的其他部分可能会篡改它。
首先,我们知道客户端代理请求上下文不是线程安全的

官方JAX-WS答案:不可以。根据JAX-WS规范,客户机代理不是线程安全的。要编写可移植代码,您应该将它们视为非线程安全的,并同步访问或使用示例池或类似的方法。
CXF回答:CXF代理对于许多用例都是线程安全的。例外情况如下:
使用((BindingProvider)proxy).getRequestContext()-根据JAX-WS规范,请求上下文是PER INSTANCE。因此,在那里设置的任何内容都将影响其他线程上的请求。
这对我使用这个上下文提出了一些危险信号,但这似乎不是问题的原因,我只是向它添加了一个简单的协议头,这不是用户特定的。
上下文在ClientImpl类中声明,并用作每个服务调用的基本上下文:

protected Map<String, Object> currentRequestContext = new ConcurrentHashMap<String, Object>(8, 0.75f, 4);

每次请求上下文时,都会执行以下方法:

public Map<String, Object> getRequestContext() {
    if (isThreadLocalRequestContext()) {
        if (!requestContext.containsKey(Thread.currentThread())) {
            requestContext.put(Thread.currentThread(), new EchoContext(currentRequestContext));
        }
        return requestContext.get(Thread.currentThread());
    }
    return currentRequestContext;
}

假设我们还没有设置thread local属性,所以我们总是返回当前的请求上下文,它现在有我们的PROTOCOL_HEADERSMap,其中仍然没有SOAPAction头。
然后是SoapPreProtocolOutInterceptor类,(我只提取了setSoapAction(...)方法的相关部分:

Map<String, List<String>> reqHeaders 
            = CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
        if (reqHeaders == null) {
            reqHeaders = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
        }

        if (reqHeaders.size() == 0) {
            message.put(Message.PROTOCOL_HEADERS, reqHeaders);
        }

        if (!reqHeaders.containsKey(SoapBindingConstants.SOAP_ACTION)) {            
            reqHeaders.put(SoapBindingConstants.SOAP_ACTION, Collections.singletonList(action));
        }

在这里,我们将在最后一个if语句中结束,当在这个客户端上执行我们的first调用时,从而在SAME协议头Map上设置SOAP_ACTION头,这是当前请求上下文的一部分,在ClientImpl类中声明。
该客户机的任何后续请求都将以已经填充的Map为基础,该Map具有协议头和上下文中的SOAP_ACTION头,从而导致观察到的行为。我们永远不会输入if语句,该语句为我们的服务调用上下文创建一个带有PROTOCOL_HEADERS的新Map。

解决方案:

在我看来,最干净的解决方案是只使用拦截器来设置协议头Map中的值,请注意,我们根本没有对基本请求上下文进行操作:

public class HttpHeaderExampleInterceptor extends AbstractPhaseInterceptor
{
private static final String HEADER_NAME = "header_name";
private String value;

public HttpHeaderExampleInterceptor(String value)
{
    super(Phase.MARSHAL);
    this.value = value;
}

/**
 * @see org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor#setSoapAction(SoapMessage)
 */
@Override
public void handleMessage(Message message) throws Fault
{
    Map<String, List<String>> protocolHeaders = CastUtils.cast((Map<?, ?>) message.get(Message.PROTOCOL_HEADERS));

    if (protocolHeaders == null)
    {
        protocolHeaders = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
        message.put(Message.PROTOCOL_HEADERS, protocolHeaders);
    }

    protocolHeaders.put(HEADER_NAME, Collections.singletonList(value));
}
}

希望这有帮助,如果有人发现我的调试错误,请分享这个问题确实证明很有趣。

xxls0lw8

xxls0lw82#

所以@DDV的答案是正确的,我只想补充两件事:

  • 我在3.2.5版本中看到了同样的问题,但是PhaseInterceptor版本做了一些修改,所以@DDV的答案中的代码并没有完全翻译。
  • 我在WS-Addressing中遇到了这个问题,所以我希望这个答案能帮助人们寻找WS-Addressing
class MessageIdResetInterceptor extends AbstractPhaseInterceptor[SoapMessage](Phase.SETUP_ENDING) {
  override def handleMessage(message: SoapMessage): Unit = {
    val addressing = message
      .getContextualProperty("javax.xml.ws.addressing.context.outbound")
      .asInstanceOf[AddressingProperties]

    addressing.setMessageID(null)
    addressing.setAction(null)
  }
}
f8rj6qna

f8rj6qna3#

丹尼尔在他的回答中很好地描述了这个问题。但是,与此同时,SoapPreProtocolOutInterceptor中的代码发生了变化。这个问题不应该再存在。

Map<String, List<String>> tempReqHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            Map<String, List<String>> reqHeaders
                    = CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
            if (reqHeaders != null) {
                tempReqHeaders.putAll(reqHeaders);
            }
            if (!tempReqHeaders.containsKey(SoapBindingConstants.SOAP_ACTION)) {
                tempReqHeaders.put(SoapBindingConstants.SOAP_ACTION, Collections.singletonList(action));
            }
            message.put(Message.PROTOCOL_HEADERS, tempReqHeaders);

相关问题