【问题标题】:Why would WCF fail to call a SOAP service when a 302 response is encountered?为什么遇到 302 响应时 WCF 会调用 SOAP 服务失败?
【发布时间】:2013-06-13 16:39:31
【问题描述】:

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

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

客户端代码:

client.Endpoint.Address = new EndpointAddress("http://" + settings.MyURL + "/myProduct/webservices/webapi.asmx"); client.DoLogin(用户名,密码);

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

获取https://www.myurl.com/myProduct/webservices/webapi.asmxHTTP/1.1 内容类型:文本/xml;字符集=utf-8 VsDebuggerCausalityData:uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQxHTAupeAkWz2p2l3jFASiUPHh+L/1xNpTd0YqI2o+wACQAA SOAPAction:“http://Interfaces.myProduct.myCompany.com/DoLogin” 接受编码:gzip,放气 主办方:www.gotimeforce2.com 连接:保持活动

我如何让这个愚蠢的东西发挥作用?

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

Edit2:在对 WCF 和 302 进行了大量研究之后,听起来它们不能很好地结合在一起,似乎也没有一种简单的方法可以自定义 WCF api处理这种情况的代码。由于我无法控制服务器,因此我将其吸收并重新生成了我的 api 作为网络参考,并且正在使用 Raj 的解决方案。

Edit3: 更新了标题以更好地反映解决方案,现在问题的原因已了解。原标题:为什么 WCF 不会在重定向中包含 s:Envelope?

【问题讨论】:

  • 进一步调查表明,许多可能遇到来自服务器的 302 的代码实现错误地在第二次提交中强制执行 GET,而不是原始的 HTTP 动词——在我的例子中是 POST。 GET 不发送参数。这就是“为什么”,但我仍然需要知道“如何”解决它。

标签: c# wcf soap saas


【解决方案1】:

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

查看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 See Other 的功能。因此,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 Temporary Redirect 在您的环境中不可行,那么我建议使用 Solution 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() 方法的结果。如果是这样,您可以将 Url 设置为响应的 Location 标头中返回的新 Url,如下所示。

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 重定向。

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

原答案

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

另外,我会避免在任何用于生产的代码中使用服务引用。在没有服务引用的情况下使用 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 可以提高性能的信息。

【讨论】:

  • 可测试性注释是一个好点。对于我的目的,字符串连接可能不是什么大问题。 Visual Studio 2010 似乎调用 wsdl.exe 来生成服务引用。
  • 我用不同的解决方案更新了答案,顺便也解释了为什么使用 wsdl.exe 手动生成代理类是一种更灵活的方法。正如我在原始帖子中提到的那样,使用 + 重载进行一次连接并没有那么大的影响。我只是建议这样使用 StringBuilder 是一种很好的做法,因为公司环境中的开发人员倾向于复制他们在现有代码中看到的内容,并证明这是“公认的”这样做的方式,因为以前有人这样做过。
  • StringBuilder cmets:啊,明白了! wsdl.exe cmets:在对生成的代码进行进一步调查后,我使用的是 WCF 和 svcutil.exe,而不是 ASMX 和 wsdl.exe 的旧技术。除了我以前没有意识到的一条信息(我正在使用 WCF),你更新的解决方案会很好用。
  • 好吧,从我所看到的一切来看,WCF 和 302 只是不兼容。我已经放弃并重做我的 API 作为 Web 参考而不是服务参考。 叹息
  • @REW 抱歉花了一点时间,但我已经发布了我的答案的更新。请看一下。它可能会提供多种使用网络参考的替代方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-22
  • 2012-01-28
  • 1970-01-01
  • 2015-04-22
相关资源
最近更新 更多