Building a Custom File Transport, Part 9: Reply Channel

The second part of the server code to fill in is the reply channel. After today, we'll just have one more piece left to go. That means tomorrow, the code for this sample will be done. An eleventh and final chapter of this series will appear next week to talk about some results from actually running the sample.

The reply channel lies in wait for the client to write a request message. Like the request channel, the reply channel in this sample uses a file system watcher to detect when it needs to grab the file. When a file appears in the right location, the channel will suck out the contents of the file and reconstitute it as a SOAP message. This message gets handed off to build a request context, where the actual work of performing the reply takes place.

 using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace FileTransport
{
   partial class FileReplyChannel : FileChannelBase, IReplyChannel
   {
      readonly EndpointAddress localAddress;
      readonly object readLock;

      public FileReplyChannel(BufferManager bufferManager, MessageEncoderFactory encoderFactory, EndpointAddress address,
         FileReplyChannelListener parent)
         : base(bufferManager, encoderFactory, address, parent, parent.Streamed, parent.MaxReceivedMessageSize)
      {
         this.localAddress = address;
         this.readLock = new object();
      }

      public IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state)
      {
         return BeginReceiveRequest(DefaultReceiveTimeout, callback, state);
      }

      public IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public IRequestContext EndReceiveRequest(IAsyncResult result)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public bool EndTryReceiveRequest(IAsyncResult result, out IRequestContext context)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public bool EndWaitForRequest(IAsyncResult result)
      {
         throw new Exception("The method or operation is not implemented.");
      }

      public EndpointAddress LocalAddress
      {
         get { return this.localAddress; }
      }

      public IRequestContext ReceiveRequest(TimeSpan timeout)
      {
         ThrowIfDisposedOrNotOpen();
         lock (readLock)
         {
            Message message = ReadMessage(PathToFile(LocalAddress.Uri, "request"));
            return new FileRequestContext(message, this);
         }
      }

      public IRequestContext ReceiveRequest()
      {
         return ReceiveRequest(DefaultReceiveTimeout);
      }

      public bool TryReceiveRequest(TimeSpan timeout, out IRequestContext context)
      {
         context = null;
         bool complete = WaitForRequest(timeout);
         if (!complete)
         {
            return false;
         }
         context = ReceiveRequest(DefaultReceiveTimeout);
         return true;
      }

      public bool WaitForRequest(TimeSpan timeout)
      {
         ThrowIfDisposedOrNotOpen();
         try
         {
            File.Delete(PathToFile(LocalAddress.Uri, "request"));
            using (FileSystemWatcher watcher = new FileSystemWatcher(LocalAddress.Uri.AbsolutePath, "request"))
            {
               watcher.EnableRaisingEvents = true;
               WaitForChangedResult result = watcher.WaitForChanged(WatcherChangeTypes.Changed, (int)timeout.TotalMilliseconds);
               return !result.TimedOut;
            }
         }
         catch (IOException exception)
         {
            throw ConvertException(exception);
         }
      }
   }
}

Bogus code alert: The TryReceiveRequest method here deserves particular scorn for being the most egregiously bad code in the entire sample. Notice the especially bad use of timeouts here. Notice the awful race condition that would occur when multiple clients and servers attempted to use the same file URL. See how many scenarios you can work out to attack either the client or server through just this bad behavior.

Next time: Building a Custom File Transport, Part 10: Request Context