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





0/5 (0投票)
本文档介绍如何发布一个不使用任何 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:本文首次提交。