我正在尝试使用新的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头。因此,如果你返回一个足够小的请求,它往往会被完全加载,因为它是在初始缓冲读取中。但是,当加载更大的内容网址时,内容会被截断。对于HttpClient来说,它约为20k,对于HttpWebRequest来说,对我来说大约是8k。
使用TcpClient没有任何缓冲问题。使用它时,我会以读取的大小读取内容,加上最近的缓冲区大小重叠,但这包括HTTP标头。使用TcpClient对我来说并不是一个真正的选择,因为我们必须处理SSL,重定向,Auth,Chunked内容等。此时我会考虑实现一个完整的自定义HTTP客户端,只是为了缓冲。
答案 0 :(得分:4)
实现您需要做的事情的最佳方式如下:
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);
}
}
}
但有几点要注意:
那里没有错误处理,因此您可能希望将它全部包装在try / catch或其他内容中,以确保您掌握任何连接错误,超时,未解决的IP解析等。
由于您处理原始流,然后HTTP标头也在那里,因此您需要考虑它们。
理论上,你可以在主套接字读取之前放置一个循环,继续抓取数据,直到你自己在一行中得到一个空白\ n它会告诉你标题结束的位置,然后你就可以抓住你实际的数据数量,但由于我不知道你说话的服务器,我把这一点留下来了: - )
如果您将整个代码复制/粘贴到VS中的新控制台项目中,它可以按原样运行,因此您可以单步执行。
据我所知,HTTP客户端没有使用户可以使用它的原始流,即便如此,如果因为它被分配为流连接,那么你不太可能对它的数量有很大的控制权,我我们之前已经考虑过并放弃了。
我已经多次使用过这段代码了,在类似的情况下它对我来说效果很好,事实上我有一台显示器,可以使用它从我的WiFi适配器获取统计数据,这样我就能看到谁在连接。
有任何问题,请随时在我这里打电话,或者在twitter上ping我,我的句柄是@shawty_ds(以防你丢失了)
辣妹
答案 1 :(得分:1)
我可能错了,但我觉得你很困惑:当你把请求发送到服务器时,它会通过网络向你发送完整的答案。然后它由框架在某处缓冲,您可以使用流访问它。 如果您不希望远程服务器向您发送完整答案,则可以使用http标头指定所需的字节范围。例如,请参阅HTTP Status: 206 Partial Content and Range Requests。
答案 2 :(得分:0)
这是我的设置。我不知道你为什么看到缓冲响应。它可能与主持人有关吗?
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.diagnostics>
<sources>
<source name="System.Net">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
</switches>
<sharedListeners>
<add name="System.Net"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="network.log"
/>
</sharedListeners>
<trace autoflush="true"/>
</system.diagnostics>
结果日志
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
答案 3 :(得分:0)
我认为/希望这可能会有所帮助。
How can I perform a GET request without downloading the content?
正如我所怀疑的那样,不管.NET看到什么,底层都会比你想要的更多。
<强>更新强>
虽然HttpWebRequest.AddRange(-256)
将获得前256个字节,但似乎只适用于IIS上的静态文件。
设置Range
标题(不要与If-Range
混淆。)
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2
服务器使用Accept-Ranges标头通告它支持范围请求。
对于Rick的问题,这可能是好的还是坏的,这取决于他是否需要阅读静态内容。我的猜测是,这不是他想要的。
替代方案可能是在ReceiveBufferSize
上公开的ServicePoint
设置WebRequest
。