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

时间:2017-01-02 11:27:18

标签: c# httpwebrequest

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

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

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

var stream = request.GetRequestStream();

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

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

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

我发现了一些相关问题,例如this

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

请问有什么解释吗?

PS:非常感谢任何关于如何避免这种“早期”连接效应的建议。

1 个答案:

答案 0 :(得分:1)

.NET I / O API通常在streams上运行,这是允许开发人员读取和写入有序数据序列的API。通过读取和写入通用API,它使通用库可以在流上运行以完成强大的功能:压缩,加密,编码等。(顺便说一句,类似地对待各种I / O的历史由来已久,在UNIX中最为著名everything is a file。)

尽管在许多不同种类的流中读取和写入数据的方式非常相似,但打开流很难使它通用。想想您用来打开文件,进行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.e。现在阅读回复
  • f。最后,关闭连接。

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

好吧,现在让您沉迷于.NET团队,他们试图构建HTTP客户端API,记得记住将非通用部分(“获取开放流”)与通用部分分开:读取和/或写入,然后关闭流。

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

但是,如果要发送POST请求(或PUT或其他类似方法),则必须将数据上传到服务器。与只有几个KB的HTTP标头不同,您在POST中上传的数据可能非常庞大。如果您要上传10GB的文件,则不希望在上传到服务器的过程中将其停放在RAM中。同时,这会损害客户的表现。相反,您需要一种获取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. 
} 

但是,老实说,上述方法似乎过大了。如果我穿上你的鞋子,我会看看为什么我试图推迟流的开放,看看是否还有另一种方法可以解决这个问题。