Search code examples
c#streampipesplitter

C# Stream Pipe (Stream Spy)


I have an input stream and a stream reader component. This works fine but now I want to log all the traffic (save a copy in a file). So I need to spy on a stream. A solution I am thinking of is a stream pipe (pipeline) or a stream wrapper that takes a stream as input and then gives me a first look at the traffic. Something like this:

    void Init(System.Net.Sockets.NetworkStream stream)
    {
        System.IO.Stream wrappedStream = new MyWrapper(stream);
        wrappedStream.ReadSpy = MyMethod;
        XmlReader reader = XmlReader.Create(wrappedStream);
    }

    // This will get called after some bytes have been read from a stream,
    // but before they get passed to the XmlReader
    byte[] MyMethod(byte[] buffer)
    {
        m_Writer.Write(buffer); // write to a file 
        return buffer; // Give to XmlReader 
    }

Solution

  • What you want is called the Decorator Pattern. It's a technique for dynamically adding/modifying behavior:

    To do this, you want to implement the abstract class Stream, with a constructor of factory that accepts another Stream instance. You provide an implementation for every method/overload of the abstract class that invokes the same method/overload on the decorated Stream, plus doing whatever additional work your needs require.

    Once you've done that and decorated a Stream with your new decorator, It can be used interchangeably by anything else that accepts a Stream, including other similar decorators: decorators can even be nested, like layers of an onion to compose the behaviors you need.

    Something like this:

    class StreamInterceptor : Stream
    {
    
      public Stream DecoratedInstance { get; set; }
    
      public event Action<byte[]> BytesRead;
      public event Action<byte[]> BytesWritten;
    
      public StreamInterceptor( Stream instance )
      {
        if ( instance == null ) throw new ArgumentNullException("instance");
        this.DecoratedInstance = instance ;
        return ;
      }
    
      public override bool CanRead
      {
        get { return DecoratedInstance.CanRead; }
      }
    
      public override bool CanSeek
      {
        get { return DecoratedInstance.CanSeek; }
      }
    
      public override bool CanWrite
      {
        get { return DecoratedInstance.CanWrite; }
      }
    
      public override void Flush()
      {
        DecoratedInstance.Flush();
        return;
      }
    
      public override long Length
      {
        get { return DecoratedInstance.Length; }
      }
    
      public override long Position
      {
        get { return DecoratedInstance.Position; }
        set { DecoratedInstance.Position = value; }
      }
    
      public override int Read( byte[] buffer , int offset , int count )
      {
        int bytesRead = DecoratedInstance.Read(buffer, offset, count);
    
        // raise the bytes read event
        byte[] temp = new byte[bytesRead];
        Array.Copy(buffer,offset,temp,0,bytesRead);
        BytesRead(temp);
    
        return bytesRead;
      }
    
      public override long Seek( long offset , SeekOrigin origin )
      {
        return DecoratedInstance.Seek(offset, origin);
      }
    
      public override void SetLength( long value )
      {
        DecoratedInstance.SetLength(value);
        return;
      }
    
      public override void Write( byte[] buffer , int offset , int count )
      {
    
        // raise the bytes written event
        byte[] temp = new byte[count];
        Array.Copy(buffer,offset,temp,0,count);
        BytesWritten(temp);
    
        DecoratedInstance.Write(buffer, offset, count);
        return;
      }
    
    }
    

    Once you have that, you can say something like this:

    static void Main()
    {
      StreamInterceptor si = new StreamInterceptor(File.Open("foo.bar.txt",FileMode.Open,FileAccess.ReadWrite,FileShare.Read));
    
      si.BytesRead    += (bytes) => { Console.WriteLine("{0} bytes read", bytes.Length); } ;
      si.BytesWritten += (bytes) => { Console.WriteLine("{0} bytes written", bytes.Length); } ;
    
      Stream s = (Stream) si ;
      DoSomethingUseful(s);
    
    }
    

    And your event handler will be invoked whenever somebody reads or writes from the stream.