如何使用System.Net.HttpClient检索部分响应

我正在尝试使用新的HttpClient类(在.NET 4.5中)从服务器检索部分响应以检查内容。 我需要将检索到的数据大小限制为HTTP请求中内容的前几个字节,以限制带宽使用。

我一直无法做到这一点。 我尝试使用GetAsync(url,HttpCompletionOption.ResponseHeadersRead),然后使用Content.ReadAsStream()尝试只读取标题,然后在一个小块中读取响应流。 我还尝试了GetStreamAsync(),然后用一小块(1000字节)读取内容流。

在这两种情况下,HttpClient似乎都在拉动和缓冲整个HTTP响应,而不仅仅是从流中读取请求的字节数。

最初我使用Fiddler监控数据,但意识到Fiddler实际上可能导致整个内容被代理。 我切换到使用System.Net跟踪(显示):

ConnectStream#6044116::ConnectStream(Buffered 16712 bytes.) 

这是完整的大小,而不仅仅是读取的1000个字节。 我还对Wireshark进行了双重检查,以确认是否确实通过电线拉出了全部内容。 对于更大的内容(如110k链接),我会在TCP / IP流被截断之前获得大约20k的数据。

我试图读取数据的两种方式:

 response = await client.GetAsync(site.Url, HttpCompletionOption.ResponseHeadersRead); var stream = await response.Content.ReadAsStreamAsync(); var buffer = new byte[1000]; var count = await stream.ReadAsync(buffer, 0, buffer.Length); response.Close() // close ASAP result.LastResponse = Encoding.UTF8.GetString(buffer); 

和:

 var stream = await client.GetStreamAsync(site.Url); var buffer = new byte[1000]; var count = await stream.ReadAsync(buffer, 0, buffer.Length); result.LastResponse = Encoding.UTF8.GetString(buffer); 

它们都产生几乎相同的.NET跟踪,其中包括缓冲读取。

是否有可能让HttpClient实际只读取一小部分Http Repsonse,而不是整个响应以便不使用全带宽? IOW有没有办法使用HttpClient或HttpWebRequest禁用HTTP连接上的任何缓冲?

更新:经过一些更广泛的测试后,它看起来像HttpClient和HttpWebRequest缓冲前几个TCP / IP帧 – 可能是为了确保捕获HTTP头。 因此,如果你返回一个足够小的请求,它往往会被完全加载,因为它是在初始缓冲读取。 但是当加载更大的内容url时,内容会被截断。 对于HttpClient来说,它约为20k,对于HttpWebRequest来说,对我来说大约是8k。

使用TcpClient没有任何缓冲问题。 使用它时,我会以读取的大小读取内容,加上最近的缓冲区大小重叠,但这包括HTTP标头。 使用TcpClient对我来说不是一个真正的选择,因为我们必须处理SSL,重定向,Auth,Chunked内容等。此时我会考虑实现一个完整的自定义HTTP客户端,只是为了转向缓冲。

实现您需要做的事情的最佳方式如下:

 using System; using System.Net.Sockets; namespace tcpclienttest { class Program { static byte[] GetData(string server, string pageName, int byteCount, out int actualByteCountRecieved) { const int port = 80; TcpClient client = new TcpClient(server, port); string fullRequest = "GET " + pageName + " HTTP/1.1\nHost: " + server + "\n\n"; byte[] outputData = System.Text.Encoding.ASCII.GetBytes(fullRequest); NetworkStream stream = client.GetStream(); stream.Write(outputData, 0, outputData.Length); byte[] inputData = new Byte[byteCount]; actualByteCountRecieved = stream.Read(inputData, 0, byteCount); // If you want the data as a string, set the function return type to a string // return 'responseData' rather than 'inputData' // and uncomment the next 2 lines //string responseData = String.Empty; //responseData = System.Text.Encoding.ASCII.GetString(inputData, 0, actualByteCountRecieved); stream.Close(); client.Close(); return inputData; } static void Main(string[] args) { int actualCount; const int requestedCount = 1024; const string server = "myserver.mydomain.com"; // NOTE: NO Http:// or https:// bit, just domain or IP const string page = "/folder/page.ext"; byte[] myPartialPage = GetData(server, page, requestedCount, out actualCount); } } } 

但要注意几点:

那里没有error handling,所以您可能希望将它全部包装在try / catch或其他内容中,以确保您掌握任何连接错误,超时,未解决的IP解析等。

因为您处理原始流,然后HTTP标头也在那里,所以您需要考虑它们。

理论上,你可以在主套接字读取之前放置一个循环,继续抓取数据,直到你自己在一行中得到一个空白\ n,它会告诉你标题结束的位置,那么你可以抓住你的实际数量数据,但由于我不知道你说话的服务器我也离开了那一点:-)

如果您将整个代码复制/粘贴到VS中的新控制台项目中,它可以按原样运行,因此您可以单步执行它。

据我所知,HTTP客户端并没有让它的原始流可供用户使用,即便如此,如果因为它被分配为流连接,那么你很可能无法控制它的数量,我已经研究过它之前和放弃。

我已经多次使用过这段代码了,在类似的情况下它对我来说效果很好,实际上我有一台显示器,可以使用它从我的WiFi适配器获取统计数据,这样我就可以看到谁在连接。

有任何问题,请随时在这里打我,或者在twitter上ping我,我的句柄是@shawty_ds(以防万一你丢了)

美女

我可能错了,但我觉得你很困惑:当你把请求发送到服务器时,它会通过网络向你发送完整的答案。 然后它由框架在某处缓冲,您可以使用流访问它。 如果您不希望远程服务器向您发送完整答案,则可以使用http标头指定所需的字节范围。 例如,请参阅HTTP状态:206部分内容和范围请求 。

这是我的设置。 我不知道你为什么看到缓冲响应。 它可能与主持人有关吗?

  class Program { static void Main(string[] args) { var host = String.Format("http://{0}:8080/", Environment.MachineName); var server = CreateServer(host); TestBigDownload(host); Console.WriteLine("Done"); server.Dispose(); } private static void TestBigDownload(string host) { var httpclient = new HttpClient() { BaseAddress = new Uri(host) }; var stream = httpclient.GetStreamAsync("bigresource").Result; var bytes = new byte[10000]; var bytesread = stream.Read(bytes, 0, 1000); } private static IDisposable CreateServer(string host) { var server = WebApp.Start(host, app => { var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); app.UseWebApi(config); }); return server; } } [Route("bigresource")] public class BigResourceController : ApiController { public HttpResponseMessage Get() { var sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.Append(i.ToString()); sb.Append(","); } var content = new StringContent(sb.ToString()); var response = new HttpResponseMessage() { Content = content }; return response; } } 

记录配置

                  

结果日志

 System.Net Information: 0 : [15028] Current OS installation type is 'Client'. System.Net Verbose: 0 : [15028] HttpWebRequest#40383808::HttpWebRequest(http://oak:8080/bigresource#-236952546) System.Net Information: 0 : [15028] RAS supported: True System.Net Verbose: 0 : [15028] Exiting HttpWebRequest#40383808::HttpWebRequest() System.Net Verbose: 0 : [15028] HttpWebRequest#40383808::HttpWebRequest(uri: 'http://oak:8080/bigresource', connectionGroupName: '17480744') System.Net Verbose: 0 : [15028] Exiting HttpWebRequest#40383808::HttpWebRequest() System.Net Verbose: 0 : [25748] HttpWebRequest#40383808::BeginGetResponse() System.Net Verbose: 0 : [25748] ServicePoint#45653674::ServicePoint(127.0.0.1:8888) System.Net Information: 0 : [25748] Associating HttpWebRequest#40383808 with ServicePoint#45653674 System.Net Information: 0 : [25748] Associating Connection#41149443 with HttpWebRequest#40383808 System.Net Verbose: 0 : [25748] Exiting HttpWebRequest#40383808::BeginGetResponse() -> ContextAwareResult#39785641 System.Net Information: 0 : [3264] Connection#41149443 - Created connection from 127.0.0.1:10411 to 127.0.0.1:8888. System.Net Information: 0 : [3264] Associating HttpWebRequest#40383808 with ConnectStream#39086322 System.Net Information: 0 : [3264] HttpWebRequest#40383808 - Request: GET http://oak:8080/bigresource HTTP/1.1 System.Net Information: 0 : [3264] ConnectStream#39086322 - Sending headers { Host: oak:8080 Proxy-Connection: Keep-Alive }. System.Net Information: 0 : [21384] Connection#41149443 - Received status line: Version=1.1, StatusCode=200, StatusDescription=OK. System.Net Information: 0 : [21384] Connection#41149443 - Received headers { Content-Length: 48890 Content-Type: text/plain; charset=utf-8 Date: Thu, 09 Jan 2014 16:41:59 GMT Server: Microsoft-HTTPAPI/2.0 }. System.Net Information: 0 : [21384] ConnectStream#56140151::ConnectStream(Buffered 48890 bytes.) System.Net Information: 0 : [21384] Associating HttpWebRequest#40383808 with ConnectStream#56140151 System.Net Information: 0 : [21384] Associating HttpWebRequest#40383808 with HttpWebResponse#1997173 System.Net Verbose: 0 : [21384] HttpWebRequest#40383808::EndGetResponse() System.Net Verbose: 0 : [21384] Exiting HttpWebRequest#40383808::EndGetResponse() -> HttpWebResponse#1997173 System.Net Verbose: 0 : [21384] HttpWebResponse#1997173::GetResponseStream() System.Net Information: 0 : [21384] ContentLength=48890 System.Net Verbose: 0 : [21384] Exiting HttpWebResponse#1997173::GetResponseStream() -> ConnectStream#56140151 System.Net Verbose: 0 : [15028] ConnectStream#56140151::Read() System.Net Verbose: 0 : [15028] Data from ConnectStream#56140151::Read System.Net Verbose: 0 : [15028] 00000000 : 30 2C 31 2C 32 2C 33 2C-34 2C 35 2C 36 2C 37 2C : 0,1,2,3,4,5,6,7, System.Net Verbose: 0 : [15028] 00000010 : 38 2C 39 2C 31 30 2C 31-31 2C 31 32 2C 31 33 2C : 8,9,10,11,12,13, System.Net Verbose: 0 : [15028] 00000020 : 31 34 2C 31 35 2C 31 36-2C 31 37 2C 31 38 2C 31 : 14,15,16,17,18,1 System.Net Verbose: 0 : [15028] 00000030 : 39 2C 32 30 2C 32 31 2C-32 32 2C 32 33 2C 32 34 : 9,20,21,22,23,24 System.Net Verbose: 0 : [15028] 00000040 : 2C 32 35 2C 32 36 2C 32-37 2C 32 38 2C 32 39 2C : ,25,26,27,28,29, System.Net Verbose: 0 : [15028] 00000050 : 33 30 2C 33 31 2C 33 32-2C 33 33 2C 33 34 2C 33 : 30,31,32,33,34,3 System.Net Verbose: 0 : [15028] 00000060 : 35 2C 33 36 2C 33 37 2C-33 38 2C 33 39 2C 34 30 : 5,36,37,38,39,40 System.Net Verbose: 0 : [15028] 00000070 : 2C 34 31 2C 34 32 2C 34-33 2C 34 34 2C 34 35 2C : ,41,42,43,44,45, System.Net Verbose: 0 : [15028] 00000080 : 34 36 2C 34 37 2C 34 38-2C 34 39 2C 35 30 2C 35 : 46,47,48,49,50,5 System.Net Verbose: 0 : [15028] 00000090 : 31 2C 35 32 2C 35 33 2C-35 34 2C 35 35 2C 35 36 : 1,52,53,54,55,56 System.Net Verbose: 0 : [15028] 000000A0 : 2C 35 37 2C 35 38 2C 35-39 2C 36 30 2C 36 31 2C : ,57,58,59,60,61, System.Net Verbose: 0 : [15028] 000000B0 : 36 32 2C 36 33 2C 36 34-2C 36 35 2C 36 36 2C 36 : 62,63,64,65,66,6 System.Net Verbose: 0 : [15028] 000000C0 : 37 2C 36 38 2C 36 39 2C-37 30 2C 37 31 2C 37 32 : 7,68,69,70,71,72 System.Net Verbose: 0 : [15028] 000000D0 : 2C 37 33 2C 37 34 2C 37-35 2C 37 36 2C 37 37 2C : ,73,74,75,76,77, System.Net Verbose: 0 : [15028] 000000E0 : 37 38 2C 37 39 2C 38 30-2C 38 31 2C 38 32 2C 38 : 78,79,80,81,82,8 System.Net Verbose: 0 : [15028] 000000F0 : 33 2C 38 34 2C 38 35 2C-38 36 2C 38 37 2C 38 38 : 3,84,85,86,87,88 System.Net Verbose: 0 : [15028] 00000100 : 2C 38 39 2C 39 30 2C 39-31 2C 39 32 2C 39 33 2C : ,89,90,91,92,93, System.Net Verbose: 0 : [15028] 00000110 : 39 34 2C 39 35 2C 39 36-2C 39 37 2C 39 38 2C 39 : 94,95,96,97,98,9 System.Net Verbose: 0 : [15028] 00000120 : 39 2C 31 30 30 2C 31 30-31 2C 31 30 32 2C 31 30 : 9,100,101,102,10 System.Net Verbose: 0 : [15028] 00000130 : 33 2C 31 30 34 2C 31 30-35 2C 31 30 36 2C 31 30 : 3,104,105,106,10 System.Net Verbose: 0 : [15028] 00000140 : 37 2C 31 30 38 2C 31 30-39 2C 31 31 30 2C 31 31 : 7,108,109,110,11 System.Net Verbose: 0 : [15028] 00000150 : 31 2C 31 31 32 2C 31 31-33 2C 31 31 34 2C 31 31 : 1,112,113,114,11 System.Net Verbose: 0 : [15028] 00000160 : 35 2C 31 31 36 2C 31 31-37 2C 31 31 38 2C 31 31 : 5,116,117,118,11 System.Net Verbose: 0 : [15028] 00000170 : 39 2C 31 32 30 2C 31 32-31 2C 31 32 32 2C 31 32 : 9,120,121,122,12 System.Net Verbose: 0 : [15028] 00000180 : 33 2C 31 32 34 2C 31 32-35 2C 31 32 36 2C 31 32 : 3,124,125,126,12 System.Net Verbose: 0 : [15028] 00000190 : 37 2C 31 32 38 2C 31 32-39 2C 31 33 30 2C 31 33 : 7,128,129,130,13 System.Net Verbose: 0 : [15028] 000001A0 : 31 2C 31 33 32 2C 31 33-33 2C 31 33 34 2C 31 33 : 1,132,133,134,13 System.Net Verbose: 0 : [15028] 000001B0 : 35 2C 31 33 36 2C 31 33-37 2C 31 33 38 2C 31 33 : 5,136,137,138,13 System.Net Verbose: 0 : [15028] 000001C0 : 39 2C 31 34 30 2C 31 34-31 2C 31 34 32 2C 31 34 : 9,140,141,142,14 System.Net Verbose: 0 : [15028] 000001D0 : 33 2C 31 34 34 2C 31 34-35 2C 31 34 36 2C 31 34 : 3,144,145,146,14 System.Net Verbose: 0 : [15028] 000001E0 : 37 2C 31 34 38 2C 31 34-39 2C 31 35 30 2C 31 35 : 7,148,149,150,15 System.Net Verbose: 0 : [15028] 000001F0 : 31 2C 31 35 32 2C 31 35-33 2C 31 35 34 2C 31 35 : 1,152,153,154,15 System.Net Verbose: 0 : [15028] 00000200 : 35 2C 31 35 36 2C 31 35-37 2C 31 35 38 2C 31 35 : 5,156,157,158,15 System.Net Verbose: 0 : [15028] 00000210 : 39 2C 31 36 30 2C 31 36-31 2C 31 36 32 2C 31 36 : 9,160,161,162,16 System.Net Verbose: 0 : [15028] 00000220 : 33 2C 31 36 34 2C 31 36-35 2C 31 36 36 2C 31 36 : 3,164,165,166,16 System.Net Verbose: 0 : [15028] 00000230 : 37 2C 31 36 38 2C 31 36-39 2C 31 37 30 2C 31 37 : 7,168,169,170,17 System.Net Verbose: 0 : [15028] 00000240 : 31 2C 31 37 32 2C 31 37-33 2C 31 37 34 2C 31 37 : 1,172,173,174,17 System.Net Verbose: 0 : [15028] 00000250 : 35 2C 31 37 36 2C 31 37-37 2C 31 37 38 2C 31 37 : 5,176,177,178,17 System.Net Verbose: 0 : [15028] 00000260 : 39 2C 31 38 30 2C 31 38-31 2C 31 38 32 2C 31 38 : 9,180,181,182,18 System.Net Verbose: 0 : [15028] 00000270 : 33 2C 31 38 34 2C 31 38-35 2C 31 38 36 2C 31 38 : 3,184,185,186,18 System.Net Verbose: 0 : [15028] 00000280 : 37 2C 31 38 38 2C 31 38-39 2C 31 39 30 2C 31 39 : 7,188,189,190,19 System.Net Verbose: 0 : [15028] 00000290 : 31 2C 31 39 32 2C 31 39-33 2C 31 39 34 2C 31 39 : 1,192,193,194,19 System.Net Verbose: 0 : [15028] 000002A0 : 35 2C 31 39 36 2C 31 39-37 2C 31 39 38 2C 31 39 : 5,196,197,198,19 System.Net Verbose: 0 : [15028] 000002B0 : 39 2C 32 30 30 2C 32 30-31 2C 32 30 32 2C 32 30 : 9,200,201,202,20 System.Net Verbose: 0 : [15028] 000002C0 : 33 2C 32 30 34 2C 32 30-35 2C 32 30 36 2C 32 30 : 3,204,205,206,20 System.Net Verbose: 0 : [15028] 000002D0 : 37 2C 32 30 38 2C 32 30-39 2C 32 31 30 2C 32 31 : 7,208,209,210,21 System.Net Verbose: 0 : [15028] 000002E0 : 31 2C 32 31 32 2C 32 31-33 2C 32 31 34 2C 32 31 : 1,212,213,214,21 System.Net Verbose: 0 : [15028] 000002F0 : 35 2C 32 31 36 2C 32 31-37 2C 32 31 38 2C 32 31 : 5,216,217,218,21 System.Net Verbose: 0 : [15028] 00000300 : 39 2C 32 32 30 2C 32 32-31 2C 32 32 32 2C 32 32 : 9,220,221,222,22 System.Net Verbose: 0 : [15028] 00000310 : 33 2C 32 32 34 2C 32 32-35 2C 32 32 36 2C 32 32 : 3,224,225,226,22 System.Net Verbose: 0 : [15028] 00000320 : 37 2C 32 32 38 2C 32 32-39 2C 32 33 30 2C 32 33 : 7,228,229,230,23 System.Net Verbose: 0 : [15028] 00000330 : 31 2C 32 33 32 2C 32 33-33 2C 32 33 34 2C 32 33 : 1,232,233,234,23 System.Net Verbose: 0 : [15028] 00000340 : 35 2C 32 33 36 2C 32 33-37 2C 32 33 38 2C 32 33 : 5,236,237,238,23 System.Net Verbose: 0 : [15028] 00000350 : 39 2C 32 34 30 2C 32 34-31 2C 32 34 32 2C 32 34 : 9,240,241,242,24 System.Net Verbose: 0 : [15028] 00000360 : 33 2C 32 34 34 2C 32 34-35 2C 32 34 36 2C 32 34 : 3,244,245,246,24 System.Net Verbose: 0 : [15028] 00000370 : 37 2C 32 34 38 2C 32 34-39 2C 32 35 30 2C 32 35 : 7,248,249,250,25 System.Net Verbose: 0 : [15028] 00000380 : 31 2C 32 35 32 2C 32 35-33 2C 32 35 34 2C 32 35 : 1,252,253,254,25 System.Net Verbose: 0 : [15028] 00000390 : 35 2C 32 35 36 2C 32 35-37 2C 32 35 38 2C 32 35 : 5,256,257,258,25 System.Net Verbose: 0 : [15028] 000003A0 : 39 2C 32 36 30 2C 32 36-31 2C 32 36 32 2C 32 36 : 9,260,261,262,26 System.Net Verbose: 0 : [15028] 000003B0 : 33 2C 32 36 34 2C 32 36-35 2C 32 36 36 2C 32 36 : 3,264,265,266,26 System.Net Verbose: 0 : [15028] 000003C0 : 37 2C 32 36 38 2C 32 36-39 2C 32 37 30 2C 32 37 : 7,268,269,270,27 System.Net Verbose: 0 : [15028] 000003D0 : 31 2C 32 37 32 2C 32 37-33 2C 32 37 34 2C 32 37 : 1,272,273,274,27 System.Net Verbose: 0 : [15028] 000003E0 : 35 2C 32 37 36 2C 32 37- : 5,276,27 System.Net Verbose: 0 : [15028] Exiting ConnectStream#56140151::Read() -> Int32#1000 

我想/希望这可能会有所帮助。

如何在不下载内容的情况下执行GET请求?

正如我所怀疑的那样,尽管.NET看到了,但潜在的层将比你想要的更多。

更新

虽然HttpWebRequest.AddRange(-256)将获得前256个字节,但似乎只适用于IIS上的静态文件。

它设置Range标头(不要与If-Range混淆)。

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2

服务器使用Accept-Ranges标头通告它支持范围请求。

对于Rick的问题,这可能是好的还是坏的,这取决于他是否需要阅读静态内容。 我的猜测是,这不是他想要的。

另一种方法是在WebRequest上公开的ServicePoint上设置ReceiveBufferSize