Αφαίρεση κενών με Response.Filter

Πληροφορίες σχετικές με την ASP, ASP.NET και με τις εφαρμογές που είναι γραμμένες με αυτήν.

Συντονιστές: WebDev Moderators, Super-Moderators

Απάντηση
Άβαταρ μέλους
skeftomilos
Script Master
Δημοσιεύσεις: 2888
Εγγραφή: 07 Ιαν 2005 07:22
Τοποθεσία: Αθήνα

Αφαίρεση κενών με Response.Filter

Δημοσίευση από skeftomilos » 15 Ιαν 2006 01:34

Αν υπάρχει κάτι που διακινείται σε τεράστιες ποσότητες στο Internet, αυτό είναι το κενό. Όχι το απόλυτο κενό ή το κβαντικό κενό, ή το κενό που γεμίζει το κεφάλι του Beorge W. Gush, αλλά ο χαρακτήρας με κωδικό ASCII 32. Αν κάνετε view-source σε οποιαδήποτε σχεδόν σελίδα του Web είναι σίγουρο ότι θα δείτε κάτι τέτοιο:

Κώδικας: Επιλογή όλων

  </head>
  <body>
    <tr>
      <td width="762" height="443" align="left" valign="top" bgcolor="#FFFFFF">
        <table width="762" border="0" cellspacing="0" cellpadding="0">
          <tr>
            <td align="left" valign="top">
              <img src="images/spacer.gif" width="2" height="10">
            </td>
            <td align="left" valign="top">
              <table width="100%" border="0" cellspacing="0" cellpadding="0">
                <tr>
                  <td width="1%" align="left" valign="top">
                    <img src="images/logo.gif" width="212" height="83">
Όλοι αυτοί οι κενοί χαρακτήρες στην αρχή των γραμμών έχουν μοναδικό λόγο ύπαρξης να βελτιώνουν την αναγνωσιμότητα του κώδικα, κάτι που ελάχιστη σημασία έχει για τους περισσότερους χρήστες του διαδικτύου. Αντίθετα επιβαρύνουν τις σελίδες με ένα καθόλου ευκαταφρόνητο αριθμό από περιττά bytes, περίπου το 30% του συνόλου των bytes είναι κενά και tabs. Ακόμα και όταν ο κώδικας των σελίδων συμπιέζεται με τη μέθοδο gzip, τα περιττά κενά εξακολουθούν να φουσκώνουν τον συμπιεσμένο κώδικα, αν και σε πολύ μικρότερο βαθμό. Άραγε υπάρχει τρόπος να απαλλάξουμε τις σελίδες μας απ' αυτό το αόρατο φορτίο;

Δε θα αναφερθώ στα προγράμματα που υπάρχουν και αφαιρούν μαζικά τα κενά από τα αρχεία, γιατί φαντάζομαι ότι τα ξέρετε όλοι. Και σίγουρα θα γνωρίζετε ότι η διαδικασία της συμπίεσης πριν από το upload είναι ένας μικρός καθημερινός μπελάς. Μια καλύτερη ιδέα θα ήταν να φροντίσουμε ώστε αυτή τη διαδικασία να συμβαίνει αυτόματα κάθε φορά που γίνεται ένα HTTP request. Κάτι τέτοιο δεν ήταν παλιότερα εφικτό με την πτωχή πλην τίμια ASP, αλλά με την έλευση της ASP.NET αυτή η δυνατότητα πλέον υπάρχει. Καλωσορίστε την ιδιότητα Response.Filter!

Η Response.Filter περιέχει ένα αντικείμενο Stream από το οποίο περνά το buffered output της σελίδας μας πριν σταλεί στον browser. Μπορούμε να αντικαταστήσουμε το υπάρχον Stream με ένα δικής μας έμπνευσης, το οποίο να φροντίζει για την αφαίρεση των κενών. Ας ονομάσουμε το νέο stream CrunchFilter, και ας χρησιμοποιήσουμε VB.NET ως γλώσσα για τον κώδικα:

crunch-filter.vb

Κώδικας: Επιλογή όλων

Public Class CrunchFilter &#58; Inherits System.IO.Stream

  Private _stream As System.IO.Stream
  Private _position As Long
  Private html As System.Text.StringBuilder
  
  Public Sub New&#40;stream As System.IO.Stream&#41;
    _stream = stream
    html = New System.Text.StringBuilder&#40;&#41;
  End Sub
  
  Public Overrides ReadOnly Property CanRead As Boolean
    Get 
      Return True
    End Get
  End Property
  
  Public Overrides ReadOnly Property CanSeek As Boolean
    Get 
      Return True
    End Get
  End Property
  
  Public Overrides ReadOnly Property CanWrite As Boolean
    Get 
      Return True
    End Get
  End Property
  
  Public Overrides ReadOnly Property Length As Long
    Get 
      Return 0
    End Get
  End Property
  
  Public Overrides Property Position As Long
    Get
      Return _position
    End Get
    Set&#40;value As Long&#41;
      _position = value
    End Set
  End Property
  
  Public Overrides Function Seek&#40;offset As Long, direction As System.IO.SeekOrigin&#41; As Long
    Return _stream.Seek&#40;offset, direction&#41;
  End Function
  
  Public Overrides Sub SetLength&#40;length As Long&#41;
    _stream.SetLength&#40;length&#41;
  End Sub
  
  Public Overrides Sub Close&#40;&#41;
    _stream.Close&#40;&#41;
  End Sub
  
  Public Overrides Sub Flush&#40;&#41;
    _stream.Flush&#40;&#41;
  End Sub
  
  Public Overrides Function Read&#40;buffer As Byte&#40;&#41;, offset As Integer, count As Integer&#41; As Integer
    Return _stream.Read&#40;buffer, offset, count&#41;
  End Function
  
  Public Overrides Sub Write&#40;buffer As Byte&#40;&#41;, offset As Integer, count As Integer&#41;
    Dim currentEncoding As System.Text.Encoding = System.Web.HttpContext.Current.Response.ContentEncoding
    Dim textBuffer As String = currentEncoding.GetString&#40;buffer, offset, count&#41;
    If System.Text.RegularExpressions.Regex.IsMatch&#40;textBuffer, "</html>", System.Text.RegularExpressions.RegexOptions.IgnoreCase&#41; Then
      html.Append&#40;textBuffer&#41;
      Dim temp As String = html.ToString&#40;&#41;.Trim&#40;&#41;
      temp = System.Text.RegularExpressions.Regex.Replace&#40;temp, "\s*\r\n\s*", Chr&#40;10&#41;&#41;
      Dim data&#40;&#41; As Byte = currentEncoding.GetBytes&#40;temp&#41;
      _stream.Write&#40;data, 0, data.Length&#41;
    Else
      html.Append&#40;textBuffer&#41;
    End If
  End Sub
  
End Class
Η κλάση CrunchFilter κληρονομεί την System.IO.Stream, και επομένως πρέπει υποχρεωτικά να υλοποιήσει ορισμένα abstract members. Αυτός είναι ο λόγος που ο κώδικας είναι τόσο μακροσκελής, γιατί στην ουσία μόνο η μέθοδος Write είναι σημαντική. Εκεί ελέγχουμε για την ύπαρξη του </html> που σηματοδοτεί την ολοκλήρωση του response, και προχωρούμε στην αφαίρεση των κενών με μια απλή regular expression.

Κώδικας: Επιλογή όλων

temp = System.Text.RegularExpressions.Regex.Replace&#40;temp, "\s*\r\n\s*", Chr&#40;10&#41;&#41;
Η συγκεκριμένη regular expression δεν αφαιρεί μόνο τα αρχικά κενά αλλά και τις κενές γραμμές. Τώρα αυτό που μένει είναι να αποδώσουμε το νέο φίλτρο στην ιδιότητα Response.Filter. Θα μπορούσαμε να το κάνουμε μέσα στην κάθε μεμονωμένη σελίδα, αλλά είναι πολύ πιο πρακτικό να γίνει μέσα σε κάποιο συμβάν του Application στο αρχείο global.asax της εφαρμογής. Πολλά είναι τα συμβάντα που μπορούμε να παγιδεύσουμε, αλλά το πιο κατάλληλο γι αυτή τη δουλειά είναι το PostRequestHandlerExecute.

global.asax

Κώδικας: Επιλογή όλων

<script runat="server" src="crunch-filter.vb" />
<script runat="server">

  Sub Application_PostRequestHandlerExecute&#40;sender As &#91;Object&#93;, e As EventArgs&#41;
    Response.Filter = New CrunchFilter&#40;Response.Filter&#41;
  End Sub
  
</script>
Αυτό είναι όλο. Δοκιμάστε να δείτε οποιαδήποτε σελίδα της εφαρμογής και παρατηρήστε ότι τα κενά έχουν ως δια μαγείας εξαφανιστεί!

Αφού βεβαιωθήκαμε ότι δουλεύει θα χρειαστεί να διορθώσουμε κάποιες ψιλο-ατέλειες. Κατα αρχήν η ASP.NET δεν περιορίζεται στην εξαγωγή HTML, μπορεί να εξάγει και JavaScript, XML ή και binary αρχεία εικόνων. Αν από το φίλτρο μας περάσει ένα binary αρχείο είναι μάλλον σίγουρο ότι θα καταστραφεί, οπότε θα πρέπει να φροντίσουμε να αποκλειστεί αυτή η πιθανότητα. Ένας απλός τρόπος είναι να ελέγξουμε ότι η ιδιότητα Response.ContentType έχει τιμή "text/html".

Μία άλλη βελτίωση είναι να εμποδίσουμε το φιλτράρισμα κατά τη διάρκεια της ανάπτυξης της εφαρμογής, γιατί τότε η μορφοποίηση του κώδικα είναι χρήσιμη. Αυτό μπορούμε να το κάνουμε ελέγχοντας είτε την Request.Url.IsLoopback είτε την Context.IsDebuggingEnabled. Η πρώτη επιστρέφει True όταν το url είναι ο localhost, και η δεύτερη επιστρέφει τη σχετική ρύθμιση του αρχείου web.config:

Κώδικας: Επιλογή όλων

<compilation debug="true" />
Κανονικά δε θα πρέπει να ξεχάσουμε να αλλάξουμε αυτή τη ρύθμιση σε false όταν ανεβάσουμε την εφαρμογή στον server, πάντως για καλό και για κακό ας προτιμήσουμε τη Request.Url.IsLoopback:

global.asax

Κώδικας: Επιλογή όλων

<script runat="server" src="crunch-filter.vb" />
<script runat="server">

  Sub Application_PostRequestHandlerExecute&#40;sender As &#91;Object&#93;, e As EventArgs&#41;
    If &#40;Response.ContentType = "text/html"&#41; AndAlso &#40;Not Request.Url.IsLoopback&#41; Then
      Response.Filter = New CrunchFilter&#40;Response.Filter&#41;
    End If
  End Sub
  
</script>
Μια παρατήρηση που πρέπει οπωσδήποτε να γίνει είναι ότι τα κενά στο Web δεν είναι πάντα περιττά. για παράδειγμα αν έχουμε στις σελίδες μας στοιχεία <textarea>, <pre>, <code> κ.λπ. είναι πιθανό να αντιμετωπίσουμε προβλήματα με το CrunchFilter. Ειδικά για τα στοιχεία textarea θα πρέπει οπωσδήποτε να ληφθεί κάποια μέριμνα ώστε να διατηρούν τα κενά τους. Αν κάποιος μπορεί ας βελτιώσει τη μέθοδο Write του CrunchFilter ώστε να χειρίζεται σωστά αυτές τις περιπτώσεις, και μετά ας μας πει πώς το έκανε. :)

Πρέπει να διευκρινιστεί ότι αυτή η τεχνική φιλτραρίσματος των σελίδων αφορά μόνο τις δυναμικές .aspx σελίδες και όχι τις στατικές .htm, αρχεία .css, .js κ.λπ. Γι αυτές η μόνη λύση είναι να τις έχουμε ήδη συμπιεσμένες πριν τις ανεβάσουμε στον server. Ας αναφερθεί με την ευκαιρία ότι ο IIS από την έκδοση 6 έχει πλέον τη δυνατότητα συμπίεσης gzip, η οποία όμως δεν είναι ενεργοποιημένη από προεπιλογή. Ένα άρθρο που περιγράφει τη διαδικασία ενεργοποίησης είναι αυτό: IIS Compression in IIS 6.

Και αυτό είναι το άρθρο στο οποίο βρήκα την αρχική μορφή του κώδικα για το φίλτρο (σε C#):
- Response Filter to Take out White Spaces and New Line Feeds using HttpResponse.Filter

Μερικά άρθρα σχετικά με τις δυνατότητες που προσφέρει η ιδιότητα Response.Filter:
- Response.Filter == nice. ASP.NET continues to surprise me
- Producing XHTML-Compliant Pages With Response Filters

Αν δε μπορείτε να ενεργοποιήσετε τη συμπίεση gzip του Server, υπάρχει μία δωρεάν engine που παρέχει αυτή τη δυνατότητα:
- HttpCompress

Για το τέλος ας δούμε πώς γίνεται η αφαίρεση των κενών με PHP:

Κώδικας: Επιλογή όλων

<?php
  function crunch_output&#40;$buffer&#41; 
  &#123;
    return preg_replace&#40;'/\s*\n\s*/', "\n", $buffer&#41;;
  &#125;
  ob_start&#40;"crunch_output"&#41;;
?>
<html>
  <head>
    ...
Όπως θα περίμενε κανείς εδώ τα πράγματα είναι πολύ πιο απλά. Ωστόσο θα πρέπει να προστεθεί κατ' ελάχιστο μία οδηγία include() σε κάθε .php σελίδα του site. Αν κάνω λάθος ας με διορθώσει κάποιος guru της PHP παρακαλώ. :)

Υ.Γ. Ουπς! Μόλις είδα ότι από τον κώδικα του FreeStuff λείπουν τα κενά! :) Μάλλον ήρθα δεύτερος. :P
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.

Άβαταρ μέλους
cherouvim
Script Master
Δημοσιεύσεις: 3137
Εγγραφή: 13 Ιούλ 2005 22:56
Τοποθεσία: Athens, Greece
Επικοινωνία:

Αφαίρεση κενών με Response.Filter

Δημοσίευση από cherouvim » 15 Ιαν 2006 10:06

Oraios! Kai ego mpika sto magiko kosmo ton Filters sti Java pou grafo tetia gia GZIP, caching kai user authentication.

Sosto kai afto pou eipes gia tin PHP alla nomizo ginete na to eisagageis san filtro 'post' kai 'pre' se opiodipote request meso kapas dilosis sto .htaccess. Opote to include den hriazete pouthena.

Keep it up!

Άβαταρ μέλους
skeftomilos
Script Master
Δημοσιεύσεις: 2888
Εγγραφή: 07 Ιαν 2005 07:22
Τοποθεσία: Αθήνα

Αφαίρεση κενών με Response.Filter

Δημοσίευση από skeftomilos » 16 Απρ 2006 02:05

Μετά από περισσότερη μελέτη προέκυψε η παρακάτω βελτιωμένη έκδοση:

Κώδικας: Επιλογή όλων

Imports System
Imports System.IO
Imports System.Web
Imports System.Text
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

Public Class CrunchFilter &#58; Inherits Stream

  Private _stream As Stream
  Private _encoding As Encoding
  Private _memory As MemoryStream
  
  Public Sub New&#40;stream As Stream&#41;
    Me.New&#40;stream, Nothing&#41;
  End Sub

  Public Sub New&#40;stream As Stream, encoding As Encoding&#41;
    _stream = stream

    _encoding = encoding
    _memory = New MemoryStream&#40;&#41;
  End Sub
  
  Public Overrides ReadOnly Property CanRead As Boolean
    Get 
      Return False
    End Get
  End Property
  
  Public Overrides ReadOnly Property CanSeek As Boolean
    Get 
      Return False
    End Get
  End Property
  
  Public Overrides ReadOnly Property CanWrite As Boolean
    Get 
      Return True
    End Get
  End Property
  
  Public Overrides ReadOnly Property Length As Long
    Get 
      Throw New NotImplementedException&#40;&#41;
    End Get
  End Property
  
  Public Overrides Property Position As Long
    Get
      Throw New NotImplementedException&#40;&#41;
    End Get
    Set&#40;value As Long&#41;
      Throw New NotImplementedException&#40;&#41;
    End Set
  End Property
  
  Public Overrides Function Seek&#40;offset As Long, direction As SeekOrigin&#41; As Long
    Throw New NotImplementedException&#40;&#41;
  End Function
  
  Public Overrides Sub SetLength&#40;length As Long&#41;
    _memory.SetLength&#40;length&#41;
  End Sub
  
  Public Overrides Function Read&#40;buffer As Byte&#40;&#41;, offset As Integer, count As Integer&#41; As Integer
    Throw New NotImplementedException&#40;&#41;
  End Function
  
  Public Overrides Sub Write&#40;buffer As Byte&#40;&#41;, offset As Integer, count As Integer&#41;
    _memory.Write&#40;buffer, 0, count&#41;
  End Sub

  Public Overrides Sub Flush&#40;&#41;
  End Sub
  
  Public Overrides Sub Close&#40;&#41;
    If _encoding Is Nothing Then
      _encoding = HttpContext.Current.Response.ContentEncoding
    End If
    Dim reader As New StreamReader&#40;_memory, _encoding&#41;
    _memory.Position = 0
    Dim text As String = reader.ReadToEnd&#40;&#41;
    reader.Close&#40;&#41;
    If Not Regex.IsMatch&#40;text, "</textarea>", RegexOptions.IgnoreCase&#41; Then
      text = Regex.Replace&#40;text, "\s*\n\s*", Chr&#40;10&#41;&#41;.Trim&#40;&#41;
    End If
    Dim data&#40;&#41; As Byte = _encoding.GetBytes&#40;text&#41;
    _stream.Write&#40;data, 0, data.Length&#41;
    _stream.Close&#40;&#41;
  End Sub
  
End Class
Αν βρεθεί στον HTML κώδικα στοιχείο textarea δε γίνεται αφαίρεση κενών.

Το φίλτρο αποθηκεύει εσωτερικά το σύνολο του output και κάνει την αφαίρεση των κενών στο κλείσιμο του stream. Αυτό σημαίνει ότι χάνεται η λειτουργικότητα της Response.Flush, στην περίπτωση που τη χρειάζεται κανείς.

Μπορείτε να δείτε το βελτιωμένο CrunchFilter σε δράση σε αυτό το site: http://www.officeworld.gr
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.

Απάντηση

Επιστροφή στο “ASP, ASP.NET”

Μέλη σε σύνδεση

Μέλη σε αυτήν τη Δ. Συζήτηση: Δεν υπάρχουν εγγεγραμμένα μέλη και 0 επισκέπτες