在内容 100% 完成之前从 HttpResponseMessage 读取标头

Posted

技术标签:

【中文标题】在内容 100% 完成之前从 HttpResponseMessage 读取标头【英文标题】:Read headers from HttpResponseMessage before Content is 100% complete 【发布时间】:2013-03-12 17:30:00 【问题描述】:
    如何在整个响应流回之前访问响应标头? 如何在流到达时读取它? 对于接收 http 响应的这种精细控制,HttpClient 是我的最佳选择吗?

这是一个可以说明我的问题的片段:

using (var response = await _httpClient.SendAsync(request,
  HttpCompletionOption.ResponseHeadersRead))

   var streamTask = response.Content.ReadAsStreamAsync();
   //how do I check if headers portion has completed? 
   //Does HttpCompletionOption.ResponseHeadersRead guarantee that?
   //pseudocode
   while (!(all headers have been received)) 
     //maybe await a Delay here to let Headers get fully populated
   access_headers_without_causing_entire_response_to_be_received

   //how do I access the response, without causing an await until contents downloaded?
   //pseudocode
   while (stremTask.Resul.?) //i.e. while something is still streaming
     //? what goes here? a chunk-read into a buffer? or line-by-line since it's http?
   ...

编辑为我澄清另一个灰色区域:我发现的任何参考都有某种阻塞语句,这将导致等待内容到达。 我阅读的引用通常访问 streamTask.Result 或 Content 上的方法或属性,我不知道哪些引用是可以的,因为 streamTask 正在进行,哪些将导致等待任务完成。

【问题讨论】:

我写了一个答案,但后来意识到它有点缺乏研究和懒惰。相反,我有一个后续问题,您所说的阻塞语句是什么意思?所有 HttpClient 操作都是异步的,不应该有任何东西阻止您读取单独任务的标头和内容流,从而防止它们相互阻塞。 @Snixtor,我的问题可能是基于一个不正确的假设,即如果我明确地等待或访问 stremTask.Result,我将读取整个内容。最终,我一直在寻找 A) 读取标题,B) 读取流的管道,我将使用伪代码编辑我的问题,以说明我想象中应该发生的事情。 你是对的,这是一个不正确的假设。 streamTask.Result 将阻塞直到Stream 可用,但它不要求整个流内容已经传输。从技术上讲,在调用 streamTask.Result 后可能有零个内容字节可用。 关于"read the stream as it come",这是默认操作。除非您特别努力不以这种方式进行操作,否则从内容流中读取将通过网络获取字节到达时 【参考方案1】:

根据我自己的测试,在您开始阅读内容流之前,内容不会被传输,您正确地认为调用Task.Result 是一个阻塞调用,但它的本质是一个同步点。 但是,它不会阻塞预缓冲整个内容,它只会阻塞直到内容开始来自服务器。

因此,无限流不会阻塞无限长的时间。因此,尝试异步获取流可能被认为是矫枉过正,尤其是在您的标头处理操作相对较短的情况下。但是,如果您愿意,您始终可以在处理另一个任务的内容流时处理标头。像这样的东西可以做到这一点。

static void Main(string[] args)

    var url = "http://somesite.com/bigdownloadfile.zip";
    var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Get, url);

    var getTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    Task contentDownloadTask = null;

    var continuation = getTask.ContinueWith((t) =>
    
        contentDownloadTask = Task.Run(() =>
        
            var resultStream = t.Result.Content.ReadAsStreamAsync().Result;
            resultStream.CopyTo(File.Create("output.dat"));
        );

        Console.WriteLine("Got 0 headers", t.Result.Headers.Count());
        Console.WriteLine("Blocking after fetching headers, press any key to continue...");
        Console.ReadKey(true);
    );

    continuation.Wait();
    contentDownloadTask.Wait();
    Console.WriteLine("Finished downloading 0 bytes", new FileInfo("output.dat").Length);

    Console.WriteLine("Finished, press any key to exit");
    Console.ReadKey(true);

请注意,无需检查标头部分是否完整,您已使用 HttpCompletionOption.ResponseHeadersRead 选项明确指定。在检索到标头之前,SendAsync 任务将不会继续。

【讨论】:

如何以块的形式读取流 - 缓冲区或字符串/行? HttpClient 是对 http 下载进行如此精细控制的最合适的类吗? 要分块阅读,你可以使用Stream.Read - msdn.microsoft.com/en-us/library/system.io.stream.read.aspx - 虽然你需要一个相当特殊的情况来证明它的合理性(与CopyTo相比,它可能有点笨拙和缓慢。如果您想逐行阅读,请将流包装在 StreamReader - msdn.microsoft.com/en-us/library/system.io.streamreader.aspx 至于HttpClient,一旦您获得了响应内容流,它就不太可能了。它管理请求和响应、标头、一些错误处理等。与直接访问响应流相比,您不会获得更多的灵活性,至少在托管代码中不会。【参考方案2】:

使用 await/async 关键字的结果更具可读性:

var url = "http://somesite.com/bigdownloadfile.zip";

using (var httpClient = new HttpClient())
using (var httpRequest = new HttpRequestMessage(HttpMethod.Get, url ))
using(HttpResponseMessage response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead))
using (Stream stream = await response.Content.ReadAsStreamAsync())

    //Access to the Stream object as it comes, buffer it or do whatever you need
    

【讨论】:

以上是关于在内容 100% 完成之前从 HttpResponseMessage 读取标头的主要内容,如果未能解决你的问题,请参考以下文章

如何让阅读完成率达到100%?有姿势——自媒体有坑,得绕②

如果我检查“内容长度”标题,它是不是 100% 准确?

html 100%-(一行)

从进度条和alert的出现顺序来了解浏览器 UI 渲染 & JS进程

如何从 Windows 10 上的单个进程在辅助 GPU 上使用 100% 的 VRAM?

http协议