Не удается получить ответ с помощью REST WebApi PutAsync при выполнении загрузки фрагментированного файла - PullRequest
1 голос
/ 22 марта 2012

Я работаю над написанием своих первых веб-сервисов REST (используя бета-версию MVC 4 и шикарный новый 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 раз, мы рассматриваем весь процесс загрузки как сбой и сообщаем об этом пользователю. Но выполнение PutAsync с проверкой результатов просто останавливает цикл в его дорожках. Он никогда даже не попадает на метод Put сервера. Fiddler показывает отсутствие трафика на сервер,

У меня совершенно нет идей - есть мысли?

1 Ответ

0 голосов
/ 22 марта 2012

Вот код, который у меня есть, который проверяет загрузку файла в 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;

        }
...