Files
linux-packaging-mono/external/aspnetwebstack/src/System.Web.Http.SelfHost/Channels/HttpMessageEncoderFactory.cs
Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

271 lines
10 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.SelfHost.Properties;
using System.Web.Http.SelfHost.ServiceModel.Channels;
namespace System.Web.Http.SelfHost.Channels
{
internal class HttpMessageEncoderFactory : MessageEncoderFactory
{
private HttpMessageEncoder _encoder;
public HttpMessageEncoderFactory()
{
_encoder = new HttpMessageEncoder();
}
public override MessageEncoder Encoder
{
get { return _encoder; }
}
public override MessageVersion MessageVersion
{
get { return MessageVersion.None; }
}
public override MessageEncoder CreateSessionEncoder()
{
throw Error.NotSupported(SRResources.HttpMessageEncoderFactoryDoesNotSupportSessionEncoder, typeof(HttpMessageEncoderFactory).Name);
}
private class HttpMessageEncoder : MessageEncoder
{
private const string ContentTypeHeaderName = "Content-Type";
private const string MaxSentMessageSizeExceededResourceStringName = "MaxSentMessageSizeExceeded";
private static readonly string _httpBindingClassName = typeof(HttpBinding).FullName;
private static readonly string _httpResponseMessageClassName = typeof(HttpResponseMessage).FullName;
public override string ContentType
{
get { return String.Empty; }
}
public override string MediaType
{
get { return String.Empty; }
}
public override MessageVersion MessageVersion
{
get { return MessageVersion.None; }
}
public override bool IsContentTypeSupported(string contentType)
{
if (contentType == null)
{
throw Error.ArgumentNull("contentType");
}
return true;
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "disposed later.")]
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
if (bufferManager == null)
{
throw Error.ArgumentNull("bufferManager");
}
HttpRequestMessage request = new HttpRequestMessage();
request.Content = new ByteArrayBufferManagerContent(bufferManager, buffer.Array, buffer.Offset, buffer.Count);
if (!String.IsNullOrEmpty(contentType))
{
request.Content.Headers.Add(ContentTypeHeaderName, contentType);
}
Message message = request.ToMessage();
message.Properties.Encoder = this;
return message;
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "disposed later.")]
public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
{
if (stream == null)
{
throw Error.ArgumentNull("stream");
}
HttpRequestMessage request = new HttpRequestMessage();
request.Content = new StreamContent(stream);
if (!String.IsNullOrEmpty(contentType))
{
request.Content.Headers.Add(ContentTypeHeaderName, contentType);
}
Message message = request.ToMessage();
message.Properties.Encoder = this;
return message;
}
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
if (message == null)
{
throw Error.ArgumentNull("message");
}
if (bufferManager == null)
{
throw Error.ArgumentNull("bufferManager");
}
if (maxMessageSize < 0)
{
throw Error.ArgumentOutOfRange("maxMessageSize", maxMessageSize, SRResources.NonnegativeNumberRequired);
}
if (messageOffset < 0)
{
throw Error.ArgumentOutOfRange("messageOffset", messageOffset, SRResources.NonnegativeNumberRequired);
}
if (messageOffset > maxMessageSize)
{
throw Error.Argument(String.Empty, SRResources.ParameterMustBeLessThanOrEqualSecondParameter, "messageOffset", "maxMessageSize");
}
// TODO: DevDiv2 bug #378887 -- find out how to eliminate this middle buffer
using (BufferManagerOutputStream stream = new BufferManagerOutputStream(MaxSentMessageSizeExceededResourceStringName, 0, maxMessageSize, bufferManager))
{
int num;
stream.Skip(messageOffset);
WriteMessage(message, stream);
byte[] buffer = stream.ToArray(out num);
ArraySegment<byte> messageData = new ArraySegment<byte>(buffer, 0, num - messageOffset);
// ToArray transfers full ownership of buffer to us, meaning we are responsible for returning it to BufferManager.
// But we must delay that release until WCF has finished with the buffer we are returning from this method.
HttpMessageEncodingRequestContext requestContext = HttpMessageEncodingRequestContext.GetContextFromMessage(message);
Contract.Assert(requestContext != null);
requestContext.BufferManager = bufferManager;
requestContext.BufferToReturn = buffer;
return messageData;
}
}
[SuppressMessage("Microsoft.WebAPI", "CR4001:DoNotCallProblematicMethodsOnTask", Justification = "The WriteMessage() API is synchronous, and Wait() won't deadlock in self-host.")]
public override void WriteMessage(Message message, Stream stream)
{
if (message == null)
{
throw Error.ArgumentNull("message");
}
if (stream == null)
{
throw Error.ArgumentNull("stream");
}
ThrowIfMismatchedMessageVersion(message);
message.Properties.Encoder = this;
HttpResponseMessage response = GetHttpResponseMessageOrThrow(message);
if (response.Content != null)
{
HttpMessageEncodingRequestContext requestContext =
HttpMessageEncodingRequestContext.GetContextFromMessage(message);
try
{
response.Content.CopyToAsync(stream)
.Catch((info) =>
{
if (requestContext != null)
{
requestContext.Exception = info.Exception;
}
return info.Throw();
})
.Wait();
}
catch (Exception ex)
{
if (requestContext != null)
{
requestContext.Exception = ex;
}
throw;
}
}
}
internal void ThrowIfMismatchedMessageVersion(Message message)
{
if (message.Version != MessageVersion)
{
throw new ProtocolException(Error.Format(SRResources.EncoderMessageVersionMismatch, message.Version, MessageVersion));
}
}
private static HttpResponseMessage GetHttpResponseMessageOrThrow(Message message)
{
HttpResponseMessage response = message.ToHttpResponseMessage();
if (response == null)
{
throw Error.InvalidOperation(
SRResources.MessageInvalidForHttpMessageEncoder,
_httpBindingClassName,
HttpMessageExtensions.ToMessageMethodName,
_httpResponseMessageClassName);
}
return response;
}
private class ByteArrayBufferManagerContent : ByteArrayContent
{
private BufferManager _bufferManager;
private byte[] _content;
public ByteArrayBufferManagerContent(BufferManager bufferManager, byte[] content, int offset, int count)
: base(content, offset, count)
{
Contract.Assert(bufferManager != null, "The 'bufferManager' parameter should never be null.");
_bufferManager = bufferManager;
_content = content;
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
BufferManager oldBufferManager = Interlocked.Exchange(ref _bufferManager, null);
if (oldBufferManager != null)
{
oldBufferManager.ReturnBuffer(_content);
_content = null;
}
}
}
finally
{
base.Dispose(disposing);
}
}
}
}
}
}