jigsaw contribution: CompressFilter

Hi !

----

I tried out GZIPFilter (attached to a HTTPFrame), but I saw only rubbish
in the web-browsers
(did I do something wrong here ?).

Therefore, I wrote a replacement for GZIPFrame, adding some features
like variable buffer size
(to get a little bit control of the CPU usage of this frame), and I've
added "compress" encoding.

----

If someone has information about the "deflate" compression supported by
MS Internet Explorer,
let me know this info that I can extend CompressFrame with this
encoding.

----

Source is included as an attachment.

----

Bye,
Roland

--
  __ .  . __
 (o.\ \/ /.o)  Roland Mainz                               C programmer
  \__\/\/__/   Roland.Mainz@informatik.med.uni-giessen.de MPEG specialist
  /O /==\ O\   gisburn@w-specht.rhein-ruhr.de             Sun&&Amiga programmer
 (;O/ \/ \O;)  TEL +49 (0) 2426901568  FAX +49 (0) 2426901569
// 
// $Id: CompressFilter.java,v 1.1 1998/04/05 11:06:24 gisburn Exp $
// Written by Roland Mainz (Roland.Mainz@informatik.med.uni-giessen.de)
// based on GZIPFilter.java 1.7 written by the Jigsaw team

package org.w3c.jigsaw.filters;

import java.io.*;
import java.util.zip.*;

import org.w3c.tools.resources.*;
import org.w3c.www.mime.*;
import org.w3c.www.http.*;
import org.w3c.jigsaw.http.*;
import org.w3c.jigsaw.resources.*;





/**
 * This filter will compress the content of replies.
 * Compression is done <em>on the fly</em>. This assumes that you're really
 * on a slow link, where you have lots of CPU, but not much bandwidth.
 * <p>A nifty usage for that filter, is to plug it on top of a
 * <code>org.w3c.jigsaw.proxy.ProxyDirectory</code>, in which case it
 * will compress the data when it flies out of the proxy.
 */

public class CompressFilter extends ResourceFilter 
{
    /**
     * This class implements the thread which moves data 
     * from given in to out because we cannot do this on the 
     * calling filter thread.
     */
    class DataMover extends Thread 
    {
        InputStream  in   = null;
        OutputStream out  = null;

        public void run() 
        {
            try 
            {
              byte buf[] = new byte[ 512 ];
              int  got   = -1;
              while( (got = in.read( buf ) ) >= 0 ) 
                out.write( buf, 0, got );
            } 
            catch( IOException ex ) 
            {
              System.err.print( getName() + ": I/O failure" ); ex.printStackTrace();
            } 
            finally 
            {
              try { in.close();  } catch( Exception ex ) { /* ignored */ };
              try { out.close(); } catch( Exception ex ) { /* ignored */ };
            }
        }


        DataMover( InputStream in, OutputStream out ) 
        {
            this.in   = in;
            this.out  = out;
      
            // FIXME: the thread name should contain more info...
            setName( "CompressDataMover" );
            start();
        }
    }
    
    /**
     * Do debugging ?
     */
    final static private boolean doDebug = false; 
         
    /**
     * Attribute index - List of MIME type that we can compress
     */
    protected static int ATTR_MIME_TYPES  = -1;
    
    /**
     * Attribute index - Compress buffer size.
     * Larger values means better compression, but more CPU usage;
     * small values means less compression, but less CPU usage.
     * Not that some compressions like "compress" (zip compression) does 
     * not respect this attribute (but "gzip" does) !
     */
    protected static int ATTR_BUFFER_SIZE = -1;
    
    /**
     * Buffer size special value "-1": Use default buffer size for this compression.
     */
    final static public int useDefaultBufferSize = -1;
    
    static 
    {
        Class     c = null;
        Attribute a = null;
        
        try 
        {
          c = Class.forName( "org.w3c.jigsaw.filters.CompressFilter" );
        } 
        catch( Exception ex ) 
        {
          ex.printStackTrace();
          System.exit( 1 );
        }
        
        d( "compress filter loaded." );
        
        // this should be a useable default: compress all text based 
        // content (html, xml, plain text etc.)
        final String mime_type_defaults[] = { "text/*" };
    
        // Register the MIME types attribute:
        a = new StringArrayAttribute( "mime-types",
                                      mime_type_defaults,
                                      Attribute.EDITABLE );
        ATTR_MIME_TYPES = AttributeRegistry.registerAttribute( c, a );
        
        // Register the buffer size attribute:
        a = new IntegerAttribute( "buffer-size",
                                  new Integer( useDefaultBufferSize ),
                                  Attribute.EDITABLE );
        ATTR_BUFFER_SIZE = AttributeRegistry.registerAttribute( c, a );
    }
    
    
    /**
     * The set of MIME types we are allowed to compress.
     */
    protected MimeType types[] = null;


    /**
     * Catch the setting of mime types to compress.
     * @param idx The attribute being set.
     * @param val The new attribute value.
     */
    public void setValue( int idx, Object value ) 
    {
        super.setValue( idx, value );
        
        // FIXME: What about ATTR_BUFFER_SIZE ?
        if( idx == ATTR_MIME_TYPES ) 
        {
          synchronized( this ) 
          {
            types = null;
          }
        }
    }
    
    
    /**
     * get the buffer size
     */
    public synchronized int  getBufferSize() 
    {
        Integer val = (Integer)getValue( ATTR_BUFFER_SIZE, null );
        if( val == null ) // use default buffer size 
            return( useDefaultBufferSize );
        return( val.intValue() );
    }

     
    /**
     * Get the set of MIME types to match:
     * @return An array of MimeType instances.
     */
    public synchronized MimeType[] getMimeTypes() 
    {
        if( types == null ) 
        {
          String strtypes[] = (String[])getValue( ATTR_MIME_TYPES, null );
          if( strtypes == null )
            return( null );
        
          types = new MimeType[strtypes.length];
          
          for( int i = 0 ; i < types.length ; i++)  
          {
            try 
            {
              types[i] = new MimeType( strtypes[i] );
            } 
            catch( Exception ex ) 
            {
              types[i] = null;
            }
          }
        }
    
        return( types );
    }
    
    
    // tested with Lynx
    protected void doCompress( Reply reply, int bufferSize ) throws Exception
    {
        d( "using compress compression, buffer = " + bufferSize + "." );
            
        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream  pin  = new PipedInputStream( pout );
              
        ZipOutputStream zout = new ZipOutputStream( pout );
        zout.putNextEntry( new ZipEntry( "x" ) ); // add a ZIP entry 
        
        new DataMover( reply.openStream(), zout );
        
        reply.addContentEncoding( "compress" );
        reply.setContentLength( -1 );
        reply.setStream( pin );             
    }
    
    
    // tested with Netscape 4.x, 5.x, IE 5 and Lynx
    protected void doGZIP( Reply reply, int bufferSize ) throws Exception
    {
        d( "using gzip compression, buffer = " + bufferSize + "." );

        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream  pin  = new PipedInputStream( pout );
        
        OutputStream out = (bufferSize == useDefaultBufferSize)?
                                 (new GZIPOutputStream( pout )):
                                 (new GZIPOutputStream( pout, bufferSize ));
        new DataMover( reply.openStream(), out );
        
        reply.addContentEncoding( "gzip" );
        reply.setContentLength( -1 );
        reply.setStream( pin );
    }


    // don#t have any info about this compression method - seems that only 
    // IE supports it 
    // void doDeflate( Reply reply ) throws Exception


    /**
     * @param request The original request.
     * @param reply It's original reply. 
     * @return A Reply instance, or <strong>null</strong> if processing 
     * should continue normally. 
     * @exception ProtocolException If processing should be interrupted, 
     * because an abnormal situation occured. 
     */
    public ReplyInterface outgoingFilter( RequestInterface req, ReplyInterface rep ) 
        throws ProtocolException
    {
        Request request = (Request)req;
        Reply   reply   = (Reply)rep;
      
        // Anything to compress ?
        if( !reply.hasStream() )
          return( null );
        
        // Match possible mime types:
        MimeType t[]        = getMimeTypes();
        int      bufferSize = getBufferSize();
        boolean  matched    = false;
    
        if( t != null ) 
        {
          for( int i = 0 ; i < t.length ; i++) 
          {
            if( t[i] == null )
              continue;
            
            if( t[i].match( reply.getContentType() ) > 0 ) 
            {
              matched = true;
              break;
            }
          }
        }
    
        // no mimetypes matched ?
        if( !matched ) 
          return( null );
          
        // FIXME: IMHO it will cause havoc if outgoing data are already compressed...
        // We should add here a check whether a content-encoding is present, and abort
        // our work here in this case.
          
        HttpAcceptEncoding encodings[] = request.getAcceptEncoding();
        
        // no encodings set (e.g. no encodings supported) ?
        if( encodings == null )
          return( null );  
          
        // TODO: The incoming array of Accept-Encodings should be sorted by quality, 
        //       if there is such a thing for this kind of headers
        
        try
        {
          for( int i = 0 ; i < encodings.length ; i++ )
          {
            String e = encodings[i].toString();
          
            if( e.indexOf( "compress" ) != -1 )
            {
              doCompress( reply, bufferSize );
              return( null );
            }          
            else if( e.indexOf( "gzip" ) != -1 )
            {
              doGZIP( reply, bufferSize );
              return( null );
            } 
            else 
            {
              // unsupported encoding (deflate etc.)
              d( "unsupported encoding `" + e + "`." );
            }
          } 
        }
        catch( Exception exc )
        {
          exc.printStackTrace();
        } 
    
        return( null );
    }
    
    
    private static void d( String s )
    {
        if( doDebug ) System.err.println( "CompressFilter: " + s );
    }
}

Received on Monday, 3 May 1999 22:04:30 UTC