【问题标题】:Targeting net461, HttpClient throws ex when GET request has content, but works with netstandard2.0以 net461 为目标,当 GET 请求有内容时,HttpClient 会抛出 ex,但适用于 netstandard2.0
【发布时间】:2018-04-30 13:26:18
【问题描述】:

我需要使用第 3 方网络 API。
在这个特定的端点上,我需要使用内容主体 (json) 发出 GET 请求。

var jsonPayload = JsonConvert.SerializeObject(myObject);

var request = new HttpRequestMessage(HttpMethod.Get, endpoint)
{
    Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
};

var client = new HttpClient();
var response = await client.SendAsync(request); //<-- explodes with net461,
//but works with netstandard2.0
var responseContent = await response.Content.ReadAsStringAsync();

我将该代码定位到 netstandard2.0 并且它有效。
但是我现在需要在我以net461 为目标的项目中使用它并抛出异常说“Cannot send a content-body with this verb-type

我知道将内容设置为 GET 请求并不常见,但我无法使用该 api。

  1. 为什么HttpClient.Send(request) 在我定位net461 时失败,但在我定位netstandard2.0 时运行良好?
  2. 当我定位 net461 时,我有哪些选择?

更新

一种复制方式。

ConsoleApp.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework> <!-- toggle this to net461 -->
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="System.Net.Http" />
  </ItemGroup>

</Project>

程序.cs

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Text;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var jsonPayload = JsonConvert.SerializeObject(new { });
            var request = new HttpRequestMessage(HttpMethod.Get, "http://www.stackoverflow.com")
            {
                Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
            };
            var client = new HttpClient();
            var response = client.SendAsync(request).Result;
            var responseContent = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine(responseContent);
        }
    }
}

dotnet --version 显示2.1.104

【问题讨论】:

  • “将内容设置为 GET 请求是不常见的”...如果您要问如何在正确执行的平台上重现错误行为,我想您会不走运。使用 GET 发送请求正文可能不会破坏某些实现,但会通过一些中间硬件传递该请求,并且请求很有可能会崩溃。我会放弃这作为一种策略并坚持标准化行为而不是 RFC/标准破坏行为。实际上,您是在说“我喜欢这个错误……请提供更多错误”。
  • 您能否再次运行这两个命令并在任何工具(如 fiddler)中捕获网络跟踪,并查看两个请求负载之间的区别。
  • @xxbbcc 有一些非常流行的产品使用带有正文的 GET 请求来设计他们的 api。一个例子是弹性搜索。以下是此类 api 的示例:elastic.co/guide/en/elasticsearch/reference/current/…
  • @Evk 好的 - 我称之为意外(即使 RFC 允许)。
  • @Evk - 但至少该 API 承认 GET+body 存在问题并允许 POST 作为替代方案。任何只提供 GET+body 的人都处于一个可能存在互操作问题的阴暗世界中。

标签: c# .net httpclient .net-standard


【解决方案1】:

你没有提到你在哪个 .net 版本上运行它(因为你可以针对网络标准,但你不能在网络标准上运行)。所以我假设当它工作时,你在 .NET Core 上运行它,而当它不起作用时,你按照你所说的在完整的 .NET 4.6.1 上运行。

那只是因为它在 .NET Core 和完整的 .NET Framework 中的实现方式不同,如issue 中所述。这两种实现都没有错,因为 RFC 说:

GET 请求消息中的有效负载没有定义的语义;
在 GET 请求上发送有效负载正文可能会导致一些现有的
拒绝请求的实现。

因此,在 GET 中包含正文(虽然几乎从来都不是一个好的做法)本身并不与 RFC 相矛盾。一种实现(较旧)旨在拒绝它,但另一种(较新)旨在允许它,仅此而已。

至于您的第二个问题“当我以 net461 为目标时,我有哪些选择”。我认为没有简单的解决方法,因为这是HttpWebRequest 的行为,而不是HttpClient 的行为。几乎所有发出网络请求的东西都在内部使用HttpWebRequest

虽然有一些丑陋的黑客使用反射。您的示例可以这样修改(仅在完整的 .NET 上运行时有效):

static void Main(string[] args) {     
    // UGLY HACK!       
    var verbType = typeof(HttpWebRequest).Assembly.GetType("System.Net.KnownHttpVerb");
    var getMethodField = verbType.GetField("Get", BindingFlags.Static | BindingFlags.NonPublic);
    var getMethod = getMethodField.GetValue(null);
    verbType.GetField("ContentBodyNotAllowed", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(getMethod, false);
    var jsonPayload = JsonConvert.SerializeObject(new { });
    var request = new HttpRequestMessage(HttpMethod.Get, "http://www.stackoverflow.com")
    {
        Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
    };
    var client = new HttpClient();
    var response = client.SendAsync(request).Result;
    var responseContent = response.Content.ReadAsStringAsync().Result;
    Console.WriteLine(responseContent);            
}

基本上我们使用反射将KnownHttpVerb.Get的名为ContentBodyNotAllowed的内部字段修改为false。使用风险自负。

【讨论】:

  • 这个问题是 RFC 的下一行,上面没有引用“对 GET 请求的响应是可缓存的”。与“有效负载 ... 没有定义的语义”一起意味着缓存允许,根据 RFC 的大多数解读,缓存基于请求标头,并且应该忽略制定缓存决策的主体。
  • @Damien_The_Unbeliever 的观点是 RFC 没有明确禁止它,并不是说这是个好主意或没有问题。出于这个原因,我想不能认为这是一个错误。
  • 那个 github 问题很有趣,并回答了第 1 个问题。在那个问题中,“karelz”说“有简单的解决方法,而且场景非常极端”。你知道解决方法是什么吗?
  • @SiimHaas 我认为他的意思是不要对正文使用 GET 请求,而是使用 POST。我不认为他意味着任何特定的解决方法来允许 GET 与身体。但是,我稍微扩展了答案以显示带有反射的丑陋黑客来做到这一点。
  • @Evk 我虽然有一个简单的解决方法。这确实很丑。谢谢你的回答。
猜你喜欢
  • 2019-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-13
  • 1970-01-01
  • 2012-10-19
相关资源
最近更新 更多