执行分块文件上载时无法使用REST Web Api GetAsync获得响应

时间:2012-03-22 03:06:43

标签: .net rest httpclient asp.net-web-api

我正在编写我的第一个REST Web服务(使用MVC 4 Beta和时髦的新Web Api),并且在使用HttpClient.PutAsync方法时遇到了问题

概述是我正在尝试编写一个文件上传过程,我正在整理文件并使用MVC 4 WebApi的HttpClient.PutAsync方法。如果我只是循环我的块并调用PutAsync,那么一切都很好。但是,有时服务器无法编写特定的块,我需要将该故障报告回客户端,以便可以重试块上传。我还需要了解上传何时完成,因此上传步骤的下一个阶段可以执行(例如:上传文件后,需要进行数据库写入,以及一些额外的处理需求在服务器上启动以创建预览图像等。

问题在于,当我尝试从PutAsync方法访问响应时,所有内容都落入尖叫堆中 - 第一个块由客户端发送,但从未被服务器接收,整个上载过程研磨到停了下来。

这是用于处理PutAsset方法的服务器代码(为清楚起见,我已经删除了所有不相关的代码)。它似乎工作正常 - 文件可以愉快地上传和重新组装:

<HttpPut()>
Public Function PutArtwork(assetID As String, data As AssetBlock) As HttpResponseMessage(Of AssetBlockResponse)

    'Get the info from the tracker file
    Dim lContents As String = IO.File.ReadAllText(IO.Path.Combine(My.Settings.ServiceTempDirectory, "Asset/", (data.Tracker & ".tracker")))
    Dim lAssetInfo As AssetUpload = JsonConvert.DeserializeObject(Of AssetUpload)(lContents)

    If WriteChunkToFile(assetID, lAssetInfo, data) Then
        Dim lTracker As AssetBlockResponse = New AssetBlockResponse
        lTracker.TrackerGID = data.Tracker
        Return New HttpResponseMessage(Of AssetBlockResponse)(lTracker, HttpStatusCode.Accepted)
    Else
        tracker.TrackerGID = Guid.NewGuid.ToString
        tracker.ErrorCode = ApiErrorCode.FileChunkWriteFailed
        tracker.Message = String.Format("Write error for chunk {0-{1}", data.Offset, data.Length - 1)
        Dim lResponse As HttpResponseMessage(Of AssetBlockResponse) = New HttpResponseMessage(Of AssetBlockResponse)(tracker, HttpStatusCode.InternalServerError)
        lResponse.ReasonPhrase = String.Format("Write error for chunk {0-{1}", data.Offset, data.Length - 1)
        Return lResponse
    End If

End Function

Private Function WriteChunkToFile(assetID As String, info As AssetUpload, data As AssetBlock) As Boolean
    Dim lFile As String = GetAssetPath(assetID, String.Empty, data.Tracker & ".upload", 1, False, data.Length)
    If IO.File.Exists(lFile) Then
        Dim lBytes As Byte() = System.Convert.FromBase64String(data.FileData)
        Try
          WriteChunk(lFile, lBytes, data.Offset, data.Length)
          Return True
        Catch ex as Exception
          Return False
       End Try
End Function

Private Sub WriteChunk(filePath As String, data() As Byte, offset As Integer, length As Integer)
    Using lStream = IO.File.Open(filePath, FileMode.Open, FileAccess.Write, FileShare.Write)
        lStream.Position = offset
        lStream.Write(data, 0, length)
        lStream.Close()
    End Using
End Sub

调用REST服务的客户端代码:

Dim lClient As HttpClient = New HttpClient
Dim lBlock As AssetBlock = New AssetBlock

lBlock.Tracker = tracker.TrackerGID

lClient.BaseAddress = CreateUploadAddressUri(_BaseAddress, assetInfo)

Dim lLocalChunk As UploadChunkInfo
For Each chunkIterator As UploadChunkInfo In chunks
    lLocalChunk = chunkIterator
    lBlock.BlockHash = AssetMethods.SHA1HashBytes(lLocalChunk.Chunk)
    lBlock.Offset = lLocalChunk.Offset
    lBlock.Length = lLocalChunk.Chunk.Length
    lBlock.FileData = Convert.ToBase64String(lLocalChunk.Chunk)
    Dim lTask = lClient.PutAsync(lClient.BaseAddress, New StringContent(JsonConvert.SerializeObject(lBlock), Text.Encoding.UTF8, "application/json"))
Next

以上版本工作正常,但这是一个消防方法。我想要的是从PutAsync获取响应,检查以确保它一切正常,然后继续下一次尝试,或根据需要重试块。所以上面的.PutAsync代码真的需要看起来像:

Dim lTask = lClient.PutAsync(lClient.BaseAddress, New StringContent(JsonConvert.SerializeObject(lBlock), Text.Encoding.UTF8, "application/json"))
If lTask.Result.IsSuccessStatusCode Then '!! This line is never hit !!
    lTask.Result.Content.ReadAsStringAsync().ContinueWith(Sub(readtask)
                                                            lLocalChunk.Status = UploadChunkInfo.ChunkUploadStatus.Uploaded
                                                        End Sub, TaskScheduler.FromCurrentSynchronizationContext())
Else
    lLocalChunk.Status = UploadChunkInfo.ChunkUploadStatus.Failed
    lLocalChunk.RetryCount += 1
End If

如果块被标记为失败,那么我们将重试作为循环的一部分。一旦我们重新尝试了X次,我们将整个上传过程视为失败并将其报告给用户。但是使用Result检查执行PutAsync只会停止其轨道中的循环。它甚至从未击中服务器Put方法。 Fiddler没有向服务器显示流量,

我现在完全没有想法 - 有什么想法吗?

1 个答案:

答案 0 :(得分:0)

以下是我测试文件上传到Web API的一些代码

   [Fact]
        public void UploadAFile() {

            var httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri(_HostUrl);
            httpClient.DefaultRequestHeaders.TransferEncodingChunked = true;
            var fileContent = new StreamContent(typeof(SimpleApiController).Assembly.GetManifestResourceStream(typeof(SimpleApiController),"bigfile.pdf"));
            fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

            var responseMessage = httpClient.PostAsync("SimpleApi/SendBigFile", fileContent).Result;


            Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
            Assert.Equal("102061", responseMessage.Content.ReadAsStringAsync().Result);
        }

如果您正在进行自托管,则需要在服务器上设置如下配置

var config = new HttpSelfHostConfiguration(_HostUrl) {
                      TransferMode = System.ServiceModel.TransferMode.Streamed,
                      // Bypass 64K buffer in request body handler
                      MaxReceivedMessageSize = 1024 * 500,
                      MaxBufferSize = 1024 * 500
                  }; // Increase DOS protection limit

,服务器端处理程序看起来像

 public HttpResponseMessage SendBigFile() {

            var stream = Request.Content.ReadAsStreamAsync().Result;
            var memoryStream = new MemoryStream();
            stream.CopyTo(memoryStream);
            var response = new HttpResponseMessage(HttpStatusCode.OK); ;
            response.Content = new StringContent(memoryStream.Length.ToString());
            return response;

        }