In case you are not following my Indigo REST/POX series, I quote one paragraph from today's Part 7 that is well worth to be quoted out of context. It talks about (SOAP-) messages and the misconception that a message is a small thing:
There’s no specification that says that you cannot stick 500 Terabyte or 500 Exabyte worth of data (think 365x24 live 1080i video streams) into a single message. As long as you have some reason to believe that the sender will eventually, in 20 years from now, give you “</soap:Body></soap:Envelope>” to terminate the message, the message can be assumed to be well-formed and complete.
The WCF transports that support "streamed" transfer-mode (all except MSMQ) all consider messages to be monsters like that when streaming is enabled. I have a bit more on the streaming mode in today's part of the series.
Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8, Part 9
Where are we?
· In Parts 1 and 2, I explained contracts in the REST/POX context and the dispatch mechanisms that we need to enable Indigo to accept and handle REST/POX requests. With that I introduced a metadata extension, the [HttpMethod] attribute that can be used to mark up operations on an Indigo contract with HTTP methods and a URI suffixes that we can dispatch on. I also showed how we can employ a parameter inspector to extract URI-embedded arguments and flow them to the operation in a message property.
· In Parts 3 and 4, I showed how we use the [HttpMethodOperationSelector] attribute to replace Indigo’s default address filtering and operation selection mechanisms, basically the entire message dispatch mechanism, with our own variants. The SuffixFilter is used to find the appropriate endpoint for an incoming request and the HttpMethodOperationSelectorBehavior find the operation (method) on that endpoint which shall receive the incoming request message.
· In Parts 5 and 6, you saw how the PoxEncoder puts outbound envelope-less POX documents onto the wire in its WriteMessage methods and accepts incoming non-SOAP XML requests through its ReadMessage methods and wraps them with an in-memory envelope (“message”) for further processing. I also showed the PoxBase64XmlStreamReader, which is an XML Infoset wrapper for arbitrary binary streams that interacts with the PoxEncoder to allow smuggling any sort of raw binary content through the Indigo infrastructure and onto the wire.
We’re pretty far along already. We’ve got the dispatch mechanisms, we know how to hook the dispatch metadata into the services, we’ve got the wire-encoding – we have most of the core pieces together. In fact, the last two key classes we’re missing (configuration hooks aside) are two specialized message classes that we need to handle incoming requests. In Part 6, you could see that the two ReadMessage overloads of the PoxEncoder delegate all work to the PoxBufferedMessage for the “buffered” transfer-mode overload and to PoxStreamedMessage for the “streamed” transfer mode overload.
ReadMessage is called on an encoder whenever a transport has received a complete message buffer (buffered mode) or has accepted and opened a network stream (streamed mode).
Using streamed mode means very concretely that Indigo will start handling the message even though the message might not have completely arrived. A transport in streaming mode will only do as much as it needs to do in order to deal with the transport-level framing protocol. I use “framing protocol” as a general term for what is done at the transport level to know what the nature of the payload is and where the payload starts and ends. For HTTP, the HTTP transport figures out whether an incoming request is indeed an HTTP request, will read/parse the HTTP headers, and will then layer a stream over the request’s content, irrespective of whether the transfer of that byte sequence has already been completed. This stream is immediately handed off to the rest of the Indigo infrastructure and the transport has done its work by doing so.
Pulling the remaining bytes from that stream is someone else’s responsibility in streamed mode. Whenever a piece of the infrastructure pulls data directly or indirectly from the stream and the data chunk requested is still in transfer, the stream will block and wait until the data is there. The transport’s handling of the framing protocol will typically also take care of chunking and thus make a chunked stream appear to be continuous. When I say “indirect pull” I mean that it may very well be an XmlDictionaryReader layered over an XmlReader layered over the incoming network stream.
The streaming mode is of particular interest for very large messages that may, in an extreme case, be virtually limitless in size. There’s no specification that says that you cannot stick 500 Terabyte or 500 Exabyte worth of data (think 365x24 live 1080i video streams) into a single message. As long as you have some reason to believe that the sender will eventually, in 20 years from now, give you “</soap:Body></soap:Envelope>” to terminate the message, the message can be assumed to be well-formed and complete.
No matter whether you use buffered or streamed mode, the configured encoder’s ReadMessage method is the first place where the read data chunk or the stream goes and that delegates, as shown to our two message classes. So let’s look at them.
We’ll primarily look at the PoxBufferedMessage, which is constructed over the read message buffer in the PoxEncoder like this:
|
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager) { return new PoxBufferedMessage(buffer, bufferManager); } |
The class PoxBufferedMessage is derived from the abstract System.ServiceModel.Message class and implements the base-class’s abstract properties Headers, Properties, and Version and overrides the OnClose(), OnGetReaderAtBodyContents(), and OnWriteBodyContents() virtual methods. Internally, Indigo has several such Message implementations that are each customized for certain scenarios. Implementing own variants of Message is simply another extensibility mechanism that Indigo gives us.
|
Using System; using System.Collections.Generic; using System.Text; using System.ServiceModel; using System.IO; using System.Xml; using System.Runtime.CompilerServices; using System.ServiceModel.Channels;
namespace newtelligence.ServiceModelExtensions { /// <summary> /// This class is one of the message classes used by the <see cref="T:PoxEncoder"/> /// It serves to wrap an unencapsulated data buffer with a message structure. /// The data buffer becomes the body content of the message. /// </summary> public class PoxBufferedMessage : Message, IPoxRawBodyMessage { MessageHeaders headers = new MessageHeaders(MessageVersion.Soap11Addressing1); MessageProperties properties = new MessageProperties(); byte[] buffer; int bufferSize; BufferManager bufferManager; Stream body; /// <summary> /// Initializes a new instance of the <see cref="T:PoxBufferedMessage"/> class. /// </summary> /// <param name="buffer">The buffer.</param> public PoxBufferedMessage(byte[] buffer) { bufferManager = null; buffer = buffer; bufferSize = buffer.Length; }
/// <summary> /// Initializes a new instance of the <see cref="T:PoxBufferedMessage"/> class. /// </summary> /// <param name="buffer">The buffer.</param> /// <param name="bufferManager">The buffer manager.</param> public PoxBufferedMessage(ArraySegment<byte> buffer, BufferManager bufferManager) { bufferManager = bufferManager; bufferSize = buffer.Count; buffer = bufferManager.TakeBuffer( bufferSize); Array.Copy(buffer.Array, buffer.Offset, buffer, 0, bufferSize); } |
We can construct instances of the class over a raw byte array or an “array segment” layered over such an array. Array segments are preferred over raw arrays, because their use eases memory management. You can keep a pool of buffers with a common size, even though the actual content is shorter than the buffer size and probably even offset from the lower buffer boundary. If we get a raw byte array we simply adopt it, but if we get an array segment alongside a reference to a buffer manager we take a new buffer from the buffer manager and copy the array segment to that acquired buffer.
|
/// <summary> /// Called when the message is being closed. /// </summary> protected override void OnClose() { base.OnClose(); if ( bufferManager != null) { bufferManager.ReturnBuffer( buffer); } } |
When we close the message and we have acquired it using the buffer manager (which is signaled by the presence of the reference) we duly return it once the message is being closed (or disposed or finalized).
The next two methods are an implementation of the IPoxRawBodyMessage interface that is, you guessed it, defined in my extensions. If the handler method wants to get straight at the raw body content knowing that it doesn’t expect XML, it can shortcut by the whole XmlReader and XML serialization story by asking for the BodyContentType and pull out the raw body data as a stream layered over the buffer:
|
/// <summary> /// Gets the raw body stream. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.Synchronized)] public Stream GetRawBodyStream() { if ( body == null) { body = new MemoryStream( buffer,0, bufferSize,false,true); } return body; }
/// <summary> /// Gets the content type of the raw message body based on the Content-Type HTTP header /// contained in the HttpRequestMessageProperty or HttpResponseMessageProperty of this /// message. The value is null if the type is unknown. /// </summary> public string BodyContentType { get { if (Properties.ContainsKey(HttpRequestMessageProperty.Name)) { return ((HttpRequestMessageProperty)Properties[HttpRequestMessageProperty.Name]).Headers["Content-Type"]; } if (Properties.ContainsKey(HttpResponseMessageProperty.Name)) { return ((HttpResponseMessageProperty)Properties[HttpResponseMessageProperty.Name]).Headers["Content-Type"]; } return null; } } |
There is a bit of caution required using this mechanism, though. Because the message State (Created, Written, Read, Copied, Closed) is controlled by the base-class and cannot be set by derived classes, the message should be considered to be in the State==MessageState.Read after calling the GetRawBodyStream() method. That doesn’t seem to be necessary because we have a buffer here, but for the streamed variant that’s a must. And for the sake of consistency we introduce this constraint here.
The BodyContentType property implementation seems, admittedly, a bit strange at first sight. Even though you won’t see the message properties being populated anywhere inside this class, we’re asking for them and base the content-type detection on their values. That only makes sense when we consider the way messages are being populated by Indigo. As I explained, the first thing that gets called once the transport has a raw data chunk or stream in its hands that it believes to be a message, it invokes the encoder. For incoming requests/messages, the encoder is really serving as the message factory constructing Message-derived instances over raw data. Once the encoder has constructed the message in one of the ReadMessage overloads, the message is returned to the transport. If the transport wants, it can then (and the HTTP transport does) stick properties into that newly created message and then hand it off to the rest of the channel infrastructure for processing and dispatching. Because these extensions are built for REST/POX and therefore have HTTP affinity, that’s precisely what we assume to be happening for the BodyContentType property and the CreateBodyReader() method below. As I already explained in Part 1, the HTTP transport will always add a HttpRequestMessageProperty to the message and that’s consequently from which we can grab the content-type of the incoming request data.
|
private XmlDictionaryReader CreateBodyReader() { XmlDictionaryReader reader = null;
/* * Check whether the message properties indicate that this is a raw binary message. * In that case, we'll wrap the body with a PoxBase64XmlStreamReader */ bool hasPoxEncoderProperty = Properties.ContainsKey(PoxEncoderMessageProperty.Name); if (!(hasPoxEncoderProperty && ((PoxEncoderMessageProperty)Properties[PoxEncoderMessageProperty.Name]).RawBinary)) { string contentType = null;
/* * Check for whether either the HttpRequestMessageProperty or the HttpResponseMessageProperty * are present. If so, extract the HTTP Content-Type header. Otherwise the content-type is * assumed to be text/xml ("POX") */ bool hasRequestProperty = Properties.ContainsKey(HttpRequestMessageProperty.Name); bool hasResponseProperty = Properties.ContainsKey(HttpResponseMessageProperty.Name); if (hasResponseProperty) { HttpResponseMessageProperty responseProperty = Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty; contentType = responseProperty.Headers["Content-Type"]; } else if (hasRequestProperty) { HttpRequestMessageProperty requestProperty = Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; contentType = requestProperty.Headers["Content-Type"]; }
if (contentType == null) { contentType = "text/xml"; }
/* * If the content type is text/xml (POX) we will create a plain XmlTextReader for the body. */ if (contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase)) { // do we only have a UTF byte-order mark? if (_bufferSize <= 4) { // create a new reader over a fake infoset and place it on the EndElement reader = XmlDictionaryReader.CreateDictionaryReader( new XmlTextReader(new StringReader("<no-data></no-data>"))); reader.Read(); reader.Read(); } else { reader = XmlDictionaryReader.CreateDictionaryReader(new XmlTextReader(GetRawBodyStream())); }
} } /* * If the content wasn't identified to be POX, we'll wrap it as binary. */ if (reader == null) { reader = XmlDictionaryReader.CreateDictionaryReader(new PoxBase64XmlStreamReader(GetRawBodyStream())); } return reader; } |
The private CreateBodyReader() method that constructs XML readers for the both, the OnGetBodyReaderAtBodyContents() and the OnWriteBodyContents() overrides shown below, uses the same strategy to figure out the content-type of the message and therefore to guess what’s hidden inside the byte-array (or array segment) the message was constructed over. To make the message class useful for the request and response direction, we’ll distinguish there two separate cases here:
· If the message is a response, the handling method in the user code might have indicated that it wants the encoder to serialize the message onto the wire in “raw binary” mode. The indicator for that is the presence of the PoxEncoderMessageProperty having the RawBinary property set to true. If that is the case, the reader we return is always our PoxBase64XmlStreamReader. The property cannot occur in request messages because the Indigo transports simply don’t know about it.
· If the message is a request or a response with the mentioned property missing, we will try figuring out the message’s content-type using the described strategy of using the HTTP transport’s message properties. If we can’t figure out a content-type for a response (it’s optional for the responding handler code to supply it), we will assume that the content-type is “text/xml”. If the message is a request we can rely of getting a content-type as long as the underlying transport is Indigo’s HTTP transport implementation. If the content-type is indeed “text/xml” we construct an XmlTextReader over the raw data and return it. If the content-type is anything else, we use our PoxBase64XmlStreamReader wrapper, because we have to assume that the encapsulated data we’re dealing with is not XML.
The OnGetBodyReaderAtBodyContents() and the OnWriteBodyContents() overrides are consequently very simple:
|
/// <summary> /// Called when the client requests a reader for the body contents. /// </summary> /// <returns></returns> protected override XmlDictionaryReader OnGetReaderAtBodyContents() { XmlDictionaryReader reader = CreateBodyReader(); reader.MoveToContent(); return reader; }
/// <summary> /// Called when the client requests to write the body contents. /// </summary> /// <param name="writer">The writer.</param> protected override void OnWriteBodyContents(XmlDictionaryWriter writer) { XmlDictionaryReader reader = CreateBodyReader(); writer.WriteNode(reader, false); } |
What’s left to complete the message implementation are the compulsory overrides of the abstract properties of Message, for which we have backing fields declared at the top of the class:
|
/// <summary> /// Gets the message version. /// </summary> /// <value>The message version.</value> public override MessageVersion Version { get { return MessageVersion.Soap11Addressing1; } }
/// <summary> /// Gets the SOAP headers. /// </summary> /// <value>The headers.</value> public override MessageHeaders Headers { get { return headers; } }
/// <summary> /// Gets the message properties. /// </summary> /// <value>The properties.</value> public override MessageProperties Properties { get { return properties; } } } } |
The PoxStreamedMessage is only different from this class insofar as that it doesn’t have the buffer management. The GetRawBodyStream() method immediately returns the encapsulated stream and the remaining implementation is largely equivalent, if not identical (yes, I should consolidate that into a base class). Therefore I am not pasting that class here as code but rather just append as a downloadable file, alongside the declaration of IPoxRawBodyMessage and the twice mentioned and not yet shown PoxEncoderMessageProperty class.
With this, we’ve got all the moving pieces we need to build what’s essentially becoming an Indigo-based, message-oriented web-server infrastructure with a REST-oriented programming model. What’s missing is how we get our encoder configured into a binding so that we can put it all together and run it.
Configuration is next; wait for part 8.
Download: PoxEncoderMessageProperty.zip Download: PoxStreamedMessage.zip Download: IPoxRawBodyMessage.zip
[2006-01-13: Updated PoxBufferedMessage code to deal with entity bodies that only consist of a UTF BOM]
Sabine and I were just browsing Channel 9 using TVTonic on our Media Center PC that's been recently connected to this christmas gift. We watched a few snippets of Microsoft PMs and other folks presenting their latest stuff and then that. Sabine (she's a nurse) said "...oh, that's like Hospital TV".
I can't help but admit that she does indeed have more than just one point in saying that.
I recently needed a TCP port-forwarder that sits on a socket connection and
monitors it. My concrete use-case is that I need to front the backend-server of
my TV application with such a port forwarder in order to create live-TV
streaming sessions as soon as a client requests them and also tears them down shortly
after the client disconnects so that the session doesn’t need to time out
and blocks the tuner until then. The backend also requires that I do a
periodical “keep-alive” ping every 30-40 seconds, which isn’t
a very practical requirement for some of my client scenarios. Therefore, I
needed, generally speaking, something that would sit between the client and the
backend server, monitors the data stream and would let me run some code (set up
the live session and start the keep-alive timer) when I get a new client connection
and just before I connect through to the target and which would let me run some
code (shut down the session and stop the keep-alive) as soon as the connection
is torn down.
Since, I didn’t find one (or was too blind or too lazy, you know how
that goes), I wrote one. It’s a fully asynchronous TcpListener/TcpClient
based implementation, it’s fast and stable enough for my purposes and it
might or might not be for yours, it has a bit of tolerance for targets that don’t
accept a connection on the first try, and you can hook up events to “before
target connect” and “after target disconnect”. Since all the
bytes fly by, you can instrument the thing further or monitor the stream as you
like.
The code is pretty straightforward, even though the asynchronous calls/callbacks
admittedly make the execution paths in the implementation a bit challenging to
follow, and should not require much further explanation. You construct an
instance of TcpPortForwader passing the local port and the target port
and host to forward to, call Start() and the listener starts listening. Stop()
stops the listener. You can call Start() from any thread; the listener
will implicitly use thread-pool threads to run on its own. Hook up the events
and they are being raised. Simple enough. Download below.
Download: TcpPortForwarder.zip
Part 1, Part 2, Part 3, Part 4, Part 5
I threw a lot of unexplained code at you in Part 5 and that wasn’t really fair.
The PoxEncoder class is a replacement for Indigo’s default TextMessageEncoder class that’s used by the HTTP transport unless you explicitly configure something different. Indigo comes with three built-in encoders, namely:
· The TextMessageEncoder serializes Indigo’s internal Message into SOAP 1.1 or SOAP 1.2 envelopes using (applies only to the latter) the desired character encodings (UTF-8, UTF-16, etc.) and of course it also deserializes incoming SOAP envelopes into the Indigo representation.
· The MtomMessageEncoder serializes messages into SOAP 1.2 messages as specified by the MTOM specification, which allows for a much more compact transmission of binary-heavy SOAP envelopes than if you were simply using base64Binary encoded element data. MTOM is a good choice whenever the size of binary content in a SOAP envelope far exceeds the size of the rest of the data. Your mileage may vary, so that’s a thing to measure carefully unless it’s blatantly obvious such as in the case of writing a service for a digital imaging library.
· The BinaryMessageEncoder serializes messages into SOAP 1.2 envelopes, but does so in a very compact binary format that preserves the XML information set, but is not at all like XML text. The gist of the binary encoding is the assumption that if both communicating parties are implemented with Indigo and share the same contract, the metadata existing at both ends reduces the hints that need to go on the wire. In other words: The binary encoding doesn’t need to throw all these lengthy XML tag names and namespace names explicitly onto the wire, but can refer to them by pointing to a dictionary that’s identically constructed on both ends. The binary encoding in Indigo is a bit like the modern-day, loosely coupled grand-child of NDR and “midl.exe /Oicf” if you like. What’s important to note about this encoding is that its primary design goal is performance and interoperability is in fact a non-goal. The BinaryMessageEncoder assumes Indigo endpoints. If you don’t like that, you can always use the text encoding, which is designed for interoperability.
Our PoxEncoder here differs from all three Indigo encoders in that it does specifically not serialize SOAP messages, but rather just the body contents of a Message.
In order for you to understand what’s happening here, I’ll pick the most relevant methods and explain them in detail. We will start with the Initialize() method that is invoked by all three constructor overloads:
|
/// <summary> /// Initializes common properties of the encoder. /// </summary> private void Initialize() { if (this.MessageVersion.Envelope == EnvelopeVersion.Soap12) { // set the aprorpiate media type for SOAP 1.2 this. mediaType = "application/soap+xml"; } else if (this.MessageVersion.Envelope == EnvelopeVersion.Soap11) { // set the appropriate media type for SOAP 1.1 this. mediaType = "text/xml"; } // compose the content type from charset and media type this. contentType = string.Format(CultureInfo.InvariantCulture, "{0}; charset={1}", mediaType, textEncoding.WebName); } |
It is required for each MessageEncoder-derived class to implement the abstract properties MediaType, ContentType, and MessageVersion, and therefore we have to initialize the backing fields for these properties properly and return meaningful values even though the PoxEncoder is exactly the “anti-SOAP” encoder. The message version specified in the encoder is relevant for Indigo higher up on the stack, because it needs to know what rules and constraints apply to Message instances as they are constructed and processed. The content type and media types are required by the transports so that they know what content and/or media type to specify as metadata in their transport frame (eg. the Content-Type header in HTTP). If we initialize the encoder with the Soap12 message version, it will consequently report the application/soap+xml media type, even though the encoder doesn’t ever write such envelopes to the wire. You might consider that a bug in the PoxEncoder and you might be right, but it doesn’t really matter. Because any methods can return all sorts of payloads, we will override the content-type on the message-level so that this information has really no effect. I do need to clean this up a little. Later.
Now let’s look at the parts that actually do the work. I will start with the two WriteMessage overloads.
The first overload’s signature is public override ArraySegment<byte> WriteMessage(Message msg, int maxMessageSize, BufferManager bufferManager, int messageOffset) and is invoked by the transport whenever a message must be wire-encoded and the output transfer mode is set to TransferMode.Buffered or TransferMode.StreamedRequest (which implies a buffered response). The second overload’s signature is public override void WriteMessage(Message msg, System.IO.Stream stream) and is invoked by the transport whenever a message must be wire-encoded and the output transfer mode is set of TransferMode.Streamed or TransferMode.StreamedResponse (which implies a streamed response).
The transfer-mode property is configurable on all of the pre-built HTTP bindings and on the <httpTransport> binding element. “Buffered” encoding means that the entire message is encoded at once and written into a buffer, which is then given to the the transport for sending. “Streamed” encoding means that the message is pushed into to a stream, whereby the stream is typically immediately layered directly over the transport. That means that whenever our encoder writes data to that stream, it is immediately pushed to the remote communication partner. The “streamed” mode is the optimal choice for sending very large messages that are, for instance, too big to be reasonably handled as a single memory block. The buffered mode is better (and faster) for compact messages. I’ll dissect the buffered variant first:
|
public override ArraySegment<byte> WriteMessage(Message msg, int maxMessageSize, BufferManager bufferManager, int messageOffset) { if (msg.IsEmpty) { // if the message is empty (no body defined) the result is an empty // byte array. byte[] buffer = bufferManager.TakeBuffer(maxMessageSize); return new ArraySegment<byte>(buffer, 0, 0); } |
If the message is empty (that means: the body is empty), we request a buffer from the buffer manager and return an empty slice of that buffer, because this encoder’s output is “nothing” if the body is empty.The BufferManager is an Indigo helper class that manages a pool of pre-allocated buffers and serves to optimize memory management by avoiding the allocation and the discarding of buffers for every message. And encoder should therefore use the buffer manager argument and use it to obtain the buffers backing the array segment that is to be returned. Once the message has been handled by the transport, the transport will return the buffer into the pool.
|
else { // check RawBinary bit in the message property bool rawBinary = false; if (msg.Properties.ContainsKey(PoxEncoderMessageProperty.Name)) { rawBinary = ((PoxEncoderMessageProperty)msg.Properties[PoxEncoderMessageProperty.Name]).RawBinary; } |
If the message is not empty (we have a body), we check whether there is a PoxEncoderMessageProperty present in the message. This property is a plain CLR class that is part of my extensions and has two significant properties: Name is a static, constant string value used as the key for the message properties collection and RawBinary is a Boolean instance value that contains and indicator for whether the encoder shall encode the data as XML or as raw binary data. The Message property collection is a simple dictionary of objects keyed by strings. The properties allow application-level code to interact with infrastructure-level code in the way illustrated by this property. Whenever I want the encoder to use its “raw binary” mode, I add this property to the message and the encoder can pick up the information.
|
ArraySegment<byte> retval = new ArraySegment<byte>(); byte[] buffer = bufferManager.TakeBuffer(maxMessageSize); if (!rawBinary) { // If we're rendering XML data, we construct a memory stream // over the output buffer, layer an XMLDictionaryWriter on top of it // and have the message write the body content into the buffer as XML. // The buffer is then wrapped into an array segment and returned. MemoryStream stream = new MemoryStream(buffer); XmlWriterSettings settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; settings.Indent = true; settings.Encoding = this. textEncoding; XmlWriter innerWriter = XmlWriter.Create(stream, settings); XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(innerWriter, false); msg.WriteBodyContents(writer); writer.Flush(); retval = new ArraySegment<byte>(buffer, 0, (int)stream.Position); } |
Next we take a buffer from the buffer manager and if we’re not in “raw binary” mode, we’ll construct a memory stream over the buffer, construct an XmlDictionaryWriter over that stream and ask the message to render its “body contents” into the writer and therefore into the memory stream and into the buffer. The “body contents” of a message is what would be the child nodes of the <soap:Body> element, if we were using that (but we don’t). Once the body contents have been written, we flush the writer to make sure that all buffered data is committed into the underlying stream and then construct the return value as an array segment over the buffer with the length of the bytes written to the stream.
|
else { // If we're rendering raw binary data, we grab at most 'buffer.Length' // bytes from the binary content of the base64Binary element (if that // exists) and return the result wrapped into an array segment. XmlDictionaryReader dictReader = msg.GetReaderAtBodyContents(); if (dictReader.NodeType == XmlNodeType.Element && dictReader.LocalName == "base64Binary") { if (dictReader.Read() && dictReader.NodeType == XmlNodeType.Text) { int size = dictReader.ReadContentAsBase64(buffer, 0, buffer.Length); retval = new ArraySegment<byte>(buffer, 0, size); } } } return retval; } } |
If the “raw binary” mode is to be used, we are making a bit of an assumption inside the encoder. The assumption is that the body content consists of a single element named “base64Binary” and that its content is just that: base64 binary encoded content. That is of course the other side of the PoxBase64XmlStreamReader trick I explained in Part 5. For binary data we simply assume here that the body reader is our wrapper class and this is how arbitrary binary data is smuggled through the Indigo infrastructure. The array segment to be returned is constructed by reading the binary data into the buffer and setting the array segment length to the number of bytes we could get from the element content.
The streamed version of WriteMessage is quite different:
|
public override void WriteMessage(Message msg, System.IO.Stream stream) { try { if (!msg.IsEmpty) { // check RawBinary bit in the message property bool rawBinary = false; if (msg.Properties.ContainsKey(PoxEncoderMessageProperty.Name)) { rawBinary = ((PoxEncoderMessageProperty)msg.Properties[PoxEncoderMessageProperty.Name]).RawBinary; } if (!rawBinary) { // If we're rendering XML, we layer an XMLDictionaryWriter over the // output stream and have the message render its body content into // that writer and therefore onto the stream. XmlWriterSettings settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; settings.Indent = true; settings.Encoding = this. textEncoding; XmlWriter innerWriter = XmlWriter.Create(stream, settings); XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(innerWriter, false); msg.WriteBodyContents(writer); writer.Flush(); } |
The first significant difference is that if we’re using streams, we will simply ignore empty messages and do nothing with them. In streaming mode, the transport will do any setup work required for sending a message before invoking the encoder and ready the output network stream so that the encoder can write to it. When the encoder returns, the transport considers the write action done. So if we don’t write to the output stream, there’s no payload data hitting the wire and that happens to be what we want.
If we have data and we’re not in “raw binary” mode, the encoder will construct an XmlDictionaryWriter over the supplied stream and have the message write its body contents to it. That’s all.
|
Else { // If we're rendering raw binary data, we grab chunks of at most 1MByte // from the 'base64Binary' content element (if that exists) and write them // out as binary data to the output stream. Chunking is done, because we // have to assume that the body content is arbitrarily large. To optimize the // behavior for large streams, we read and write concurrently and swap buffers. XmlDictionaryReader dictReader = msg.GetReaderAtBodyContents(); if (dictReader.NodeType == XmlNodeType.Element && dictReader.LocalName == "base64Binary") { if (dictReader.Read() && dictReader.NodeType == XmlNodeType.Text) { byte[] buffer1 = new byte[1024*1024], buffer2 = new byte[1024*1024]; byte[] readBuffer = buffer1, writeBuffer = buffer2; int bytesRead = 0; // read the first chunk into the read buffer bytesRead = dictReader.ReadContentAsBase64(readBuffer, 0, readBuffer.Length); do { // the abort condition for the loop is that we can't read // any more bytes from the input because the base64Binary element is // exhausted. if (bytesRead > 0 ) { // make the last read buffer the write buffer writeBuffer = readBuffer; // write the write buffer to the output stream asynchronously IAsyncResult result = stream.BeginWrite(writeBuffer, 0, bytesRead,null,null); // swap the read buffer readBuffer = (readBuffer == buffer1) ? buffer2 : buffer1; // read a new chunk into the 'other' buffer synchronously bytesRead = dictReader.ReadContentAsBase64(readBuffer, 0, readBuffer.Length); // wait for the write operation to complete result.AsyncWaitHandle.WaitOne(); stream.EndWrite(result); } } while (bytesRead > 0); } } } } } catch { // the client may disconnect at any time, so that's an expected exception and absorbed. } } |
In streamed “raw binary” mode things get a bit more complicated. Under these circumstances we assume that the output we are sending is HUGE. The use-case I had in mind when I wrote this is the download of multi-GByte video recordings. Therefore I construct two 1MByte buffers that are used in turns to read a chunk of data from the source body reader (for which we make the same content assumption as for the buffered case: This is believed to be a PoxBase64XmlStreamReader compatible infoset) and asynchronously push the read data into the output stream.
Because it may take a while to get a huge data stream to the other side, a lot of things can happen to the network connection during that time. Therefore the encoder fully expects that the network connection terminates unexpectedly. If that happens, we’ll catch and absorb the network exception and happily return to the caller as if we’re done.
Compared to all the complexity of the WriteMessage overloads, the respective ReadMessage methods look fairly innocent, simple, and similar:
|
/// <summary> /// Reads an incoming array segment containing a message and /// wraps it with a buffered message. The assumption is that the incoming /// data stream is <i>not</i> a SOAP envelope, but rather an unencapsulated /// data item, may it be some raw binary, an XML document or HTML form /// postback data. This method is called if the inbound transfer mode of the /// transport is "buffered". /// </summary> /// <param name="buffer">Buffer to wrap</param> /// <param name="bufferManager">Buffer manager to help with allocating a copy</param> /// <returns>Buffered message</returns> public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager) { return new PoxBufferedMessage(buffer, bufferManager); }
/// <summary> /// Reads an incoming stream containing a message and /// wraps it with a streamed message. The assumption is that the incoming /// data stream is <i>not</i> a SOAP envelope, but rather an unencapsulated /// data item, may it be some raw binary, an XML document or HTML form /// postback data. This method is called if the inbound transfer mode of the /// transport is "streamed". /// </summary> /// <param name="stream">Input stream</param> /// <param name="maxSizeOfHeaders">Maximum size of headers in bytes</param> /// <returns>Stream message</returns> public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders) { return new PoxStreamedMessage(stream, maxSizeOfHeaders); } |
Both variants take the raw incoming data (whatever it is) and hand it to the PoxStreamMessage class or PoxBufferedMessage class that adopt the buffer or stream as their body content, respectively. I’ll explain those in Part 7.
Happy New Year!
 My little “TV
anywhere“ project makes a bit more sense now, does it? Because Sabine and
I will more than likely find ourselves somewhere in the Seattle area by
mid-2006 (I’ll be telecommuting and “long-haul shuttling” for
a while) and we are both big time German football (as in “soccer”) fans,
we just need to fix a problem.
And I am doing it just because I can, of course. The whole Indigo REST/POX
series here on the blog does nothing more than describe all the WCF extensions I
wrote to build my personal TV server. The first screenshot here on the right shows
the current state of things as it shows up in Media Player. The UI is a Windows
Media Player hosted AJAX app with EPG data in tooltips as I hover over the
channel icons, I can schedule recordings with a single mouse click and I have
access to all my recordings on a separate channel or just on the channel that’s
currently being watched. But that’s just the “Clemens on the road”
version of this.
The other front-end is the “far away at home”
version of it and that looks like the screen shots on the left. Meanwhile I
have an AJAX front-end that’s built for Windows XP Media Center Edition (MCE)
and snaps right into the MCE experience. My dad will get the PC that sits here
in my living room and host it at his house behind an upgraded DSL line and my (to
be bought) MCE machine over in Seattle will connect to it and give me remote (control)
access to the 35 cable channels over here. That’s the plan.
The backend is and remains Beyond TV, because it’s a very flexible,
programmable and – most importantly – streaming enabled TV engine. It’s
an unlikely couple, but I decided that Beyond TV and MCE should have a wedding
on my boxes. Works well.
The immediate question is of course still “Looks cool, when can I have
it?” The issue is that this is a lot more of a tricky story than DasBlog,
because I am depending on an existing backend software with its own, a bit
limiting EULA, I am relying on a foundation that isn’t even released
(WCF) and I am a lot more worried about the support situation, because setting
this up correctly with Beyond TV alongside MCE and my service is … ummm…
only something for folks who have no fear of the gates of Mordor and beyond. We’ll
see what I can do…
Part 1, Part 2, Part 3, Part 4
POX means “plain old XML” and I’ve also heard a definition saying that “POX is REST without the dogma”, but that’s not really correct. POX is not really well defined, but it’s clear that the “plain” is the focus and that means typically that folks who talk about “POX web services” explicitly mean that those services don’t use SOAP. You could see POX as an antonym for SOAP.
The design of Indigo (WCF) assumes that all messages that go onto the wire and come from the wire have a shape that is aligned with SOAP. That means that they have a “body” containing the user-defined message payload and a “header” section that contains the out-of-band metadata that helps getting the message from one place to the next, possibly through a chain of intermediaries. Most of the Indigo binding elements and their implementations also assume that those metadata elements (headers) conform to their respective WS-* standard that they are dealing with.
However, Indigo isn’t hard-wired to a specific envelope format. The default “encoders” that are responsible for turning a message into data stream (or a data package) that a transport can throw down a TCP socket or into a message queue (or whatever else) and which are likewise responsible for picking up the data from the wire to turn them into Message objects have two envelope formats baked in: SOAP 1.1 and SOAP 1.2. But that doesn’t mean that you have to use those. If your envelope format were different (there seem to be thousands, I’ll name AdsML [spec] as an example) and that’s what you want to use on the wire, you can assemble a binding that will compose an Indigo transport with your encoder. Moving away from SOAP means, though, that you can’t use the standard implementations of capabilities such as message-level security, reliable delivery, and transaction flow, because all of these are built on the assumption that you are exchanging WS-* headers with the other party and all of these specs depend on the SOAP information model. But if there are comparable specifications that come with your envelope format you can of course write Indigo extensions that you can configure into a binding just like you can compose the default binding elements. It’d be a lot of work to do that, but you’d still benefit greatly from the Indigo architecture per-se.
When we want to use a REST/POX model, our envelope format is quite simple: We don’t really have an envelope.
The idea of POX is that there’s only payload and that out-of-band metadata is unnecessary fluff. The idea of REST is that there is already and appropriate place for out-of-band metadata and that’s the HTTP headers.
In order to make REST/POX work, we therefore need to replace the Indigo default encoder with an encoder that fulfills these requirements:
1. Extract the message body XML content of any outbound message and format it for the wire as-is and without a SOAP envelope around it and
2. Accept an arbitrary inbound XML data and wrap it into a Message-derived class so that Indigo can handle it.
Since the use-case in whose context I’ve developed these extensions is a bit more far reaching than POX, but I indeed want to support RESTful access to any data including multi-GByte unencapsulated MPEG recordings I make on my Media PC, I’ve broadened these two requirements a bit and left out the “XML” constraint:
1. Extract the message body XML content of any outbound message and format it for the wire as-is and without a SOAP envelope around it and
2. Accept an arbitrary inbound XML data and wrap it into a Message-derived class so that Indigo can handle it.
XML aka POX is an interesting content-type to throw around, but it’s by no means the only one and therefore let’s not restrict ourselves too much here. Any content is good.
But then again, Indigo is assuming that all messages flowing through its channels contain XML payloads and therefore we’ve got a bit of a nut to crack when we want to use Indigo for arbitrary, non-XML payloads of arbitrary size. Luckily, XML is just an illusion.
The Indigo Message holds the message body content inside an XmlDictionaryReader (which is an optimized derivation of the well-known XmlReader). To construct a message, you can walk up to the static Message.CreateMessage(string action, XmlDictionaryReader reader) factory method and pass the readily formatted body content as a reader object and the message will happily adopt it. But can we use the XmlReader to smuggle arbitrary binary content into the message so that our own encoder can later unwrap it and put it onto the wire in whatever raw binary format we like? Sure we can! The class below may look a bit like an evil hack, but it’s a perfectly legal construct:
|
using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.IO; using System.Xml.Schema;
namespace newtelligence.ServiceModelExtensions { public class PoxBase64XmlStreamReader : XmlTextReader { | |