001: // ThrottledOutputStream - output stream with throttling
002: //
003: // Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
004: //
005: // Redistribution and use in source and binary forms, with or without
006: // modification, are permitted provided that the following conditions
007: // are met:
008: // 1. Redistributions of source code must retain the above copyright
009: // notice, this list of conditions and the following disclaimer.
010: // 2. Redistributions in binary form must reproduce the above copyright
011: // notice, this list of conditions and the following disclaimer in the
012: // documentation and/or other materials provided with the distribution.
013: //
014: // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
015: // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
016: // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
017: // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
018: // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
019: // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
020: // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
021: // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
022: // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
023: // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
024: // SUCH DAMAGE.
025: //
026: // Visit the ACME Labs Java page for up-to-date versions of this and other
027: // fine Java utilities: http://www.acme.com/java/
028:
029: package Acme.Serve;
030:
031: import java.io.*;
032: import java.util.*;
033:
034: /// Output stream with throttling.
035: // <P>
036: // Restricts output to a specified rate. Also includes a static utility
037: // routine for parsing a file of throttle settings.
038: // <P>
039: // <A HREF="/resources/classes/Acme/Serve/ThrottledOutputStream.java">Fetch the software.</A><BR>
040: // <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
041:
042: public class ThrottledOutputStream extends FilterOutputStream {
043:
044: /// Parses a standard throttle file.
045: // <P>
046: // A throttle file lets you set maximum byte rates on filename patterns.
047: // The format of the throttle file is very simple. A # starts a
048: // comment, and the rest of the line is ignored. Blank lines are ignored.
049: // The rest of the lines should consist of a pattern, whitespace, and a
050: // number. The pattern is a simple shell-style filename pattern, using
051: // ? and *, or multiple such patterns separated by |.
052: // <P>
053: // The numbers in the file are byte rates, specified in units of bytes
054: // per second. For comparison, a v.32b/v.42b modem gives about
055: // 1500/2000 B/s depending on compression, a double-B-channel ISDN line
056: // about 12800 B/s, and a T1 line is about 150000 B/s.
057: // <P>
058: // Example:
059: // <BLOCKQUOTE><PRE>
060: // # throttle file for www.acme.com
061: // * 100000 # limit total web usage to 2/3 of our T1
062: // *.jpg|*.gif 50000 # limit images to 1/3 of our T1
063: // *.mpg 20000 # and movies to even less
064: // jef/* 20000 # jef's pages are too popular
065: // </PRE></BLOCKQUOTE>
066: // <P>
067: // The routine returns a WildcardDictionary. Do a lookup in this
068: // dictionary using a filename, and you'll get back a ThrottleItem
069: // containing the corresponding byte rate.
070: public static Acme.WildcardDictionary parseThrottleFile(
071: String filename) throws IOException {
072: Acme.WildcardDictionary wcd = new Acme.WildcardDictionary();
073: BufferedReader br = new BufferedReader(new FileReader(filename));
074: while (true) {
075: String line = br.readLine();
076: if (line == null)
077: break;
078: int i = line.indexOf('#');
079: if (i != -1)
080: line = line.substring(0, i);
081: line = line.trim();
082: if (line.length() == 0)
083: continue;
084: String[] words = Acme.Utils.splitStr(line);
085: if (words.length != 2)
086: throw new IOException("malformed throttle line: "
087: + line);
088: try {
089: wcd.put(words[0], new ThrottleItem(Long
090: .parseLong(words[1])));
091: } catch (NumberFormatException e) {
092: throw new IOException(
093: "malformed number in throttle line: " + line);
094: }
095: }
096: br.close();
097: return wcd;
098: }
099:
100: private long maxBps;
101: private long bytes;
102: private long start;
103:
104: /// Constructor.
105: public ThrottledOutputStream(OutputStream out, long maxBps) {
106: super (out);
107: this .maxBps = maxBps;
108: bytes = 0;
109: start = System.currentTimeMillis();
110: }
111:
112: private byte[] oneByte = new byte[1];
113:
114: /// Writes a byte. This method will block until the byte is actually
115: // written.
116: // @param b the byte to be written
117: // @exception IOException if an I/O error has occurred
118: public void write(int b) throws IOException {
119: oneByte[0] = (byte) b;
120: write(oneByte, 0, 1);
121: }
122:
123: /// Writes a subarray of bytes.
124: // @param b the data to be written
125: // @param off the start offset in the data
126: // @param len the number of bytes that are written
127: // @exception IOException if an I/O error has occurred
128: public void write(byte b[], int off, int len) throws IOException {
129: // Check the throttle.
130: bytes += len;
131: long elapsed = System.currentTimeMillis() - start;
132: long bps = bytes * 1000L / elapsed;
133: if (bps > maxBps) {
134: // Oops, sending too fast.
135: long wakeElapsed = bytes * 1000L / maxBps;
136: try {
137: Thread.sleep(wakeElapsed - elapsed);
138: } catch (InterruptedException ignore) {
139: }
140: }
141:
142: // Write the bytes.
143: out.write(b, off, len);
144: }
145:
146: }
|