为什么WCF在遇到302响应时无法调用SOAP服务?

时间:2013-06-17 16:28:27

标签: c# wcf soap saas

我编写了一个应用程序,它首先要进行WCF调用登录。我使用服务引用生成了客户端代码。它适用于将其服务本地安装到其网络的客户端。然而,还有一个saas环境,这些相同的服务由公司权力控制。在saas环境中,我被告知登录失败。调查使用Fiddler,我发现登录服务调用返回HTML,特别是列出.asmx中所有可用方法的网页。

saas环境有一个小怪癖可能会导致问题,但我不知道如何验证这是问题,如果问题是如何解决它。怪癖是服务器重定向(302)呼叫。

客户端代码:

    client.Endpoint.Address = new EndpointAddress("http://" + settings.MyURL + "/myProduct/webservices/webapi.asmx");
    client.DoLogin(username, password);

在重定向之前发送到服务器的原始数据包括s:Envelope XML标记。发送到重定向的服务器时,请注意缺少的s:Envelope XML标记:

    GET https://www.myurl.com/myProduct/webservices/webapi.asmx HTTP/1.1
    Content-Type: text/xml; charset=utf-8
    VsDebuggerCausalityData: uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQxHTAupeAkWz2p2l3jFASiUPHh+L/1xNpTd0YqI2o+wACQAA
    SOAPAction: "http://Interfaces.myProduct.myCompany.com/DoLogin"
Accept-Encoding: gzip, deflate
    Host: www.gotimeforce2.com
    Connection: Keep-Alive

如何让这个愚蠢的事情发挥作用?

编辑:值得注意的是,我使用的是WCF / svcutil.exe / service-reference,而不是旧的ASMX / wsdl.exe / web-reference。否则,对于本主题的未来读者,Raj建议的wsdl解决方案将是一个很好的解决方案。如果你看到这个问题并使用wsdl技术,请参阅Raj的优秀答案。

编辑2:在对WCF和302进行大量研究后,听起来他们只是不能很好地协同工作,似乎也没有一种简单的方法来提供WCF api自定义处理这种情况的代码。因为我无法控制服务器,所以我已将其吸引并重新生成我的api作为网络参考并使用Raj的解决方案。

编辑3:更新了标题以更好地反映解决方案,现在可以了解问题的原因。原标题:为什么WCF不包括s:重定向的信封?

1 个答案:

答案 0 :(得分:6)

好的,所以我对此进行了一些挖掘并尝试在我身边复制这个问题。我能够复制问题并找到解决方案。但是我不确定这在你的情况下会有多好,因为它依赖于与管理负载均衡器的服务器团队的接口。以下是调查结果。

查看http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html您会在HTTP状态代码302和303的说明中注意到以下附录。

302发现

  Note: RFC 1945 and RFC 2068 specify that the client is not allowed
  to change the method on the redirected request.  However, most
  existing user agent implementations treat 302 as if it were a 303
  response, performing a GET on the Location field-value regardless
  of the original request method. The status codes 303 and 307 have
  been added for servers that wish to make unambiguously clear which
  kind of reaction is expected of the client.

303见其他

  Note: Many pre-HTTP/1.1 user agents do not understand the 303
  status. When interoperability with such clients is a concern, the
  302 status code may be used instead, since most user agents react
  to a 302 response as described here for 303.

进一步查看http://en.wikipedia.org/wiki/List_of_HTTP_status_codes您会注意到HTTP状态代码302,303和307的以下说明。

302发现: 这是与标准相矛盾的行业惯例的一个例子。 HTTP / 1.0规范(RFC 1945)要求客户端执行临时重定向(原始描述短语是"暂时移动"),但是流行的浏览器实现302具有303的功能。 。因此,HTTP / 1.1添加了状态代码303和307以区分这两种行为。但是,某些Web应用程序和框架使用302状态代码,就像它是303一样。

303见其他(自HTTP / 1.1起): 可以使用GET方法在另一个URI下找到对请求的响应。 当收到POST(或PUT / DELETE)时,应该假设服务器已收到数据,并且应该使用单独的GET消息发出重定向。 以下是常规客户端/服务器交互的基本流程

307临时重定向(自HTTP / 1.1起): 在这种情况下,请求应该使用另一个URI重复;但是,未来的请求仍应使用原始URI。 与历史上实现302的方式相反,重新发出原始请求时不允许更改请求方法。例如,应使用另一个POST请求重复POST请求。

因此,根据这一点,我们能够解释WCF调用的行为,该调用在302重定向上发送没有s:Envelope的GET请求。毫无疑问,这将在客户端失败。

解决此问题的最简单方法是让服务器在响应中返回307临时重定向而不是302 Found状态代码。您需要在服务器团队的帮助下管理负载均衡器上的重定向规则。我在本地对此进行了测试,即使使用307临时重定向,使用服务引用的客户端代码也可以无缝地执行调用。

事实上,您可以使用我已上传到Github Here 的解决方案对此进行全面测试。我已对此进行了更新,以说明使用服务引用而不是使用wsdl生成的代理类来使用asmx服务。

但是,如果在您的环境中从302 Found到307临时重定向的更改不可行,那么我建议使用解决方案1 ​​ (这不应该有问题它是响应中的302或307状态代码或使用我的原始答案,它可以通过根据配置文件中的设置直接访问正确URL的服务来解决此问题。希望这有帮助!

解决方案1 ​​

如果您无法访问生产中的配置文件,或者您只是想要在配置文件中使用多个URL,则可以使用以下方法。链接到包含样本解决方案的Github repo Click Here

基本上,如果您注意到wsdl.exe自动生成的文件,您会注意到服务代理类派生自System.Web.Services.Protocols.SoapHttpClientProtocol。此类具有可以覆盖的受保护方法System.Net.WebRequest GetWebRequest(Uri uri)。在这里,您可以添加一个方法来检查302临时重定向是否是HttpWebRequest.GetResponse()方法的结果。如果是这样,您可以将网址设置为在响应的位置标头中返回的新网址,如下所示。

this.Url = new Uri(uri, response.Headers["Location"]).ToString();

所以创建一个名为SoapHttpClientProtocolWithRedirect的类,如下所示。

public class SoapHttpClientProtocolWithRedirect :
    System.Web.Services.Protocols.SoapHttpClientProtocol
{
    protected override System.Net.WebRequest GetWebRequest(Uri uri)
    {
        if (!_redirectFixed)
        {
            FixRedirect(new Uri(this.Url));
            _redirectFixed = true;

            return base.GetWebRequest(new Uri(this.Url));
        }

        return base.GetWebRequest(uri);
    }

    private bool _redirectFixed = false;
    private void FixRedirect(Uri uri)
    {
        var request = (HttpWebRequest)WebRequest.Create(uri);
        request.CookieContainer = new CookieContainer();
        request.AllowAutoRedirect = false;
        var response = (HttpWebResponse)request.GetResponse();

        switch (response.StatusCode)
        {
            case HttpStatusCode.Redirect:
            case HttpStatusCode.TemporaryRedirect:
            case HttpStatusCode.MovedPermanently:
                this.Url = new Uri(uri, response.Headers["Location"]).ToString();
                break;
        }
    }
}

现在出现的部分说明了使用wsdl.exe而不是服务引用手动生成的代理类的优势。在手动创建的代理类中。从

修改类声明
public partial class WebApiProxy : System.Web.Services.Protocols.SoapHttpClientProtocol

public partial class WebApiProxy : SoapHttpClientProtocolWithRedirect

现在按如下方式调用DoLogin方法。

var apiClient = new WebApiProxy(GetServiceUrl());
//TODO: Add any required headers etc.
apiClient.DoLogin(username,password);

您会注意到SoapHttpClientProtocolWithRedirect类中的代码可以顺利处理302重定向。

另一个优点是,通过这样做,您不必担心其他开发人员会刷新服务引用并丢失您对代理类所做的更改,因为您已手动生成它。希望这会有所帮助。

原始答案

为什么不在配置文件中包含生产/本地服务的完整网址?这样,您就可以在适当的位置使用相应的网址发起呼叫。

另外,我会避免在任何用于生产的代码中使用服务引用。在没有服务引用的情况下使用asmx服务的一种方法是使用wsdl.exe工具生成WebApiProxy.cs文件。现在,您可以在项目中包含WebApiProxy.cs文件并实例化,如下所示。

var apiClient = new WebApiProxy(GetServiceUrl());
//TODO: Add any required headers etc.
apiClient.DoLogin(username,password);

这是GetServiceUrl()方法。使用配置存储库进一步解耦并提高可测试性。

private string GetServiceUrl()
    {
        try
        {
            return
            _configurationRepository.AppSettings[
                _configurationRepository.AppSettings["WebApiInstanceToUse"]];
        }
        catch (NullReferenceException ex)
        {
            //TODO: Log error
            return string.Empty;
        }
    }

然后您的配置文件可以在该部分中包含以下信息。

<add key="StagingWebApiInstance" value="http://mystagingserver/myProduct/webservices/webapi.asmx "/>
<add key="ProductionWebApiInstance" value="https://www.myurl.com/myProduct/webservices/webapi.asmx"/>
<!-- Identify which WebApi.asmx instance to Use-->
<add key="WebApiInstanceToUse" value="ProductionWebApiInstance"/>

另外,我会避免使用+重载连接字符串。在执行一次时,它并没有因为太多的性能影响而遇到,但如果在整个代码中有许多这样的连接,那么与使用StringBuilder相比,它会导致执行时间的巨大差异。检查http://msdn.microsoft.com/en-us/library/ms228504.aspx以获取有关使用StringBuilder提高性能的原因的更多信息。