locked
Caching stream in ResponseFilter until stream is finished. RRS feed

  • Question

  • User-1312014603 posted

    Hi I need to cache the stream in a responsefilter, modify the entire output at once and then finally return it all to the user.

     I can easily store the stream and keep watching until I get an </html> tag, however, some output may not have an </html> tag, such as an AJAX response OR will have multiple </html> tags, as in bad HTML.

     Anyone have any ideas on how to determine when the stream is finished so that I know to stop trying to read it and then finish what I'm doing?

    My code is below, please suggest how to do this:

    Option Explicit On
    
    Imports System.IO
    Imports System.Text
    Imports System.Text.RegularExpressions
    
    Public Class myResponseFilter
        Inherits Stream
    
        Private SPDataIn As Stream
        Private SPPos As Long
        Private SPDataOut As StringBuilder
    
        Public Sub New(ByVal inputStream As Stream)
            MyBase.New()
            SPDataOut = New StringBuilder
            SPDataIn = inputStream
        End Sub
    
        Public Overloads Overrides ReadOnly Property CanRead() As Boolean
            Get
                Return True
            End Get
        End Property
    
        Public Overloads Overrides ReadOnly Property CanSeek() As Boolean
            Get
                Return True
            End Get
        End Property
    
        Public Overloads Overrides ReadOnly Property CanWrite() As Boolean
            Get
                Return True
            End Get
        End Property
    
        Public Overloads Overrides ReadOnly Property Length() As Long
            Get
                Return 0
            End Get
        End Property
    
        Public Overloads Overrides Property Position() As Long
            Get
                Return SPPos
            End Get
    
            Set(ByVal value As Long)
                SPPos = value
            End Set
        End Property
    
        Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal direction As System.IO.SeekOrigin) As Long
            Return SPDataIn.Seek(offset, direction)
        End Function
    
        Public Overloads Overrides Sub SetLength(ByVal length As Long)
            SPDataIn.SetLength(length)
        End Sub
    
        Public Overloads Overrides Sub Close()
            SPDataIn.Close()
        End Sub
    
        Public Overloads Overrides Sub Flush()
            SPDataIn.Flush()
        End Sub
    
        Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer
            Return SPDataIn.Read(buffer, offset, count)
        End Function
    
        Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)
    
            Dim SPData As String = Encoding.UTF8.GetString(buffer, offset, count)
    
            If (New Regex("</body>[\W]*</html>", RegexOptions.IgnoreCase)).IsMatch(SPData) Then
                'Add SP Output To SPDataOut
                SPDataOut.Append(SPData)
                'Store Output To New String
                Dim SPDataStr As String = SPDataOut.ToString()
    
                SPDataStr = DoStuffToAllOutput(SPDataStr)
    
                Dim SPBytes As Byte() = Encoding.UTF8.GetBytes(SPDataStr)
                SPDataIn.Write(SPBytes, 0, CInt(SPBytes.Length))
                Return
            End If
            SPDataOut.Append(SPData)
    
        End Sub
    
    End Class
     

     

    Monday, August 13, 2007 11:51 AM

All replies

  • User-225114762 posted

    I suppose that you mean HttpResponse.Filter. This will actually never use the 'Read' methods - the framework will write data to it's Write() method. At some point in time, your filter will then write to the wrapped stream, the one passed in to the constructor (which you're calling inputStream above, it should be named something like 'previousFilter').

    So, what you do, if you *really* need to cache the entire response, is to store it in your StringBuilder without further action at every Write. Then, in your override of Dispose() (which is called by Close() etc), you do what you have to do, and write the result to 'previousFilter'.Write(), before calling Dispose() on 'previousFilter' and then you're done. Remember the Dispose()-pattern (you actually implement a helper Dispose(bool disposing), and the semantics must allow multiple calls without damage. Search 'dispose pattern' if you're unsure.

    A much worse problem is trying to determine if you actually should modify the output stream, since ASP.NET has a bug in that it's script callback results are sent with MIME-type 'text/html' - it should be 'text/plain'. So there's really no way but through heurstics to determine if the response really is HTML and thus should be rewritten.

    Monday, August 13, 2007 12:56 PM