65.9K
CodeProject 正在变化。 阅读更多。
Home

发布不使用 action 参数的 SOAP 1.2 Web 服务

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2013 年 8 月 6 日

CPOL

4分钟阅读

viewsIcon

40784

downloadIcon

280

本文档介绍如何发布一个不使用任何 action 参数的 SOAP 1.2 Web 服务。

引言

本文档介绍如何发布一个不使用任何 action 参数的 SOAP 1.2 Web 服务。

背景

几周前,有人要求我发布一个 Web 服务。这个 Web 服务的规范(WSDL)由我们的客户定义。他们已经编写了 Web 服务的客户端。

客户设计的客户端依赖于一个自制的中间件。这个中间件的特点是它不在 HTTP 头中发布被调用的 SOAP action。虽然这不是强制性的 (www.w3.org/TR/2003/REC-soap12-part2-20030624/#ietf-action),但对于 .NET 来说,路由消息是必要的。

此外,这个中间件不使用 SOAP 1.2 中可用的 WS-Addressing 扩展。

事实上,可以使用一个默认的 action(在 ServiceContract 中使用 `[System.ServiceModel.OperationContractAttribute(Action="*", ReplyAction="*")]`),但这样一来,所有魔力都会消失,并且需要在 Web 服务代码中进行大量操作。

这就是我寻找另一个解决方案的原因:代理请求。

示例

为了说明这个例子,我们假设我们要发布一个简单的计算器 Web 服务。操作包括加、减、乘。通过 soapUI 连接到 Web 服务,我们可以成功测试加法操作。


SOAP Action 和 action 参数

首先,重要的是要弄清楚 action 的定义位置。存在两个位置:

  • 在 SOAP 头内部;
  • 在 HTTP 头中的 content-type 中。

SOAP 头内的 SOAP Action

默认情况下,C# 主要依赖 SOAP 头中的信息。顺便说一句,SOAP 消息的内容如下:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
           xmlns:cal="http://www.distorsion.fr/calculator">
   <soap:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
   <wsa:Action>http://www.distorsion.fr/calculator/Add</wsa:Action></soap:Header>
   <soap:Body>
      <cal:Add>
         <!--Optional:-->
         <cal:p_A>1</cal:p_A>
         <!--Optional:-->
         <cal:p_B>1</cal:p_B>
      </cal:Add>
   </soap:Body>
</soap:Envelope>

即使 action 没有在 HTTP 头中定义,路由也能正常工作,Web 服务也能正确响应。要禁用 WsHttpBinding 上的 WS-Addressing,您需要定义一个 CustomBinding。

WSHttpBinding l_Binding = new WSHttpBinding(SecurityMode.None);
l_Binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
CustomBinding l_CustomBinding = new CustomBinding(l_Binding);
MessageEncodingBindingElement l_EncodingElement = 
     l_CustomBinding.Elements.Find<MessageEncodingBindingElement>();
l_EncodingElement.MessageVersion = MessageVersion.Soap12;

如果您想验证 WS-Addressing 是否已启用,您应该在 WSDL 中找到 UsingAddressing。

<wsdl:definitions name="CalculatorService" 
         targetNamespace="http://www.distorsion.fr/calculator">
   <wsp:Policy wsu:Id="CalculatorService_policy">
      <wsp:ExactlyOne>
         <wsp:All>
            <wsaw:UsingAddressing/>
         </wsp:All>
      </wsp:ExactlyOne>
   </wsp:Policy>
... 

HTTP 头中 content-type 中的 action 参数

无论 action 是否在 SOAP 头中定义,SOAP 都允许客户端在 HTTP 头中定义 action。

POST https://:44305/calculator.svc HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/soap+xml;charset=UTF-8;action="http://www.distorsion.fr/calculator/Add"
Content-Length: 337
Host: localhost:44305
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

Action 参数在 Content-type 头中定义。建议如果同时定义了 action,它们应该相同。从协议的角度来看,在禁用 WS-Addressing 时,在 HTTP 头中使用 Action 参数不是强制性的。但实际上,如果 WS-Addressing 被禁用,.NET Framework 将依赖于 action 参数。

Action 参数

仔细查看 HTTP 交换,我们可以看到 soapUI 发送了以下头信息:

POST https://:44305/calculator.svc HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/soap+xml;charset=UTF-8;action="http://www.distorsion.fr/calculator/Add"
Content-Length: 337
Host: localhost:44305
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

content-type 头包含完全限定的 SOAP action。在 soapUI 中,通过单击请求并在属性中设置 Skip SOAP Action 为 true,action 将不再发送在 HTTP Content-type 头中,但 Web 服务将抛出一个 SOAP Fault,其子代码为 ActionNotSupported 和原因:

接收端无法处理消息 "" 的 Action,因为 EndpointDispatcher 处的 ContractFilter 不匹配。这可能是因为 contract 不匹配(发送方和接收方之间的 Actions 不匹配)或发送方和接收方之间的绑定/安全不匹配。检查发送方和接收方是否具有相同的 contract 和相同的绑定(包括安全要求,例如 Message、Transport、None)。

HttpProxy

为了避免此错误,第一个解决方案是使用默认 action(在 ServiceContract 中使用 `[System.ServiceModel.OperationContractAttribute(Action="*", ReplyAction="*")]`)。但是,这样做会丢失 SOAP Web 服务的简洁性,并且您需要以非常不方便的方式操作流。这就是我选择使用代理的原因。

代理将接收请求,并根据主体(body)将 action 参数注入到头信息中。假设 action 将是 Body 标签的子标签。

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
         xmlns:cal="http://www.distorsion.fr/calculator">
   <soap:Header/>
   <soap:Body>
      <cal:Add>
         <!--Optional:-->
         <cal:p_A>4</cal:p_A>
         <!--Optional:-->
         <cal:p_B>2</cal:p_B>
      </cal:Add>
   </soap:Body>
</soap:Envelope>

在上面的示例中,Add 标签是 Body 标签的子标签。

为了能够监听请求,HttpProxy 将需要监听一个特定的端口,该端口与 Web 服务的端口不同。这是棘手的部分:必须修改 wsdl 文件,使端点指向代理监听端口而不是 Web 服务端点。

使用代码

要使用 HttpProxy,您首先需要注意操作契约(operation contracts)。为了简化代理,它们的定义不包含对服务名称的引用。例如,在示例中,如果没有显式定义,关于 Add 操作的操作契约的 action 将是:http://www.distorsion.fr/calculator/CalculatorService/Add。通过显式定义操作契约 action 为仅 http://www.distorsion.fr/calculator/CalculatorService/Add,路由会简化:action 是目标命名空间和 action 本地名称 Add 的组合。

此外,在这个非常基础的版本中,HttpProxy 仅在服务端点与代理在同一台机器上时才有效。

完成这些简单的验证后,您将可以使用以下参数实例化 HttpProxy:

  • p_ListenPort:HttpProxy 将监听的端口;
  • p_Encoding:编码很重要,因为我们的代理需要检查请求的内容以完成 Http content-type 头;
  • p_ProxifiedWSName:这是服务的域名。例如,我可能已将我的 Web 服务发布在 ws.distorsion.fr 上。此值可能与目标命名空间不同;
  • p_ProxifiedWSPort:被代理 Web 服务监听的端口。
m_HttpProxy = new HttpProxy(44306, Encoding.UTF8, "localhost", 44305);
m_HttpProxy.Start();

历史

  • 2013/08/06:本文首次提交。
© . All rights reserved.