也许这似乎是一个奇怪的问题,但我遇到了以下情况:
我尝试向服务发出发布请求,并添加我选择的发布数据,从请求中创建一个 Stream 并使用 StreamWriter 在其上写入正文。
但是,在我实际执行请求(使用 GetResponse)之前,甚至在我写入流对象之前,我都会收到一个“无法连接异常”
var stream = request.GetRequestStream();
Run Code Online (Sandbox Code Playgroud)
经过一番调查,我意识到request.GetRequestStream() 实际上是在尝试连接。我的问题是与服务器的网络连接(防火墙问题)。
但我的问题是为什么 HttpWebRequest.GetRequestStream() 尝试连接???
我的简单想法是,在创建请求时,没有连接到请求的服务器。
我发现了一些相关的问题,比如像这样
但它似乎并没有完全回答我的问题。
请问有什么解释吗?
PS:关于如何避免这种“早期”连接效应的任何建议将不胜感激。
.NET I/O API 通常在流上操作,流是允许开发人员读取和写入有序数据序列的 API。通过将读取和写入写入通用 API,它使通用库能够在流上进行操作以执行强大的操作:压缩、加密、编码等(顺便说一句,以类似方式处理不同类型的 I/O 已有很长的历史,最著名的是在 UNIX 中)一切都是文件。)
尽管在许多不同类型的流中读取和写入数据的工作方式非常相似,但打开流很难通用。考虑一下用于打开文件、发出 HTTP 请求和执行数据库查询的截然不同的 API。
因此,.NET 的Stream类没有通用Open()方法,因为不同类型的流之间使流进入打开状态的方式有很大不同。相反,流 API 期望获得一个已经打开的流,其中“打开”意味着它已准备好写入和/或读取。
因此,在 .NET 中有一个典型的 I/O 模式:
现在考虑一下上面的模式如何与 HTTP 请求对齐,该请求具有以下步骤:
(我在上面的步骤中忽略了很多现实世界的复杂性,例如 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)
但老实说,上面的方法似乎有点矫枉过正。如果我处于你的立场,我会看看为什么我要尝试推迟打开流,并看看是否有其他方法来解决这个问题。