【问题标题】:Tracking progress of HttpWebRequest and HttpWebResponse跟踪 HttpWebRequest 和 HttpWebResponse 的进度
【发布时间】:2014-01-08 10:47:08
【问题描述】:

我正在使用 Sharefile API,它发送 HTTP 请求并获得各自的答案。它们是通过 URL 制作的,并且始终使用相同的功能。在这里。

Private Function InvokeShareFileOperation(ByVal requestUrl As String) As JObject

    Dim request As HttpWebRequest = WebRequest.Create(requestUrl)
    Dim response As HttpWebResponse = request.GetResponse()

    Dim reader As StreamReader = New StreamReader(response.GetResponseStream())

    Dim json As String = reader.ReadToEnd()
    response.Close()
    Return JObject.Parse(json)

End Function

由于一些操作有点长,我需要在他们进行时以某种方式跟踪他们的进度,但不知道该怎么做。稍后我打算使用该进度并从中创建一个进度条。

(编辑) 顺便说一句,花费最多时间的是第二行代码(如下),即要跟踪的操作。

Dim response As HttpWebResponse = request.GetResponse()

【问题讨论】:

  • 您应该将HttpWebResponse and StreamReader` 放入Using 块中,特别是因为您经常使用它们。这可能是放缓的一部分。
  • 你觉得我的回答怎么样?
  • API 返回的 JSON 文件通常是多大的?如果它很小,例如小于 1k,那么它将在一次读取中从响应流中读取。这将使进度条变得毫无意义,因为只有一个测量值,并且不可能获得GetResponse() 的进度。在您对@srka 的回复中,内容长度似乎只有 50 个字节
  • 是的,我猜这也是不可能的。应该有办法以某种方式取得进展,但不幸的是,我认为没有
  • 我无法让它工作。无论如何,它会显示下载正文的进度,在你的情况下,它的完成时间不到第二次。

标签: json vb.net httprequest httpresponse getjson


【解决方案1】:

编辑:我认为您在这里无法以任何准确的方式衡量进度,因为大部分操作似乎依赖于处理请求的远程服务器。 GetResponse() 处理设置 DNS、连接、发送和等待远程服务器,而这一切都由您掌控。仅当返回内容长度标头时才可测量读取响应流。就我个人而言,我最初会将进度显示为 20%,GetResponse 返回时显示为 60%,然后如果您在下载前有内容长度,或者在阅读完响应流后一次性完成,则可以增量显示最后 40%。

作为网络请求,您可以先找出内容长度,然后使用缓冲区而不是 ReadToEnd() 读取响应流。这允许您在下载响应时计算进度并触发通知。

Dim request As HttpWebRequest = WebRequest.Create(requestUrl)

Using response As HttpWebResponse = request.GetResponse()
  Dim contentLength As Long = response.ContentLength
  Dim bytesReceived As Long
  Dim bufferLength as Integer = 8192
  Dim buffer(bufferLength) As Char
  Dim sb As New StringBuilder

  Using reader As StreamReader = New StreamReader(response.GetResponseStream())
    Do
      Dim bufferedCount As Integer = reader.Read(buffer, 0, bufferLength)
      sb.Append(buffer, 0, bufferedCount)
      bytesReceived += bufferedCount
      Console.WriteLine(bytesReceived / contentLength * 100 & "%")
    Loop While bytesReceived < contentLength
  End Using

  Return JObject.Parse(sb.ToString)
End Using

显然,您可以将 Console.WriteLine 替换为进度更新函数或调用 SignalR 集线器来更新网页,并且您可以试验缓冲区大小以查看最适合您的方法。

【讨论】:

  • 感谢您的回答。循环是一个无限循环,bytesReceived 总是
  • GetResponse() 应该只获取标题 - 实际内容是使用响应流下载的。在 GetResponse 返回之前,无法测量远程服务器处理请求的进度。获取 response.ContentLength 可能会强制进行完整下载 - 您可以尝试 response.GetResponseHeader("content-length") 吗
  • 如果您可以提供来自远程服务器的实际响应标头会很有帮助 - 可能使用 Fiddler 或通过浏览器发出请求并使用浏览器开发工具捕获它。如果响应是分块发送的,那么我认为没有设置内容长度标头。获取内容长度是这里的关键:)
  • 您也可以尝试在顶部的Using response 行之前插入request.Proxy = Nothing。我记得在较旧的 .net 版本上有很大的延迟,这解决了它。事实上,这可能是你的问题,我真的希望这是因为它很容易解决:)
【解决方案2】:

首先我们必须找出减速的​​原因。在调用GetResponse() 之前不会发送请求,因此服务器处理可能需要一些时间。下载也可能需要一些时间。如果响应很小(相对于连接速度),您将无法做太多事情(如果服务器是您的,您可以,但我们将专注于客户端),因为您无法从服务器获得进度。如果响应很大,并且您想跟踪下载,则只能在具有 Content-Length 标头的情况下进行。并且要仅获取标头,服务器必须支持HEAD 请求方法。所以这里是代码:

Imports System
Imports System.Net
Imports System.IO
Imports System.Text
Imports System.Threading
Imports Microsoft.VisualBasic

Public Class Form1

    Private Function InvokeShareFileOperation(ByVal requestUrl As String) As JObject
        HTTPWebRequest_GetResponse.Main(requestUrl)
        ProgressBar1.Value = 0
        Dim result As String
        Do
            Try
                ProgressBar1.Value = HTTPWebRequest_GetResponse.progress
            Catch ex As ArgumentOutOfRangeException
                ProgressBar1.Style = ProgressBarStyle.Marquee
            End Try
            If HTTPWebRequest_GetResponse.done = True Then
                result = HTTPWebRequest_GetResponse.response
                ProgressBar1.Style = ProgressBarStyle.Continuous
                ProgressBar1.Value=100
                Debug.WriteLine(result)
                Return JObject.Parse(result)
                Exit Do
            End If
        Loop
    End Function

End Class


Public Class RequestState
    ' This class stores the State of the request. 
    Private BUFFER_SIZE As Integer = 1024
    Public requestData As StringBuilder
    Public BufferRead() As Byte
    Public request As HttpWebRequest
    Public response As HttpWebResponse
    Public streamResponse As Stream

    Public Sub New()
        BufferRead = New Byte(BUFFER_SIZE) {}
        requestData = New StringBuilder("")
        request = Nothing
        streamResponse = Nothing
    End Sub 'New 
End Class 'RequestState


Class HTTPWebRequest_GetResponse

    Private BUFFER_SIZE As Integer = 1024
    Public Shared response As String
    Public Shared done As Boolean = False
    Public Shared length As Long = 1
    Public Shared progress As Integer
    Public Shared myHttpWebRequest As HttpWebRequest
    Public Shared myRequestState As New RequestState()

    Shared Sub Main(url As String)

        Try
            Dim headRequest As HttpWebRequest = WebRequest.Create(url)
            headRequest.Method = "HEAD"
            Dim headResponse As HttpWebResponse = headRequest.GetResponse
            length = headResponse.ContentLength
            Debug.WriteLine(length)
            headResponse.Close()
            ' Create a HttpWebrequest object to the desired URL.  
            myHttpWebRequest = WebRequest.Create(url)

            ' Create an instance of the RequestState and assign the previous myHttpWebRequest 
            ' object to its request field.   

            myRequestState.request = myHttpWebRequest
            'Dim myResponse As New HTTPWebRequest_GetResponse()

            ' Start the asynchronous request. 
            Dim result As IAsyncResult = CType(myHttpWebRequest.BeginGetResponse(New AsyncCallback(AddressOf RespCallback), myRequestState), IAsyncResult)

        Catch e As WebException
            Debug.WriteLine("Main Exception raised!")
            Debug.WriteLine("Message: " + e.Message)
            Debug.WriteLine("Status: " + e.Status)
        Catch e As Exception
            Debug.WriteLine("Main Exception raised!")
            Debug.WriteLine("Source : " + e.Source)
            Debug.WriteLine("Message : " + e.Message)
        End Try
    End Sub 'Main

    Private Shared Sub RespCallback(asynchronousResult As IAsyncResult)
        Debug.WriteLine("RespCallBack entered")
        Try
            ' State of request is asynchronous. 
            Dim myRequestState As RequestState = CType(asynchronousResult.AsyncState, RequestState)
            Dim myHttpWebRequest As HttpWebRequest = myRequestState.request
            myRequestState.response = CType(myHttpWebRequest.EndGetResponse(asynchronousResult), HttpWebResponse)

            ' Read the response into a Stream object. 
            Dim responseStream As Stream = myRequestState.response.GetResponseStream()
            myRequestState.streamResponse = responseStream

            ' Begin the Reading of the contents of the HTML page. 
            Dim asynchronousInputRead As IAsyncResult = responseStream.BeginRead(myRequestState.BufferRead, 0, 1024, New AsyncCallback(AddressOf ReadCallBack), myRequestState)
            Return
        Catch e As WebException
            Debug.WriteLine("RespCallback Exception raised!")
            Debug.WriteLine("Message: " + e.Message)
            Debug.WriteLine("Status: " + e.Status)
        Catch e As Exception
            Debug.WriteLine("RespCallback Exception raised!")
            Debug.WriteLine("Source : " + e.Source)
            Debug.WriteLine("Message : " + e.Message)
        End Try
    End Sub 'RespCallback

    Private Shared Sub ReadCallBack(asyncResult As IAsyncResult)
        Debug.WriteLine("ReadCallBack entered")
        Try

            Dim myRequestState As RequestState = CType(asyncResult.AsyncState, RequestState)
            Dim responseStream As Stream = myRequestState.streamResponse
            Dim read As Integer = responseStream.EndRead(asyncResult)
            ' Read the HTML page. 
            If read > 0 Then
                myRequestState.requestData.Append(Encoding.ASCII.GetString(myRequestState.BufferRead, 0, read))
                If length = -1 Or length = 0 Then
                    progress = -1
                Else
                    progress = myRequestState.BufferRead.Length * 100 / length
                    Debug.WriteLine(progress)
                End If
                Dim asynchronousResult As IAsyncResult = responseStream.BeginRead(myRequestState.BufferRead, 0, 1024, New AsyncCallback(AddressOf ReadCallBack), myRequestState)

            Else
                If myRequestState.BufferRead.Length > 1 Then
                    Dim fullResponse As String = myRequestState.requestData.ToString
                    response = fullResponse.Substring(0, fullResponse.IndexOf("</body>")).Substring(fullResponse.IndexOf(">", fullResponse.IndexOf("<body")) + 2) 'Returns only body
                    ' Release the HttpWebResponse resource.
                    myRequestState.response.Close()
                    done = True
                    Debug.WriteLine(done)
                End If

                responseStream.Close()
            End If

        Catch e As WebException
            Debug.WriteLine("ReadCallBack Exception raised!")
            Debug.WriteLine("Message: " + e.Message)
            Debug.WriteLine("Status: " + e.Status)
        Catch e As Exception
            Debug.WriteLine("ReadCallBack Exception raised!")
            Debug.WriteLine("Source : " + e.Source)
            Debug.WriteLine("Message : " + e.Message)
        End Try
    End Sub 'ReadCallBack 
End Class 'HttpWebRequest_BeginGetResponse

我从http://msdn.microsoft.com/en-us/library/debx8sh9(v=vs.110).aspx 获取代码并更改了它。

编辑:代码现在只返回正文并关闭响应。

EDIT2:正如@Geezer68 所说,它不是100% 准确,但可以向用户显示进度。

【讨论】:

  • 对不起@srka,您的解决方案不起作用,它会导致无限循环
  • @chiapa 它对我来说很好用。无限循环在哪里?
  • @chiapa 我发现设置 ProgressBar 值和编辑答案存在问题。也许这让你觉得有一个无限循环?
  • 再次无限循环:在“InvokeSharefileOperation”函数中,“ProgressBar1.Value = HTTPWebRequest_GetResponse.progress”行在一个值处停止。这样,它会保持循环并将 progressBar 值设置为始终相同。它永远不会退出“do”循环,因为“getResponse.done 永远不会”为真。你自己测试过吗?我问这个是因为缺少一些“进口”
  • 只是在想,如果被调用的 API 需要很长时间才能返回初始响应,那么发出 HEAD 和 GET 请求只会让事情花费更长的时间——甚至可能不会允许 HEAD 请求。但是读取您给出的响应的异步方法是更好的方法
【解决方案3】:

我很确定你想要的是reader.BaseStream.Length,所以你可以在阅读之前知道长度。 (至少我做到了,所以我尝试了)但它抛出了 NotSupportedException 和消息 This stream does not support seek operations。所以我用谷歌搜索了StreamReader + This stream... 并找到了这个 SO 链接:

Error “This stream does not support seek operations” in C#

所以简短的回答是:不可能。

【讨论】:

  • 好的,这是不可能的。有办法解决吗?为了达到同样的效果?
  • @chiapa 我还没有找到解决方案。我已经搜索和搜索。我什至尝试了WebClient,但它抛出了NotSupportedException,它不支持搜索。因此,除非content-length 可用,否则我认为这是一个失败的原因。对不起。
  • 感谢您的努力,这确实很棘手。尽管自上周五以来没有寻找任何新东西,但我自己还没有找到解决方案。我会尝试更多,也许明天我会再次深入研究。
【解决方案4】:

也许一个简单的秒表是一种开始?

    Dim timer As System.Diagnostics.Stopwatch = New Stopwatch()

    Dim request As HttpWebRequest = WebRequest.Create(requestUrl)

    timer.Start()
    Dim response As HttpWebResponse = request.GetResponse()
    timer.Stop()

    Dim reader As StreamReader = New StreamReader(response.GetResponseStream())
    Dim json As String = reader.ReadToEnd()
    response.Close()

    Label1.Text = "Secs:" & timer.Elapsed.ToString()

【讨论】:

  • 谢谢@carleson。好吧,你的建议很好:我确实知道时间已经过去了。但我无法将其与手术的进展联系起来。我不知道它会持续 5 秒还是 10 秒(它是可变的),所以我不知道在 4 秒时我是在 80% 还是在 40% 的操作。
【解决方案5】:

这里是 Microsoft 示例的链接 您在其中设置缓冲区大小和对响应对象的回调 https://msdn.microsoft.com/en-us/library/86wf6409%28v=vs.110%29.aspx

【讨论】:

  • “虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-26
  • 2011-10-07
  • 1970-01-01
  • 2023-03-14
相关资源
最近更新 更多