|
|
Browse by Tags
All Tags » Channel Authors » Indigo (RSS)
-
The final pieces needed for the ROT 128 sample are a stream upgrade initiator and a stream upgrade acceptor. The initiator starts the upgrade process by providing an upgrade type string from GetNextUpgrade. I've coded this so that the initiator and acceptor share a type string that is stored as a static member back on the binding element . You can produce or store your upgrade type string however you want. This is an opaque string to the runtime. using System; using System.IO; using System.ServiceModel; using System.ServiceModel.Channels; namespace Microsoft.ServiceModel.Samples { class InitiateAsyncResult : TypedCompletedAsyncResult<Stream> { internal InitiateAsyncResult(Stream stream, AsyncCallback callback, object state) : base (stream, callback, state) { } } class ROT128StreamUpgradeInitiator : StreamUpgradeInitiator { ROT128StreamUpgradeProvider provider; string nextUpgrade = ROT128StreamUpgradeBindingElement.ROT128UpgradeType; internal ROT128StreamUpgradeInitiator(ROT128StreamUpgradeProvider provider, EndpointAddress remoteAddress, Uri via) : base () { this .provider = provider; } public override IAsyncResult BeginInitiateUpgrade(Stream stream, AsyncCallback callback, object state) { return new InitiateAsyncResult( new ROT128Stream(stream), callback, state); } public override Stream EndInitiateUpgrade(IAsyncResult result) { return ((InitiateAsyncResult)result).Data; } public override string GetNextUpgrade() { string result = nextUpgrade; nextUpgrade = null ; return result; } public override Stream InitiateUpgrade(Stream stream) { return new ROT128Stream(stream); } } } Each time that GetNextUpgrade is called, you're expected to provide a different upgrade type string if you support multiple upgrades. Once you're out of upgrade types to suggest, GetNextUpgrade should return null forever afterwards. This sample only supports a single upgrade type. At some point in the future, after the upgrade is accepted, you'll get a call on InitiateUpgrade to actually perform the Stream transformation. There's no type string given to InitiateUpgrade so, implicitly, calling InitiateUpgrade means to perform the upgrade for the last upgrade type that you passed out of GetNextUpgrade. To get to the point where you're transforming Streams, you first need to make it through the upgrade acceptor. The upgrade acceptor takes an upgrade type string argument to the CanUpgrade method and returns whether it recognizes this upgrade type. If CanUpgrade returns false, then the Read More...
|
-
Last time, we built the binding element for the stream upgrade sample . The job of the binding element was to stash itself away in the binding context so that the transport could later pull out the stream upgrade and build the provider. This time we'll look at the implementation of the stream upgrade provider. The stream upgrade provider needs to do three things: Build the upgrade initiator for the client side of the connection. The initiator gets the remote address and via of the connection so that we can differentiate behavior based on where the connection is actually going. Build the upgrade acceptor for the server side of the connection. The acceptor and initiator pieces are the first time we have asymmetry in this simple example. Fundamentally, the initiator and acceptor use a request-reply exchange regardless of the shape of the contract or connection. This means that you're always going to get asymmetry at least at this level. Run any part of the protocol that happens independently from making the individual connections. These actions go in the Open, Close, and Abort methods of the provider. I've given all of these empty implementations because this sample doesn't need to do anything here. If you had resources allocated at pre-connect time, such as a socket connection, you would do the allocation and deallocation in these methods. using System; using System.ServiceModel; using System.ServiceModel.Channels; namespace Microsoft.ServiceModel.Samples { class OpenAsyncResult : CompletedAsyncResult { internal OpenAsyncResult(AsyncCallback callback, object state) : base (callback, state) { } } class CloseAsyncResult : CompletedAsyncResult { internal CloseAsyncResult(AsyncCallback callback, object state) : base (callback, state) { } } class ROT128StreamUpgradeProvider : StreamUpgradeProvider { public ROT128StreamUpgradeProvider(ROT128StreamUpgradeBindingElement element, BindingContext context) : base (context.Binding) { } public override StreamUpgradeAcceptor CreateUpgradeAcceptor() { return new ROT128StreamUpgradeAcceptor( this ); } public override StreamUpgradeInitiator CreateUpgradeInitiator(EndpointAddress remoteAddress, Uri via) { return new ROT128StreamUpgradeInitiator( this , remoteAddress, via); } protected override void OnAbort() { } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return new CloseAsyncResult(callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback Read More...
|
-
Building a stream upgrade for ROT 128 starts with creating a binding element to put in the channel stack. This binding element extends the special StreamUpgradeBindingElement base class , which functions very similarly to the specialized binding element base classes for transports and message encoders. We then need to override the channel factory and listener build processes because the stream upgrade binding element does not actually generate a channel. Stream upgrades are handled internally by the transport if it supports them. using System; using System.ServiceModel.Channels; namespace Microsoft.ServiceModel.Samples { public class ROT128StreamUpgradeBindingElement : StreamUpgradeBindingElement { internal static string ROT128UpgradeType = "application/rot128" ; public ROT128StreamUpgradeBindingElement() : base () { } protected ROT128StreamUpgradeBindingElement(ROT128StreamUpgradeBindingElement copyFrom) : base (copyFrom) { } public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.BuildInnerChannelFactory<TChannel>(); } public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.BuildInnerChannelListener<TChannel>(); } public override StreamUpgradeProvider BuildClientStreamUpgradeProvider(BindingContext context) { return new ROT128StreamUpgradeProvider( this , context); } public override StreamUpgradeProvider BuildServerStreamUpgradeProvider(BindingContext context) { return new ROT128StreamUpgradeProvider( this , context); } public override bool CanBuildChannelFactory<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.CanBuildInnerChannelFactory<TChannel>(); } public override bool CanBuildChannelListener<TChannel>(BindingContext context) { context.BindingParameters.Add( this ); return context.CanBuildInnerChannelListener<TChannel>(); } public override BindingElement Clone() { return new ROT128StreamUpgradeBindingElement( this ); } public override T GetProperty<T>(BindingContext context) { return context.GetInnerProperty<T>(); } } } The only other important task when writing a stream upgrade binding element is to build your stream upgrade provider when asked by the transport. ROT 128 works the same on both the client and server so I've made those methods do exactly Read More...
|
-
The mission for the next five days is to build and demonstrate an implementation of a stream upgrade. For review, a stream upgrade is a component that plugs into the transport and rewrites the byte stream as it goes on and off of the network. Stream upgrades are stackable and composable. You add stream upgrades by including stream upgrade binding elements in the desired order in the binding. I went over the basics of stream upgrades a few weeks ago. Here are the articles in that series: Stream Upgrades, Part 1 Stream Upgrades, Part 2 Stream Upgrades, Part 3 The example I've picked out is a stream upgrade that applies ROT 128 (ROTate by 128). ROT 13 is a famous example of a Caesar cipher that occludes messages by replacing letters in the English alphabet with the letter that is 13 places higher. When you go past 'Z', you wrap back around to 'A'. Since there are 26 letters, applying ROT 13 twice returns a letter back to where it started. ROT 128 is the equivalent for a single byte value. Here is the stream class that I want to have my messages run through. It includes some debugging so that we can see the messages as they go in and out. using System; using System.IO; namespace Microsoft.ServiceModel.Samples { class ROT128Stream : Stream { Stream stream; public ROT128Stream(Stream stream) { this .stream = stream; } public override bool CanRead { get { return this .stream.CanRead; } } public override bool CanSeek { get { return this .stream.CanSeek; } } public override bool CanWrite { get { return this .stream.CanWrite; } } protected override void Dispose( bool disposing) { if (disposing) { this .stream.Dispose(); } else { this .stream.Close(); } base .Dispose(disposing); } public override void Flush() { this .stream.Flush(); } public override long Length { get { return this .stream.Length; } } public override long Position { get { return this .stream.Position; } set { this .stream.Position = value ; } } static void DumpBuffer( byte [] buffer, int offset, int count) { int pos = 0; while (pos < count) { int lineCount = count - pos; if (lineCount > 15) { lineCount = 15; } for ( int linePos = 0; linePos < lineCount; linePos++) { Console.Write( " {0:X2}" , buffer[offset + pos + linePos]); } for ( int linePos = 15 - lineCount; linePos >= 0; linePos--) { Console.Write( " " ); } for ( int linePos = 0; linePos < lineCount; linePos++) { byte item = buffer[offset + pos + linePos]; if (item < 0x20 || item >= 0x80) { Console.Write( "." ); } else { Console.Write(( Read More...
|
-
Today’s the final part of the series on the stream upgrade model ( StreamUpgradeBindingElement and StreamUpgradeProvider were covered previously). I’ve got a sample of a stream upgrade to show next week but I figured that everyone would like a little break from the topic before jumping into that. The rest of this week has topics about transport security. When we left off with the StreamUpgradeProvider, there were two methods on that class for creating an initiator and an acceptor. The initiator and acceptor are paired components for negotiating the upgrade and transforming the data stream. Let’s start by looking at the initiator. public abstract class StreamUpgradeInitiator { protected StreamUpgradeInitiator(); public abstract IAsyncResult BeginInitiateUpgrade(Stream stream, AsyncCallback callback, object state); public abstract Stream EndInitiateUpgrade(IAsyncResult result); public abstract string GetNextUpgrade(); public abstract Stream InitiateUpgrade(Stream stream); } Every time GetNextUpgrade is called, it returns an opaque string that specifies an upgrade type that this initiator supports. The upgrade types will be tried successively until the initiator indicates that it has no more types to attempt by returning null. The upgrade type string typically looks something like a MIME type and most initiators will only have one type to try. The SSL stream security and Windows stream security upgrades use application/ssl-tls and application/negotiate, respectively. After an upgrade is accepted, InitiateUpgrade is used in either its synchronous or asynchronous form to actually transform the data stream. The acceptor side looks virtually the same as the initiator side. public abstract class StreamUpgradeAcceptor { protected StreamUpgradeAcceptor(); public virtual Stream AcceptUpgrade(Stream stream); public abstract IAsyncResult BeginAcceptUpgrade(Stream stream, AsyncCallback callback, object state); public abstract bool CanUpgrade( string contentType); public abstract Stream EndAcceptUpgrade(IAsyncResult result); } Each time the initiator proposes an upgrade type, CanUpgrade on the acceptor is called with that type string. The acceptor then says yes or no to performing the upgrade. AcceptUpgrade is the exact equivalent to InitiateUpgrade for transforming the data stream. The stream upgrade model has been stable over the last few months. The most significant change was to add the Via parameter so that the provider can make upgrade decisions based on both the next Read More...
|
-
The stream upgrade model consists of four abstract base classes. We looked at the StreamUpgradeBindingElement yesterday. Since stream upgrades don't have a corresponding channel, the only purpose of the binding element is to notify the transport that this binding is interested in having a potential stream upgrade available. If the transport does not support stream upgrades, then it will ignore this request and the stream upgrade binding element will go unhandled. It is up to the transport to at some point initialize the stream upgrade. The other three parts of the stream upgrade model are the StreamUpgradeProvider, StreamUpgradeInitiator, and StreamUpgradeAcceptor. The binding element is responsible for creating the provider. The provider is then responsible for creating the initiator and acceptor as needed. We'll look at the provider today and look at the initiator and acceptor tomorrow. public abstract class StreamUpgradeProvider : CommunicationObject { protected StreamUpgradeProvider(); protected StreamUpgradeProvider(IDefaultCommunicationTimeouts timeouts); protected override TimeSpan DefaultCloseTimeout { get; } protected override TimeSpan DefaultOpenTimeout { get; } public abstract StreamUpgradeAcceptor CreateUpgradeAcceptor(); public abstract StreamUpgradeInitiator CreateUpgradeInitiator(EndpointAddress remoteAddress, Uri via); } Although stream upgrades aren't channels, the provider shares the Open, Close, and Abort functionality of ICommunicationObject . In Open, the provider takes care of any work that needs to be completed prior to being able to offer an upgrade. For example, a security stream upgrade might use the call to the Open method to allocate credentials or certificates that the local endpoint will present as its identity. The Close and Abort methods allow the provider to clean up any resources allocated in Open or in the created upgrades. Take note of the fact that nothing related to the initiator or acceptor has a timeout parameter. The only timeouts on the provider are on the methods that come from ICommunicationObject. This does not mean that you have an unlimited amount of time to complete operations. Stream upgrades are run as part of a larger bounded operation, such as a receive, and you simply don't know how much of that time slice is remaining. This is an unfortunate part of the model, but it also encourages you to frontload as much work as possible into the startup of the provider. Next time: Stream Upgrades, Part 3 Read More...
|
-
The next several posts are about the stream upgrade model for modifying the byte stream output of a transport. I'll use these posts to cover the basic elements of the stream upgrade model, take a break for a while to talk about some other topics, and later have a sample that shows building a stream upgrade from scratch. A stream upgrade takes the output from message serialization and replaces that byte stream with another byte stream. Stream upgrades are represented in the channel stack by a binding element that doesn't actually create a channel. It is legal to have more than one stream upgrade in the channel stack and active at the same time. These stream upgrades chain sequentially so that the stream output of one is passed as the stream input to the next one. Our primary use of stream upgrades is to provide transport security by replacing the unencrypted stream with an encrypted stream. You could also use stream upgrades for tasks such as compression, character re-encoding, or byte reordering. public abstract class StreamUpgradeBindingElement : BindingElement { protected StreamUpgradeBindingElement(); protected StreamUpgradeBindingElement(StreamUpgradeBindingElement elementToBeCloned); public abstract StreamUpgradeProvider BuildClientStreamUpgradeProvider(BindingContext context); public abstract StreamUpgradeProvider BuildServerStreamUpgradeProvider(BindingContext context); } Building a client or server StreamUpgradeProvider is very similar to constructing a ChannelFactory or ChannelListener for a particular channel. The StreamUpgradeProvider class will be the topic of tomorrow's post. Since there's no channel corresponding to the stream upgrade, the transport is the part of the channel stack that's responsible for actually creating and using the stream upgrade. This means that stream upgrades are only going to be supported by certain transports. Our named pipe and TCP transports, the connection-oriented transports, both support stream upgrades. These transports provide connections that flow undelimited streams of bytes, meaning that they mesh well with the stream upgrade model. Our HTTP transport does not support stream upgrades. Applying a stream upgrade to a connection is optional. The stream upgrades included in the channel stack represent the maximum collection of upgrades that can be used with a connection. The client and server sides have to negotiate and agree on the collection of stream upgrades that will be used for a particular collection. The Read More...
|
-
I occasionally get requests to help people build what turns out to be a variation of our CompositeDuplexBindingElement . Composite duplex is just a channel shape changer that supports both reading and writing, and has independence between the connections on the two sides. The basic binding element for this shape changer really does not contain any deep functionality. If you need to build a similar type of shape changer, you can get most of the way there by doing only four things. Override CanBuildChannelFactory and CanBuildChannelListener to delegate down to the underlying bindings on the factory and listener sides. You'll want to explicitly validate the TChannel shape parameter against the shapes that your composite duplex supports before delegating. Override BuildChannelFactory and BuildChannelListener to delegate down to the underlying bindings on the factory and listener sides. You need some way of distinguishing the addresses of the two sides. If each side has its own transport and protocol scheme, they'll resolve to separate base addresses on your service host and avoid a conflict. If the two sides have the same protocol scheme, then they'll pick up the same base address. The BindingContext has an explicit override mechanism that you can use for the address of the listener side. Override GetProperty to have a meaningful resolution order. GetProperty returns a single value. If you have a different collection of property settings on the listener and factory sides, you need to pick one to be the winner when both want to return a query result. Consider the impact of your shape changing on metadata and security. Underneath of your binding element, the channel stack is going to be forked into two connections. This is generally going to cause problems for anyone that assumes that the endpoint you receive messages from is the same as the endpoint you write messages to. That may break guarantees, such as the ISecurityCapabilities, of underlying channels. You need to explicitly correct the guarantees that are being advertised through GetProperty. Next time: You Must Understand This Read More...
|
-
To finish up the series on one-way HTTP requests , I promised to supply a custom channel that fixes the scenario of using the POX message encoder together with one-way requests. This is primarily a code post since most of the interesting discussion is already taken care of. I'm supplying a binding element, channel factory, and request channel. There isn't a channel listener or reply channel because there's no conflict on the server-side between one-way and POX. It's only the client side that requires this patch. Stick the binding element in your channel stack between the transport and the OneWay channel. CustomBinding binding = new CustomBinding( new OneWayBindingElement(), new ReplyMangler(), new TextMessageEncodingBindingElement(), new HttpTransportBindingElement() ); The channel code is mostly plumbing to make the reply run through the FilterMessage method that I posted last time. I spent less than 15 seconds testing this so it would be unwise to drop the code directly into your production system. It does appear however to do the proper job of swallowing messages that come back from the POX message encoder. class ReplyManglerChannel : ChannelBase, IRequestChannel { IRequestChannel innerChannel; public ReplyManglerChannel(ChannelManagerBase channelManager, IRequestChannel innerChannel) : base (channelManager) { this .innerChannel = innerChannel; } public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state) { return innerChannel.BeginRequest(message, timeout, callback, state); } public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state) { return BeginRequest(message, DefaultSendTimeout, callback, state); } public Message EndRequest(IAsyncResult result) { return FilterMessage(innerChannel.EndRequest(result)); } Message FilterMessage(Message reply) { if (reply == null ) { return null ; } HttpResponseMessageProperty properties = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name]; if (properties != null && properties.StatusCode == HttpStatusCode.Accepted) { return null ; } return reply; } protected override void OnAbort() { innerChannel.Abort(); } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return innerChannel.BeginClose(timeout, callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { return innerChannel.BeginOpen(timeout, callback, Read More...
|
-
It's hard to believe that I introduced the ICommunicationObject state machine six months ago but never got around to talking about the CommunicationState enumeration that actually describes the states. Let's fix that today. I'll specifically talk about our CommunicationObject implementation. public enum CommunicationState { Created = 0, Opening = 1, Opened = 2, Closing = 3, Closed = 4, Faulted = 5, } There are six states in total for a CommunicationObject. Every object starts in the Created state and proceeds in a one-way progression through the Opening, Opened, Closing, and Closed states. Every object that's Opened gets there from having been Opening and every object that's Closed gets there from having been Closing. However, it's possible to jump from any of the previous states to Closing without going through the states in between. You can close an object without having ever opened it. An object that's not in the process of being closed can be faulted to indicate that it is no longer valid. That means that an object that is Created, Opening, and Opened can be faulted, but not one that is Closing, Closed, or has already been faulted before. Once an object is Faulted, the only thing you can do with it is to close it. There are only a small number of methods that actually modify the state. The state is set to Created in the constructor of CommunicationObject. The state is set to Opening when either BeginOpen or Open is called. The state is set to Opened when OnOpened is called. This happens after calling OnOpening and OnOpen but before the Opened event occurs. The state is set to Closing when BeginClose, Close, or Abort is called. The state is set to Closed when OnClosed is called. This happens after calling OnClosing and OnClose but before the Closed event occurs. And finally, the state is set to Faulted when Fault is called. Next time: Using HTTP in System.Net Read More...
|
|
|
|