为什么 HttpWebRequest.GetRequestStream() 尝试连接

cno*_*nom 6 c# httpwebrequest

也许这似乎是一个奇怪的问题,但我遇到了以下情况:

我尝试向服务发出发布请求,并添加我选择的发布数据,从请求中创建一个 Stream 并使用 StreamWriter 在其上写入正文。

但是,在我实际执行请求(使用 GetResponse)之前,甚至在我写入流对象之前,我都会收到一个“无法连接异常”

var stream = request.GetRequestStream();
Run Code Online (Sandbox Code Playgroud)

经过一番调查,我意识到request.GetRequestStream() 实际上是在尝试连接。我的问题是与服务器的网络连接(防火墙问题)。

但我的问题是为什么 HttpWebRequest.GetRequestStream() 尝试连接???

我的简单想法是,在创建请求时,没有连接到请求的服务器。

我发现了一些相关的问题,比如像这样

但它似乎并没有完全回答我的问题。

请问有什么解释吗?

PS:关于如何避免这种“早期”连接效应的任何建议将不胜感激。

Jus*_*ant 2

.NET I/O API 通常在流上操作,流是允许开发人员读取和写入有序数据序列的 API。通过将读取和写入写入通用 API,它使通用库能够在流上进行操作以执行强大的操作:压缩、加密、编码等(顺便说一句,以类似方式处理不同类型的 I/O 已有很长的历史,最著名的是在 UNIX 中)一切都是文件。)

尽管在许多不同类型的流中读取和写入数据的工作方式非常相似,但打开流很难通用。考虑一下用于打开文件、发出 HTTP 请求和执行数据库查询的截然不同的 API。

因此,.NET 的Stream类没有通用Open()方法,因为不同类型的流之间使流进入打开状态的方式有很大不同。相反,流 API 期望获得一个已经打开的流,其中“打开”意味着它已准备好写入和/或读取。

因此,在 .NET 中有一个典型的 I/O 模式:

  1. 编写一些特定于资源的代码来打开流。这些 API 通常返回一个开放流。
  2. 将该开放流移交给从中读取和/或写入的通用 API。
  3. 完成后关闭流(也是通用的)。

现在考虑一下上面的模式如何与 HTTP 请求对齐,该请求具有以下步骤:

  • A。在 DNS 中查找服务器的 IP 地址
  • b. 与服务器建立 TCP 连接
  • C。将 URL 和请求标头发送到服务器
  • d. 如果是 POST(或 PUT 或其他发送请求正文的方法),则上传请求正文。如果是 GET,则这是一个空操作。
  • e. 现在阅读回复
  • F。最后,关闭连接。

(我在上面的步骤中忽略了很多现实世界的复杂性,例如 SSL、保持活动连接、缓存响应等,但基本工作流程足以准确地回答您的问题。)

好的,现在将自己置于 .NET 团队的立场上,尝试构建 HTTP 客户端 API,记住将非通用部分(“获取开放流”)与通用部分分开:读取和/或写入,然后关闭溪流。

如果您的 API 只需处理 GET 请求,那么您可能会在执行返回响应流的相同 API 时建立连接。这正是HttpWebRequest.GetResponse所做的。

但如果您发送 POST 请求(或 PUT 或其他类似方法),则必须将数据上传到服务器。与只有几 KB 的 HTTP 标头不同,您在 POST 中上传的数据可能会很大。如果您要上传 10GB 文件,您不希望在上传到服务器期间将其停放在 RAM 中。同时这会影响客户的表现。相反,您需要一种方法来获取 a Stream,这样您只需在发送到服务器之前将小块数据加载到 RAM 中。请记住,它Stream没有Open()方法,因此您的 API 必须提供开放流。

现在您有了第一个问题的答案:HttpWebRequest.GetRequestStream必须建立网络连接,因为如果没有建立网络连接,那么流将被关闭,您将无法写入它。

现在讨论第二个问题:如何延迟连接?我假设您的意思是连接应该在第一次写入请求流时发生。实现此目的的一种方法是编写一个继承自该类的类,该类仅尽可能晚地Stream调用,然后将所有方法委托给底层请求流。GetRequestStream像这样作为起点:

using System.Net;
using System.Threading.Tasks;
using System.Threading;

class DelayConnectRequestStream : Stream 
{
  private HttpWebRequest _req;
  private Stream _stream = null;

  public DelayConnectRequestStream (HttpWebRequest req) 
  {
    _req = req;
  }

  public void Write (byte[] buffer, int offset, int count) 
  {
    if (_stream == null) 
    {
      _stream = req.GetRequestStream();
    }
    return _stream.Write(buffer, offset, count);
  }

  public override WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
  {
    if (_stream == null) 
    {
      // TODO: figure out if/how to make this async 
      _stream = req.GetRequestStream();
    }
    return _stream.WriteAsync(buffer, offset, count, cancellationToken);
  }

  // repeat the pattern above for all needed methods on Stream 
  // you may need to decide by trial and error which properties and methods
  // must require an open stream. Some properties/methods you can probably just return
  // without opening the stream, e.g. CanRead which will always be false so no need to 
  // create a stream before returning from that getter. 

  // Also, the code sample above is not thread safe. For 
  // thread safety, you could use Lazy<T> or roll your own locking. 
} 
Run Code Online (Sandbox Code Playgroud)

但老实说,上面的方法似乎有点矫枉过正。如果我处于你的立场,我会看看为什么我要尝试推迟打开流,并看看是否有其他方法来解决这个问题。