// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Properties;
namespace System.Web.Http.Batch
{
///
/// Default implementation of that encodes the HTTP request/response messages as MIME multipart.
///
///
/// By default, it buffers the HTTP request messages in memory during parsing.
///
public class DefaultHttpBatchHandler : HttpBatchHandler
{
private const string MultiPartContentSubtype = "mixed";
private const string MultiPartMixed = "multipart/mixed";
private BatchExecutionOrder _executionOrder;
///
/// Initializes a new instance of the class.
///
/// The for handling the individual batch requests.
public DefaultHttpBatchHandler(HttpServer httpServer)
: base(httpServer)
{
ExecutionOrder = BatchExecutionOrder.Sequential;
SupportedContentTypes = new List() { MultiPartMixed };
}
///
/// Gets or sets the execution order for the batch requests. The default execution order is sequential.
///
/// value
public BatchExecutionOrder ExecutionOrder
{
get
{
return _executionOrder;
}
set
{
if (!Enum.IsDefined(typeof(BatchExecutionOrder), value))
{
throw new InvalidEnumArgumentException("value", (int)value, typeof(BatchExecutionOrder));
}
_executionOrder = value;
}
}
///
/// Gets the supported content types for the batch request.
///
public IList SupportedContentTypes { get; private set; }
///
/// Creates the batch response message.
///
/// The responses for the batch requests.
/// The original request containing all the batch requests.
/// The token to monitor for cancellation requests.
/// The batch response message.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
public virtual Task CreateResponseMessageAsync(IList responses, HttpRequestMessage request, CancellationToken cancellationToken)
{
if (responses == null)
{
throw Error.ArgumentNull("responses");
}
if (request == null)
{
throw Error.ArgumentNull("request");
}
MultipartContent batchContent = new MultipartContent(MultiPartContentSubtype);
foreach (HttpResponseMessage batchResponse in responses)
{
batchContent.Add(new HttpMessageContent(batchResponse));
}
HttpResponseMessage response = request.CreateResponse();
response.Content = batchContent;
return Task.FromResult(response);
}
///
public override async Task ProcessBatchAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
ValidateRequest(request);
IList subRequests = await ParseBatchRequestsAsync(request, cancellationToken);
try
{
IList responses = await ExecuteRequestMessagesAsync(subRequests, cancellationToken);
return await CreateResponseMessageAsync(responses, request, cancellationToken);
}
finally
{
foreach (HttpRequestMessage subRequest in subRequests)
{
request.RegisterForDispose(subRequest.GetResourcesForDisposal());
request.RegisterForDispose(subRequest);
}
}
}
///
/// Executes the batch request messages.
///
/// The collection of batch request messages.
/// The token to monitor for cancellation requests.
/// A collection of for the batch requests.
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of response messages asynchronously.")]
public virtual async Task> ExecuteRequestMessagesAsync(IEnumerable requests, CancellationToken cancellationToken)
{
if (requests == null)
{
throw Error.ArgumentNull("requests");
}
List responses = new List();
try
{
switch (ExecutionOrder)
{
case BatchExecutionOrder.Sequential:
foreach (HttpRequestMessage request in requests)
{
responses.Add(await Invoker.SendAsync(request, cancellationToken));
}
break;
case BatchExecutionOrder.NonSequential:
responses.AddRange(await Task.WhenAll(requests.Select(request => Invoker.SendAsync(request, cancellationToken))));
break;
}
}
catch
{
foreach (HttpResponseMessage response in responses)
{
if (response != null)
{
response.Dispose();
}
}
throw;
}
return responses;
}
///
/// Converts the incoming batch request into a collection of request messages.
///
/// The request containing the batch request messages.
/// The token to monitor for cancellation requests.
/// A collection of .
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")]
public virtual async Task> ParseBatchRequestsAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
List requests = new List();
cancellationToken.ThrowIfCancellationRequested();
MultipartStreamProvider streamProvider = await request.Content.ReadAsMultipartAsync();
foreach (HttpContent httpContent in streamProvider.Contents)
{
cancellationToken.ThrowIfCancellationRequested();
HttpRequestMessage innerRequest = request.RequestUri == null ? await httpContent.ReadAsHttpRequestMessageAsync() : await httpContent.ReadAsHttpRequestMessageAsync(request.RequestUri.Scheme);
innerRequest.CopyBatchRequestProperties(request);
requests.Add(innerRequest);
}
return requests;
}
///
/// Validates the incoming request that contains the batch request messages.
///
/// The request containing the batch request messages.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")]
public virtual void ValidateRequest(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (request.Content == null)
{
throw new HttpResponseException(request.CreateErrorResponse(
HttpStatusCode.BadRequest,
SRResources.BatchRequestMissingContent));
}
MediaTypeHeaderValue contentType = request.Content.Headers.ContentType;
if (contentType == null)
{
throw new HttpResponseException(request.CreateErrorResponse(
HttpStatusCode.BadRequest,
SRResources.BatchContentTypeMissing));
}
if (!SupportedContentTypes.Contains(contentType.MediaType, StringComparer.OrdinalIgnoreCase))
{
throw new HttpResponseException(request.CreateErrorResponse(
HttpStatusCode.BadRequest,
Error.Format(SRResources.BatchMediaTypeNotSupported, contentType.MediaType)));
}
}
}
}