自定义HTTP客户端实现指南

对于.NET程序员来说,像WebRequest、WebResponse或WebClient这样的类非常有用,但有时它们并不提供需要的所有功能。在这种情况下,可能别无选择,只能基于TCP套接字编写自己的HTTP客户端实现。本文就是一个如何实现的例子。读者应该了解HTTP协议的基础知识。

编写自己的HTTP客户端库的第一个原因是需要将大文件上传到服务器,并显示当前的上传速度。这看起来并不困难,但发现了标准类在发送数据之前会将所有输出数据加载到内存中,尽管WebRequest类为POST数据提供了输出流。HTTP协议并不复杂,为什么不直接在端口80上打开一个套接字并开始通信呢?

不想重写整个应用程序,只是想先做一些实验,以确保想法是可行的。需要创建一个抽象接口,这样可以随时返回到标准解决方案。这些是类:

HttpConnection - 表示对Web服务器的抽象连接,一次只能处理一个请求。

HttpSocketConnection - 基于TCP套接字的新客户端实现。

HttpWebRequestConnection - 使用标准WebRequest和WebResponse类的实现。

HttpMessage - 包含HTTP请求或响应的容器,包括头和数据流。

这些类是运行简单HTTP请求以及双向数据上传/下载所需的全部。还准备了一些代码,可以控制网络通信使用的带宽。这可以通过减慢读流来实现 - 首先是POST数据,其次是服务器响应,所以创建了一个流代理类,用于减慢给定流的读写操作。

作为给定流的代理,在每次读写操作之前调用事件。

流传输事件的事件参数。

使用基类的事件插入一些等待时间。

负责测量时间并计算传输的数据以保持所需的速度。

用于创建POST请求体的抽象类。

以标准多部分格式准备请求体,允许上传文件。

以标准URL格式准备请求体。

这个流从多个给定的流中读取数据,允许准备整个请求体而无需将所有必要的数据加载到内存中。

从System.Web.dll中提取的URL编码逻辑。这允许避免引用这个库,并将应用程序保持在.NETFramework Client Profile下。

使用代码

为准备了一个客户端/服务器示例解决方案,供测试和更好地理解库。有一个ASP.NET网站允许上传图像,然后它执行一些图形操作,并将结果发送回浏览器。要调用服务,可以使用标准浏览器或WinForms客户端模拟浏览器行为。

// 选择连接 HttpConnection http; switch(httpMethod) { case 0: http = new HttpSocketConnection(); break; case 1: http = new HttpWebRequestConnection(); break; default: throw new NotSupportedException(); } // 准备请求 var url = "http://localhost:12345/Page.aspx"; var postBody = new HttpPostBodyBuilder.Multipart(); var fileStream = new FileStream(this.openFileDialog.FileName, FileMode.Open, FileAccess.Read); var advStream = new BandwidthControlledStream(fileStream); lock(this.bandWidthSync) { this.uploadSpeed = advStream.ReadSpeed; this.uploadSpeed.BytesPerSecond = 1024 * (int)this.upSpeedBox.Value; } postBody.AddData("imageFile", advStream, Path.GetFileName(this.openFileDialog.FileName), GetMimeType(this.openFileDialog.FileName)); var bodyStream = postBody.PrepareData(); bodyStream.Position = 0; var req = new HttpMessage.Request(bodyStream, "POST"); req.ContentLength = bodyStream.Length; req.ContentType = postBody.GetContentType(); req.Headers["Referer"] = url; // 发送请求 advStream.BeforeRead += (s, e) => this.NotifyState("Uploading...", e.Position, bodyStream.Length); var response = http.Send(url, req); // 获取响应 var readStream = new BandwidthControlledStream(response.Stream); lock(this.bandWidthSync) { this.downloadSpeed = readStream.ReadSpeed; this.downloadSpeed.BytesPerSecond = 1024*(int)this.downSpeedBox.Value; } readStream.BeforeRead += (s, e) => this.NotifyState("Downloading...", e.Position, response.ContentLength); this.convertedFile = ReadAll(readStream, (int) response.ContentLength); this.NotifyState("Done", 100, true);

如所见,代码片段并不短,但它做了很多工作:

  • 选择上传数据的方法(WebRequests/套接字)
  • 准备带有要上传文件的请求体(文件在发送过程中连续加载)
  • 准备限速流(也可以选择不使用它们,或在传输过程中改变速度)
  • 使用BeforeRead事件通知用户进度
  • 展示如何设置请求头
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485