00001 // ThrottledOutputStream - output stream with throttling 00002 // 00003 // Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved. 00004 // 00005 // Redistribution and use in source and binary forms, with or without 00006 // modification, are permitted provided that the following conditions 00007 // are met: 00008 // 1. Redistributions of source code must retain the above copyright 00009 // notice, this list of conditions and the following disclaimer. 00010 // 2. Redistributions in binary form must reproduce the above copyright 00011 // notice, this list of conditions and the following disclaimer in the 00012 // documentation and/or other materials provided with the distribution. 00013 // 00014 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 00015 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00016 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00017 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 00018 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 00019 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 00020 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 00021 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00022 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 00023 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 00024 // SUCH DAMAGE. 00025 // 00026 // Visit the ACME Labs Java page for up-to-date versions of this and other 00027 // fine Java utilities: http://www.acme.com/java/ 00028 00029 package Acme.Serve; 00030 00031 import java.io.*; 00032 import java.util.*; 00033 00035 // <P> 00036 // Restricts output to a specified rate. Also includes a static utility 00037 // routine for parsing a file of throttle settings. 00038 // <P> 00039 // <A HREF="/resources/classes/Acme/Serve/ThrottledOutputStream.java">Fetch the software.</A><BR> 00040 // <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A> 00041 00042 public class ThrottledOutputStream extends FilterOutputStream 00043 { 00044 00046 // <P> 00047 // A throttle file lets you set maximum byte rates on filename patterns. 00048 // The format of the throttle file is very simple. A # starts a 00049 // comment, and the rest of the line is ignored. Blank lines are ignored. 00050 // The rest of the lines should consist of a pattern, whitespace, and a 00051 // number. The pattern is a simple shell-style filename pattern, using 00052 // ? and *, or multiple such patterns separated by |. 00053 // <P> 00054 // The numbers in the file are byte rates, specified in units of bytes 00055 // per second. For comparison, a v.32b/v.42b modem gives about 00056 // 1500/2000 B/s depending on compression, a double-B-channel ISDN line 00057 // about 12800 B/s, and a T1 line is about 150000 B/s. 00058 // <P> 00059 // Example: 00060 // <BLOCKQUOTE><PRE> 00061 // # throttle file for www.acme.com 00062 // * 100000 # limit total web usage to 2/3 of our T1 00063 // *.jpg|*.gif 50000 # limit images to 1/3 of our T1 00064 // *.mpg 20000 # and movies to even less 00065 // jef/* 20000 # jef's pages are too popular 00066 // </PRE></BLOCKQUOTE> 00067 // <P> 00068 // The routine returns a WildcardDictionary. Do a lookup in this 00069 // dictionary using a filename, and you'll get back a ThrottleItem 00070 // containing the corresponding byte rate. 00071 public static Acme.WildcardDictionary parseThrottleFile( String filename ) throws IOException 00072 { 00073 Acme.WildcardDictionary wcd = new Acme.WildcardDictionary(); 00074 BufferedReader br = new BufferedReader( new FileReader( filename ) ); 00075 while ( true ) 00076 { 00077 String line = br.readLine(); 00078 if ( line == null ) 00079 break; 00080 int i = line.indexOf( '#' ); 00081 if ( i != -1 ) 00082 line = line.substring( 0, i ); 00083 line = line.trim(); 00084 if ( line.length() == 0 ) 00085 continue; 00086 String[] words = Acme.Utils.splitStr( line ); 00087 if ( words.length != 2 ) 00088 throw new IOException( "malformed throttle line: " + line ); 00089 try 00090 { 00091 wcd.put( 00092 words[0], new ThrottleItem( Long.parseLong( words[1] ) ) ); 00093 } 00094 catch ( NumberFormatException e ) 00095 { 00096 throw new IOException( 00097 "malformed number in throttle line: " + line ); 00098 } 00099 } 00100 br.close(); 00101 return wcd; 00102 } 00103 00104 00105 private long maxBps; 00106 private long bytes; 00107 private long start; 00108 00110 public ThrottledOutputStream( OutputStream out, long maxBps ) 00111 { 00112 super( out ); 00113 this.maxBps = maxBps; 00114 bytes = 0; 00115 start = System.currentTimeMillis(); 00116 } 00117 00118 private byte[] oneByte = new byte[1]; 00119 00121 // written. 00122 // @param b the byte to be written 00123 // @exception IOException if an I/O error has occurred 00124 public void write( int b ) throws IOException 00125 { 00126 oneByte[0] = (byte) b; 00127 write( oneByte, 0, 1 ); 00128 } 00129 00131 // @param b the data to be written 00132 // @param off the start offset in the data 00133 // @param len the number of bytes that are written 00134 // @exception IOException if an I/O error has occurred 00135 public void write( byte b[], int off, int len ) throws IOException 00136 { 00137 // Check the throttle. 00138 bytes += len; 00139 long elapsed = System.currentTimeMillis() - start; 00140 long bps = bytes * 1000L / elapsed; 00141 if ( bps > maxBps ) 00142 { 00143 // Oops, sending too fast. 00144 long wakeElapsed = bytes * 1000L / maxBps; 00145 try 00146 { 00147 Thread.sleep( wakeElapsed - elapsed ); 00148 } 00149 catch ( InterruptedException ignore ) {} 00150 } 00151 00152 // Write the bytes. 00153 out.write( b, off, len ); 00154 } 00155 00156 }