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

标签 c# wcf soap saas

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

saas环境有一个小怪癖,可能是这里的问题,但我不知道如何验证这是问题,也不知道如果是问题如何解决。奇怪的是服务器重定向(302)调用。

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

在重定向之前发送到服务器的原始数据包括 s:Envelope XML 标记。请注意发送到重定向服务器时缺少的 s:Envelope XML 标记:GET https://www.myurl.com/myProduct/webservices/webapi.asmx HTTP/1.1
内容类型:文本/xml;字符集=utf-8
VsDebuggerCausalityData:uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQxHTAupeAkWz2p2l3jFASiUPHh+L/1xNpTd0YqI2o+wACQAA
SOAPAction:“http://Interfaces.myProduct.myCompany.com/DoLogin
接受编码:gzip、deflate
主持人:www.gotimeforce2.com
连接:保持事件

我如何让这个愚蠢的事情起作用?

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

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

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

最佳答案

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

看着 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) 要求客户端执行临时重定向(原始描述短语是“临时移动”),但是流行的浏览器使用 303 See Other 的功能实现了 302。因此,HTTP/1.1 添加了状态码 303 和 307 来区分这两种行为。 但是,某些 Web 应用程序和框架将 302 状态代码用作 303。

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

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

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

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

事实上,您可以使用我上传到 Github 的解决方案来测试这一切。 Here .我已经更新了它来说明使用服务引用而不是 wsdl 生成的代理类来使用 asmx 服务。

但是,如果从 302 Found 更改为 307 Temporary Redirect 在您的环境中不可行,那么我建议使用 解决方案 1 (无论是响应中的 302 还是 307 状态代码,这都不应该有问题)或使用我的 原答案这将通过根据配置文件中的设置直接访问正确 URL 上的服务来解决此问题。希望这可以帮助!

解决方案 1

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

基本上,如果您注意到 wsdl.exe 自动生成的文件,您会注意到服务代理类派生自 System.Web.Services.Protocols.SoapHttpClientProtocol .这个类有一个 protected 方法 System.Net.WebRequest GetWebRequest(Uri uri)你可以覆盖。在这里,您可以添加一个方法来检查 302 临时重定向是否是 HttpWebRequest.GetResponse() 的结果。方法。如果是这样,您可以将Url 设置为 中返回的新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);

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

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

原答案

为什么不在配置文件中包含生产/本地服务的整个 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 可以提高性能的更多信息。

关于c# - 为什么 WCF 在遇到 302 响应时无法调用 SOAP 服务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17152385/

相关文章:

c# - xPath 通过 XPathNavigator 对象而不是通过表达式求值来查找祖先

c# - 如何使用异或运算正确加密jpeg文件

.net - 使用 WCF 进行动态编程

c# - 在 WCF 或 WebAPI 方法 (IIS) 中返回 "Task<int>"而不是 "int"的好处

node.js - Node Js SOAP 模块 - 超时选项

java - 运行时动态 Web 服务调用

c# - WPF:如何与非事件窗口交互?

c# - 如何在重定向到 ASP .NET MVC 中的另一个操作时保存模型状态?

c# - WCF NetTcpBinding - 来自 Microsoft 教程 - 超时

.net - 使用 Android 调用 .NET Webservice