我正在编写我的第一个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没有向服务器显示流量,
我现在完全没有想法 - 有什么想法吗?
答案 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;
}