在HttpResponseMessage的内容完全完成之前读取头信息

14
  1. 在整个响应流返回之前,我如何访问响应头?
  2. 我如何在数据流到达时读取数据流?
  3. HttpClient是我接收HTTP响应的最佳选择吗?

以下是一段可能说明我的问题的代码片段:

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
@Snixtor,我的问题可能基于错误的假设,即如果我明确等待或访问stremTask.Result,我将导致整个内容被读取。最终,我正在寻找A)读取标头的管道,B)按流程读取流我将使用伪代码编辑我的问题,以说明我所想象的应该发生的事情。 - G. Stoynev
你是正确的,这是一个错误的假设。streamTask.Result会阻塞直到Stream可用,但它并不要求整个流内容已经传输完成。在调用streamTask.Result之后,技术上可能没有任何内容字节可用。 - Snixtor
关于“按流读取”,这是默认操作。除非您特别努力不按照这种方式操作,否则从内容流中读取将会在网络上传输字节时立即获取。 - Snixtor
2个回答

7
基于我的测试,只有在您开始阅读内容流时才会传输内容,并且您是正确的,调用 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下载的最合适的类? - G. Stoynev
1
要分块读取,您可以使用 Stream.Read - http://msdn.microsoft.com/en-us/library/system.io.stream.read.aspx - 尽管您需要一个相当特殊的情况来证明它(与 CopyTo 相比,它可能有点笨拙和慢)。如果您想逐行读取,请将流包装在 StreamReader 中 - http://msdn.microsoft.com/en-us/library/system.io.streamreader.aspx。 - Snixtor
1
关于 HttpClient,一旦你获取了响应内容流,它就有点不在画面中了。它管理请求和响应、头部、一些错误处理等等。至少在托管代码中,你不会比直接访问响应流获得更多的灵活性。 - Snixtor

6
使用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
}    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接