//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System; using System.IO; using System.Net.Http; using System.Runtime; using System.Threading.Tasks; using System.Xml; public static class ByteStreamMessage { public static Message CreateMessage(Stream stream) { if (stream == null) { throw FxTrace.Exception.ArgumentNull("stream"); } return CreateMessage(stream, XmlDictionaryReaderQuotas.Max, true); // moveBodyReaderToContent is true, for consistency with the other implementations of Message (including the Message base class itself) } public static Message CreateMessage(ArraySegment buffer) { return CreateMessage(buffer, null); } public static Message CreateMessage(ArraySegment buffer, BufferManager bufferManager) { if (buffer.Array == null) { throw FxTrace.Exception.ArgumentNull("buffer.Array", SR.ArgumentPropertyShouldNotBeNullError("buffer.Array")); } ByteStreamBufferedMessageData data = new ByteStreamBufferedMessageData(buffer, bufferManager); return CreateMessage(data, XmlDictionaryReaderQuotas.Max, true); // moveBodyReaderToContent is true, for consistency with the other implementations of Message (including the Message base class itself) } internal static Message CreateMessage(Stream stream, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { return new InternalByteStreamMessage(stream, quotas, moveBodyReaderToContent); } internal static Message CreateMessage(HttpRequestMessage httpRequestMessage, XmlDictionaryReaderQuotas quotas) { return new InternalByteStreamMessage(httpRequestMessage, quotas, true); // moveBodyReaderToContent is true, for consistency with the other implementations of Message (including the Message base class itself) } internal static Message CreateMessage(HttpResponseMessage httpResponseMessage, XmlDictionaryReaderQuotas quotas) { return new InternalByteStreamMessage(httpResponseMessage, quotas, true); // moveBodyReaderToContent is true, for consistency with the other implementations of Message (including the Message base class itself) } internal static Message CreateMessage(ByteStreamBufferedMessageData bufferedMessageData, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { return new InternalByteStreamMessage(bufferedMessageData, quotas, moveBodyReaderToContent); } internal static bool IsInternalByteStreamMessage(Message message) { Fx.Assert(message != null, "message should not be null"); return message is InternalByteStreamMessage; } class InternalByteStreamMessage : Message { BodyWriter bodyWriter; MessageHeaders headers; MessageProperties properties; XmlByteStreamReader reader; /// /// If set to true, OnGetReaderAtBodyContents() calls MoveToContent() on the reader before returning it. /// If set to false, the reader is positioned on None, just before the root element of the body message. /// /// /// We use this flag to preserve compatibility between .net 4.0 (or previous) and .net 4.5 (or later). /// /// In .net 4.0: /// - WebMessageEncodingBindingElement uses a raw encoder, different than ByteStreamMessageEncoder. /// - ByteStreamMessageEncodingBindingElement uses the ByteStreamMessageEncoder. /// - When the WebMessageEncodingBindingElement is used, the Message.GetReaderAtBodyContents() method returns /// an XmlDictionaryReader positioned initially on content (the root element of the xml); that's because MoveToContent() is called /// on the reader before it's returned. /// - When the ByteStreamMessageEncodingBindingElement is used, the Message.GetReaderAtBodyContents() method returns an /// XmlDictionaryReader positioned initially on None (just before the root element). /// /// In .net 4.5: /// - Both WebMessageEncodingBindingElement and ByteStreamMessageEncodingBindingElement use the ByteStreamMessageEncoder. /// - So we need the ByteStreamMessageEncoder to call MoveToContent() when used by WebMessageEncodingBindingElement, and not do so /// when used by the ByteStreamMessageEncodingBindingElement. /// - Preserving the compatibility with 4.0 is important especially because 4.5 is an in-place upgrade of 4.0. /// /// See 252277 @ CSDMain for other info. /// bool moveBodyReaderToContent; public InternalByteStreamMessage(ByteStreamBufferedMessageData bufferedMessageData, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { // Assign both writer and reader here so that we can CreateBufferedCopy without the need to // abstract between a streamed or buffered message. We're protected here by the state on Message // preventing both a read/write. quotas = ByteStreamMessageUtility.EnsureQuotas(quotas); this.bodyWriter = new BufferedBodyWriter(bufferedMessageData); this.headers = new MessageHeaders(MessageVersion.None); this.properties = new MessageProperties(); this.reader = new XmlBufferedByteStreamReader(bufferedMessageData, quotas); this.moveBodyReaderToContent = moveBodyReaderToContent; } public InternalByteStreamMessage(Stream stream, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { // Assign both writer and reader here so that we can CreateBufferedCopy without the need to // abstract between a streamed or buffered message. We're protected here by the state on Message // preventing both a read/write on the same stream. quotas = ByteStreamMessageUtility.EnsureQuotas(quotas); this.bodyWriter = StreamedBodyWriter.Create(stream); this.headers = new MessageHeaders(MessageVersion.None); this.properties = new MessageProperties(); this.reader = XmlStreamedByteStreamReader.Create(stream, quotas); this.moveBodyReaderToContent = moveBodyReaderToContent; } public InternalByteStreamMessage(HttpRequestMessage httpRequestMessage, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { Fx.Assert(httpRequestMessage != null, "The 'httpRequestMessage' parameter should not be null."); // Assign both writer and reader here so that we can CreateBufferedCopy without the need to // abstract between a streamed or buffered message. We're protected here by the state on Message // preventing both a read/write on the same stream. quotas = ByteStreamMessageUtility.EnsureQuotas(quotas); this.bodyWriter = StreamedBodyWriter.Create(httpRequestMessage); this.headers = new MessageHeaders(MessageVersion.None); this.properties = new MessageProperties(); this.reader = XmlStreamedByteStreamReader.Create(httpRequestMessage, quotas); this.moveBodyReaderToContent = moveBodyReaderToContent; } public InternalByteStreamMessage(HttpResponseMessage httpResponseMessage, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { Fx.Assert(httpResponseMessage != null, "The 'httpResponseMessage' parameter should not be null."); // Assign both writer and reader here so that we can CreateBufferedCopy without the need to // abstract between a streamed or buffered message. We're protected here by the state on Message // preventing both a read/write on the same stream. quotas = ByteStreamMessageUtility.EnsureQuotas(quotas); this.bodyWriter = StreamedBodyWriter.Create(httpResponseMessage); this.headers = new MessageHeaders(MessageVersion.None); this.properties = new MessageProperties(); this.reader = XmlStreamedByteStreamReader.Create(httpResponseMessage, quotas); this.moveBodyReaderToContent = moveBodyReaderToContent; } InternalByteStreamMessage(ByteStreamBufferedMessageData messageData, MessageHeaders headers, MessageProperties properties, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) { this.headers = new MessageHeaders(headers); this.properties = new MessageProperties(properties); this.bodyWriter = new BufferedBodyWriter(messageData); this.reader = new XmlBufferedByteStreamReader(messageData, quotas); this.moveBodyReaderToContent = moveBodyReaderToContent; } public override MessageHeaders Headers { get { if (this.IsDisposed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } return this.headers; } } public override bool IsEmpty { get { if (this.IsDisposed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } return false; } } public override bool IsFault { get { if (this.IsDisposed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } return false; } } public override MessageProperties Properties { get { if (this.IsDisposed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } return this.properties; } } public override MessageVersion Version { get { if (this.IsDisposed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } return MessageVersion.None; } } protected override void OnBodyToString(XmlDictionaryWriter writer) { if (this.bodyWriter.IsBuffered) { bodyWriter.WriteBodyContents(writer); } else { writer.WriteString(SR.MessageBodyIsStream); } } protected override void OnClose() { Exception ex = null; try { base.OnClose(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } ex = e; } try { if (properties != null) { properties.Dispose(); } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } if (ex == null) { ex = e; } } try { if (reader != null) { reader.Close(); } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } if (ex == null) { ex = e; } } if (ex != null) { throw FxTrace.Exception.AsError(ex); } this.bodyWriter = null; } protected override MessageBuffer OnCreateBufferedCopy(int maxBufferSize) { BufferedBodyWriter bufferedBodyWriter; if (this.bodyWriter.IsBuffered) { // Can hand this off in buffered case without making a new one. bufferedBodyWriter = (BufferedBodyWriter)this.bodyWriter; } else { bufferedBodyWriter = (BufferedBodyWriter)this.bodyWriter.CreateBufferedCopy(maxBufferSize); } // Protected by Message state to be called only once. this.bodyWriter = null; return new ByteStreamMessageBuffer(bufferedBodyWriter.MessageData, this.headers, this.properties, this.reader.Quotas, this.moveBodyReaderToContent); } protected override T OnGetBody(XmlDictionaryReader reader) { Fx.Assert(reader is XmlByteStreamReader, "reader should be XmlByteStreamReader"); if (this.IsDisposed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } Type typeT = typeof(T); if (typeof(Stream) == typeT) { Stream stream = (reader as XmlByteStreamReader).ToStream(); reader.Close(); return (T)(object)stream; } else if (typeof(byte[]) == typeT) { byte[] buffer = (reader as XmlByteStreamReader).ToByteArray(); reader.Close(); return (T)(object)buffer; } throw FxTrace.Exception.AsError( new NotSupportedException(SR.ByteStreamMessageGetTypeNotSupported(typeT.FullName))); } protected override XmlDictionaryReader OnGetReaderAtBodyContents() { XmlDictionaryReader r = this.reader; this.reader = null; if ((r != null) && this.moveBodyReaderToContent) { r.MoveToContent(); } return r; } protected override IAsyncResult OnBeginWriteMessage(XmlDictionaryWriter writer, AsyncCallback callback, object state) { WriteMessagePreamble(writer); return new OnWriteMessageAsyncResult(writer, this, callback, state); } protected override void OnEndWriteMessage(IAsyncResult result) { OnWriteMessageAsyncResult.End(result); } protected override void OnWriteBodyContents(XmlDictionaryWriter writer) { this.bodyWriter.WriteBodyContents(writer); } protected override IAsyncResult OnBeginWriteBodyContents(XmlDictionaryWriter writer, AsyncCallback callback, object state) { return this.bodyWriter.BeginWriteBodyContents(writer, callback, state); } protected override void OnEndWriteBodyContents(IAsyncResult result) { this.bodyWriter.EndWriteBodyContents(result); } class OnWriteMessageAsyncResult : AsyncResult { InternalByteStreamMessage message; XmlDictionaryWriter writer; public OnWriteMessageAsyncResult(XmlDictionaryWriter writer, InternalByteStreamMessage message, AsyncCallback callback, object state) : base(callback, state) { this.message = message; this.writer = writer; IAsyncResult result = this.message.OnBeginWriteBodyContents(this.writer, PrepareAsyncCompletion(HandleWriteBodyContents), this); bool completeSelf = SyncContinue(result); if (completeSelf) { this.Complete(true); } } static bool HandleWriteBodyContents(IAsyncResult result) { OnWriteMessageAsyncResult thisPtr = (OnWriteMessageAsyncResult)result.AsyncState; thisPtr.message.OnEndWriteBodyContents(result); thisPtr.message.WriteMessagePostamble(thisPtr.writer); return true; } public static void End(IAsyncResult result) { AsyncResult.End(result); } } class BufferedBodyWriter : BodyWriter { ByteStreamBufferedMessageData bufferedMessageData; public BufferedBodyWriter(ByteStreamBufferedMessageData bufferedMessageData) : base(true) { this.bufferedMessageData = bufferedMessageData; } internal ByteStreamBufferedMessageData MessageData { get { return bufferedMessageData; } } protected override BodyWriter OnCreateBufferedCopy(int maxBufferSize) { // Never called because when copying a Buffered message, we simply hand off the existing BodyWriter // to the new message. Fx.Assert(false, "This is never called"); return null; } protected override void OnWriteBodyContents(XmlDictionaryWriter writer) { writer.WriteStartElement(ByteStreamMessageUtility.StreamElementName, string.Empty); writer.WriteBase64(this.bufferedMessageData.Buffer.Array, this.bufferedMessageData.Buffer.Offset, this.bufferedMessageData.Buffer.Count); writer.WriteEndElement(); } } abstract class StreamedBodyWriter : BodyWriter { private StreamedBodyWriter() : base(false) { } public static StreamedBodyWriter Create(Stream stream) { return new StreamBasedStreamedBodyWriter(stream); } public static StreamedBodyWriter Create(HttpRequestMessage httpRequestMessage) { return new HttpRequestMessageStreamedBodyWriter(httpRequestMessage); } public static StreamedBodyWriter Create(HttpResponseMessage httpResponseMessage) { return new HttpResponseMessageStreamedBodyWriter(httpResponseMessage); } // OnCreateBufferedCopy / OnWriteBodyContents can only be called once - protected by state on Message (either copied or written once) protected override BodyWriter OnCreateBufferedCopy(int maxBufferSize) { using (BufferManagerOutputStream bufferedStream = new BufferManagerOutputStream(SR.MaxReceivedMessageSizeExceeded("{0}"), maxBufferSize)) { using (XmlDictionaryWriter writer = new XmlByteStreamWriter(bufferedStream, true)) { OnWriteBodyContents(writer); writer.Flush(); int size; byte[] bytesArray = bufferedStream.ToArray(out size); ByteStreamBufferedMessageData bufferedMessageData = new ByteStreamBufferedMessageData(new ArraySegment(bytesArray, 0, size)); return new BufferedBodyWriter(bufferedMessageData); } } } // OnCreateBufferedCopy / OnWriteBodyContents can only be called once - protected by state on Message (either copied or written once) protected override void OnWriteBodyContents(XmlDictionaryWriter writer) { writer.WriteStartElement(ByteStreamMessageUtility.StreamElementName, string.Empty); writer.WriteValue(new ByteStreamStreamProvider(this.GetStream())); writer.WriteEndElement(); } protected override IAsyncResult OnBeginWriteBodyContents(XmlDictionaryWriter writer, AsyncCallback callback, object state) { return new WriteBodyContentsAsyncResult(writer, this.GetStream(), callback, state); } protected override void OnEndWriteBodyContents(IAsyncResult result) { WriteBodyContentsAsyncResult.End(result); } protected abstract Stream GetStream(); class ByteStreamStreamProvider : IStreamProvider { Stream stream; internal ByteStreamStreamProvider(Stream stream) { this.stream = stream; } public Stream GetStream() { return stream; } public void ReleaseStream(Stream stream) { //Noop } } class WriteBodyContentsAsyncResult : AsyncResult { XmlDictionaryWriter writer; public WriteBodyContentsAsyncResult(XmlDictionaryWriter writer, Stream stream, AsyncCallback callback, object state) : base(callback, state) { this.writer = writer; this.writer.WriteStartElement(ByteStreamMessageUtility.StreamElementName, string.Empty); IAsyncResult result = this.writer.WriteValueAsync(new ByteStreamStreamProvider(stream)).AsAsyncResult(PrepareAsyncCompletion(HandleWriteBodyContents), this); bool completeSelf = SyncContinue(result); // Note: The current task implementation hard codes the "IAsyncResult.CompletedSynchronously" property to false, so this fast path will never // be hit, and we will always hop threads. CSDMain #210220 if (completeSelf) { this.Complete(true); } } static bool HandleWriteBodyContents(IAsyncResult result) { // If result is a task, we need to get the result so that exceptions are bubbled up in case the task is faulted. Task t = result as Task; if (t != null) { t.GetAwaiter().GetResult(); } WriteBodyContentsAsyncResult thisPtr = (WriteBodyContentsAsyncResult)result.AsyncState; thisPtr.writer.WriteEndElement(); return true; } public static void End(IAsyncResult result) { AsyncResult.End(result); } } class StreamBasedStreamedBodyWriter : StreamedBodyWriter { private Stream stream; public StreamBasedStreamedBodyWriter(Stream stream) { this.stream = stream; } protected override Stream GetStream() { return this.stream; } } class HttpRequestMessageStreamedBodyWriter : StreamedBodyWriter { private HttpRequestMessage httpRequestMessage; public HttpRequestMessageStreamedBodyWriter(HttpRequestMessage httpRequestMessage) { Fx.Assert(httpRequestMessage != null, "The 'httpRequestMessage' parameter should not be null."); this.httpRequestMessage = httpRequestMessage; } protected override Stream GetStream() { HttpContent content = this.httpRequestMessage.Content; if (content != null) { return content.ReadAsStreamAsync().Result; } return new MemoryStream(EmptyArray.Instance); } protected override BodyWriter OnCreateBufferedCopy(int maxBufferSize) { HttpContent content = this.httpRequestMessage.Content; if (content != null) { content.LoadIntoBufferAsync(maxBufferSize).Wait(); } return base.OnCreateBufferedCopy(maxBufferSize); } } class HttpResponseMessageStreamedBodyWriter : StreamedBodyWriter { private HttpResponseMessage httpResponseMessage; public HttpResponseMessageStreamedBodyWriter(HttpResponseMessage httpResponseMessage) { Fx.Assert(httpResponseMessage != null, "The 'httpResponseMessage' parameter should not be null."); this.httpResponseMessage = httpResponseMessage; } protected override Stream GetStream() { HttpContent content = this.httpResponseMessage.Content; if (content != null) { return content.ReadAsStreamAsync().Result; } return new MemoryStream(EmptyArray.Instance); } protected override BodyWriter OnCreateBufferedCopy(int maxBufferSize) { HttpContent content = this.httpResponseMessage.Content; if (content != null) { content.LoadIntoBufferAsync(maxBufferSize).Wait(); } return base.OnCreateBufferedCopy(maxBufferSize); } } } class ByteStreamMessageBuffer : MessageBuffer { bool closed; MessageHeaders headers; ByteStreamBufferedMessageData messageData; MessageProperties properties; XmlDictionaryReaderQuotas quotas; bool moveBodyReaderToContent; object thisLock = new object(); public ByteStreamMessageBuffer(ByteStreamBufferedMessageData messageData, MessageHeaders headers, MessageProperties properties, XmlDictionaryReaderQuotas quotas, bool moveBodyReaderToContent) : base() { this.messageData = messageData; this.headers = new MessageHeaders(headers); this.properties = new MessageProperties(properties); this.quotas = new XmlDictionaryReaderQuotas(); quotas.CopyTo(this.quotas); this.moveBodyReaderToContent = moveBodyReaderToContent; this.messageData.Open(); } public override int BufferSize { get { return this.messageData.Buffer.Count; } } object ThisLock { get { return this.thisLock; } } public override void Close() { lock (ThisLock) { if (!closed) { closed = true; this.headers = null; if (properties != null) { properties.Dispose(); properties = null; } this.messageData.Close(); this.messageData = null; this.quotas = null; } } } public override Message CreateMessage() { lock (ThisLock) { if (closed) { throw FxTrace.Exception.ObjectDisposed(SR.ObjectDisposed("message")); } return new InternalByteStreamMessage(this.messageData, this.headers, this.properties, this.quotas, this.moveBodyReaderToContent); } } } } } }