#include(proxy.java)
#include(compiler.java)
#include(jvm.c)
Admin.java*c
/******************************************************************
*** File Admin.java*c
***
***/
import java.net.*;
import java.io.*;
import c.net.*;
import c.io.*;
//
// Class: Admin
// Abstract: The admin thread listens on admin socket and handle all
// communications with the remote administrator.
//
public class Admin extends Thread
{
//
// Member variables
//
ServerSocket adminSocket = null;
Socket appletSocket = null;
String passwordCandidate = null;
DataInputStream in = null;
DataOutputStream out = null;
Config config = null;
Cache cache;
//
// Public methods
//
//
// Constructor
//
Admin(Config configObject, Cache cacheManager)
{
try
{
config = configObject;
cache = cacheManager;
adminSocket = new ServerSocket(0);
config.setAdminPort(adminSocket.getLocalPort());
}
catch (IOException e)
{
System.out.println("Error opening admin socket");
}
}
//
// Handle communications with remote administrator
//
public void run()
{
while(true)
{
try
{
appletSocket = adminSocket.accept();
in = new DataInputStream(appletSocket.getInputStream());
out = new DataOutputStream(appletSocket.getOutputStream());
do
{
// Read password candidate sent by applet
String passwordCandidate = in.readLine();
// Send applet ack/nack on password
if (config.getPassword().equals(passwordCandidate))
{
out.writeBytes("ACCEPT\n");
break;
}
else
{
out.writeBytes("REJECT\n");
}
out.flush();
}
while (true);
//
// Password is OK, so let's send the administrator the
// parameters values and read his new values
//
while(true)
{
out.writeBytes(config.toString());
out.flush();
config.parse(in.readLine());
System.out.println("Configuration changed by administrator.");
// Administrator wants to clean the cache
if (config.getCleanCache())
{
cache.clean();
config.setCleanCache(false); //no need to clean again
}
}
}
catch (Exception e)
{
//
// This line was reached because the administrator closed
// the connection with the proxy. That's fine, we are now
// available for another administrator to log in.
//
System.out.println("Connection with administrator closed.");
}
finally
{
try
{
out.close();
in.close();
}
catch(Exception exc)
{}
}
}//while
}
}
Cache.java
/******************************************************************
*** File Cache.java
***
***/
import java.io.*;
import java.util.*;
import java.net.*;
//
// Class: Cache
// Abstract: manages all caching activities.
//
public class Cache
{
//
// Members variables
//
String basePath = null;
long MinFreeSpace;// in bytes
Hashtable htable;
Config config;
//
// Public methods
//
//
// Constructor
//
public Cache(Config configObject)
{
//
// Initialize variables
//
config = configObject;
MinFreeSpace = 15000;
htable = new Hashtable();
//
// Create directory for caching
//
File cacheDir = new File("Cache");
cacheDir.mkdirs();
basePath = cacheDir.getAbsolutePath();
//
// Delete all files in cache directory
//
int i;
File file = new File(basePath);;
String filename;
// Get list of files in cache direcotry
String files[] = file.list();
// Delete each file found
for (i=0; i<files.length; i++)
{
file = new File(basePath + File.separatorChar + files[i]);
file.delete();
}
config.setFilesCached(0);
config.setBytesCached(0);
config.setHits(0);
config.setMisses(0);
}
//
// isCachable - check if URL reply should be cached
//
public boolean IsCachable(String rawUrl)
{
return (getFileName(rawUrl) != null);
}
//
// IsCached - Check if we have in cache what the client wants.
//
public boolean IsCached(String rawUrl)
{
// Generate filename from URL
String filename = getFileName(rawUrl);
if (filename == null)
return false;
// Search in hash table
if (htable.get(filename) != null)
return true;
return false;
}
//
// getFileInputStream - When this method is called, it means a cache hit.
// We update the date field in the hash table entry and return a
// FileInputStream object corresponding to the file caching the info.
//
public FileInputStream getFileInputStream(String rawUrl)
{
FileInputStream in = null;
try
{
String filename = getFileName(rawUrl);
// Update the hash table entry with current date as value
htable.put(filename,new Date());
in = new FileInputStream(filename);
}
catch (FileNotFoundException fnf)
{
try
{
System.out.println("File Not Found:"+getFileName(rawUrl)+" "+fnf);
}
catch (Exception e)
{}
}
finally
{
return in;
}
}
//
// getFileoutputStream - When this method is called, it means we're about
// to cache a new object. We generate a file name, and return
// a corresponding FileOutputStream object.
//
public FileOutputStream getFileOutputStream(String rawUrl)
{
FileOutputStream out = null;
String filename;
try
{
filename = getFileName(rawUrl);
out = new FileOutputStream(filename);
}
catch (IOException e)
{}
finally
{
return out;
}
}
//
// Decrement Cache Free Space (In Bytes)
//
public synchronized void DecrementFreeSpace(int nbytes, String rawUrl)
{
config.setBytesCached(config.getBytesCached() + nbytes);
if (config.getBytesFree() <= MinFreeSpace)
MakeFreeSpace(rawUrl);
}
//
// Add new entry to hash table
//
public synchronized void AddToTable(String rawUrl)
{
String filename = getFileName(rawUrl);
// Add filename to hash table with the current date as its value
htable.put(filename,new Date());
config.increaseFilesCached();
}
//
// clean - delete the cached files
//
public synchronized void clean()
{
System.out.println("Cleaning the cache...");
// Enumerate the hash table
for (Enumeration keys = htable.keys(); keys.hasMoreElements() ;)
{
String filename = (String)keys.nextElement();
File file = new File(filename);
long nbytes = file.length();
boolean result = file.delete();
if (result == true)
{
// Delete entry in hash table
htable.remove(filename);
config.decreaseFilesCached();
// Increment free space
config.setBytesCached(config.getBytesCached() - nbytes);
}
else
{
// Another thread holds this file open for writing
}
}
config.setHits(0);
config.setMisses(0);
System.out.println("Cache is clean.");
}
//
// Private methods
//
//
// MakeFreeSpace - throw LRU file until free space is above min level
//
private synchronized void MakeFreeSpace(String rawUrl)
{
String filename,
LRUfilename;
Date date,
minDate;
minDate = new Date();
while (config.getBytesFree() < MinFreeSpace)
{
filename = LRUfilename = null;
date = null;
if (htable.isEmpty())
{
System.out.println("Could not make free space: Hash table empty...");
return;
}
//
// Enumerate the hash table entries to find the LRU file
//
for (Enumeration keys = htable.keys(); keys.hasMoreElements() ;)
{
filename = (String)keys.nextElement();
date = (Date)htable.get(filename);
if (date.before(minDate))
LRUfilename = filename;
}
//
// Delete the LRU file
//
File LRUfile = new File(LRUfilename);
long nbytes = LRUfile.length();
boolean result = LRUfile.delete();
if (result == true)
{
// Delete entry in hash table
htable.remove(LRUfilename);
config.decreaseFilesCached();
// Increment free space
config.setBytesCached(config.getBytesCached() - nbytes);
}
else
{
// Another thread holds this file open for writing
System.out.println("File "+LRUfilename+" could not be deleted...");
return;
}
}
}
//
// Convert the URL to filename - this method parses the URL and
// generate filename only if the URL is to be cached.
// We do not cache URLs containing '?', "cgi-bin" and
// a list of not-to-cached-URLs as instructed by the proxy administrator.
//
private String getFileName(String rawUrl)
{
String filename = basePath + File.separatorChar + rawUrl.substring(7).replace('/','@');
if (filename.indexOf('?') != -1 || filename.indexOf("cgi-bin") != -1)
{
return null;
}
return filename;
}
}
Config.java
/******************************************************************
*** File Config.java
***
***/
import java.util.*;
import java.net.*;
import java.io.*;
//
// Class: Config
// Abstract: Configurable parameters of the proxy. This class is
// used by both the applet and the proxy.
//
class Config
{
//
// Member variables
//
private boolean isFatherProxy;
private String fatherProxyHost;
private int fatherProxyPort;
private String[] deniedHosts;
private String password;
private boolean isCaching; // enable/disable caching
private long cacheSize; // cache size in bytes
private boolean cleanCache;
private String[] cacheMasks;
private long filesCached;
private long bytesCached;
private long bytesFree;
private long hits;
private long misses;
private final int defaultProxyPort = 8080;
private final String defaultPassword = "admin";
private final long defaultCacheSize = 1000000;
private String adminPath;
private int adminPort;
private String localHost;
private String localIP;
private boolean isAppletContext;
private String separator = " ";
private String proxyMachineNameAndPort;
//
// Member methods
//
//
// Constructor
//
Config()
{
filesCached = 0;
bytesCached = 0;
bytesFree = cacheSize;
hits = 0;
misses = 0;
reset();
}
//
// Re-initialize
//
public void reset()
{
isFatherProxy = true;
fatherProxyHost = "wwwproxy.ac.il";
fatherProxyPort = defaultProxyPort;
password = defaultPassword;
isCaching = true;
cacheSize = defaultCacheSize;
cleanCache = false;
deniedHosts = new String[0];
cacheMasks = new String[0];
}
//
// Set/get methods
//
// Set if we are in the applet or in the proxy
public void setIsAppletContext(boolean b)
{
isAppletContext = b;
}
public void setProxyMachineNameAndPort(String s)
{
proxyMachineNameAndPort = s;
}
public String getProxyMachineNameAndPort()
{
return proxyMachineNameAndPort;
}
public int getAdminPort()
{
return adminPort;
}
public void setAdminPort(int port)
{
adminPort = port;
}
public void setAdminPath(String path)
{
adminPath = path;
}
public String getAdminPath()
{
return adminPath;
}
public void setLocalHost(String host)
{
localHost = host;
}
public String getLocalHost()
{
return localHost;
}
public void setLocalIP(String ip)
{
localIP = ip;
}
public String getLocalIP()
{
return localIP;
}
boolean getIsCaching()
{
return isCaching;
}
public synchronized void setIsCaching(boolean caching)
{
isCaching = caching;
}
public synchronized long getCacheSize()
{
return cacheSize;
}
public synchronized void setCacheSize(long size)
{
cacheSize = size;
}
public boolean getIsFatherProxy()
{
return isFatherProxy;
}
public synchronized void setIsFatherProxy(boolean fatherProxy)
{
isFatherProxy = fatherProxy;
}
public String getFatherProxyHost()
{
return fatherProxyHost;
}
public synchronized void setFatherProxyHost(String host)
{
fatherProxyHost = host;
}
public int getFatherProxyPort()
{
return fatherProxyPort;
}
public synchronized void setFatherProxyPort(int port)
{
fatherProxyPort = port;
}
public String[] getDeniedHosts()
{
return deniedHosts;
}
public synchronized void setDeniedHosts(String[] hosts)
{
deniedHosts = hosts;
}
public String getPassword()
{
return password;
}
public synchronized void setPassword(String newPassword)
{
password = newPassword;
}
public boolean getCleanCache()
{
return cleanCache;
}
public synchronized void setCleanCache(boolean clean)
{
cleanCache = clean;
}
public String[] getCacheMasks()
{
return cacheMasks;
}
public synchronized void setCacheMasks(String[] masks)
{
cacheMasks = masks;
}
public long getFilesCached()
{
return filesCached;
}
public synchronized void increaseFilesCached()
{
filesCached++;
}
public synchronized void decreaseFilesCached()
{
filesCached--;
}
public synchronized void setFilesCached(long number)
{
filesCached = number;
}
public long getBytesCached()
{
return bytesCached;
}
public synchronized void setBytesCached(long number)
{
bytesCached = number;
}
public long getBytesFree()
{
return cacheSize - bytesCached;
}
public long getHits()
{
return hits;
}
public synchronized void increaseHits()
{
hits++;
}
public synchronized void setHits(long number)
{
hits = number;
}
public long getMisses()
{
return misses;
}
public synchronized void increaseMisses()
{
misses++;
}
public synchronized void setMisses(long number)
{
misses = number;
}
public double getHitRatio()
{
if ((hits + misses)==0)
return 0;
else
return 100*hits / (hits + misses);
}
//
// Construct a string with all parameters
//
public synchronized String toString()
{
int i;
String s = "";
s += isFatherProxy + separator;
s += fatherProxyHost.equals("") ? "NULL" : fatherProxyHost;
s += separator +
fatherProxyPort + separator;
s += deniedHosts.length + separator;
for (i=0; i<deniedHosts.length; i++)
s += deniedHosts[i] + separator;
s +=
password + separator +
isCaching + separator +
cacheSize + separator +
cleanCache + separator;
s += cacheMasks.length + separator;
for (i=0; i<cacheMasks.length; i++)
s += cacheMasks[i] + separator;
s+= proxyMachineNameAndPort + separator;
s +=
filesCached + separator +
bytesCached + separator +
bytesFree + separator +
hits + separator +
misses + separator +
"\n";
return s;
}
//
// Set parameters according to a string (that was sent by applet)
//
public synchronized void parse(String config)
{
System.out.println("Parsing administrator request...");
int size,i;
StringTokenizer s = new StringTokenizer(config,separator);
isFatherProxy = s.nextToken().equals("true");
System.out.println("Use father proxy = "+isFatherProxy);
fatherProxyHost = s.nextToken();
if(fatherProxyHost.equals("NULL"))
fatherProxyHost = "";
System.out.println("Father proxy name = "+fatherProxyHost);
fatherProxyPort = Integer.parseInt(s.nextToken());
System.out.println("Father proxy port = "+fatherProxyPort);
size = Integer.parseInt(s.nextToken());
deniedHosts = new String[size];
for (i=0; i<size; i++)
{
deniedHosts[i] = s.nextToken();
System.out.println("Deny access to "+deniedHosts[i]);
}
password = s.nextToken();
System.out.println("password = "+password);
isCaching = s.nextToken().equals("true");
System.out.println("Caching = "+isCaching);
cacheSize = Long.parseLong(s.nextToken());
System.out.println("Cache size = "+cacheSize);
cleanCache = s.nextToken().equals("true");
System.out.println("Do cache clean up = "+cleanCache);
size = Integer.parseInt(s.nextToken());
cacheMasks = new String[size];
for (i=0; i<size; i++)
{
cacheMasks[i] = s.nextToken();
System.out.println("Don't cache "+cacheMasks[i]);
}
proxyMachineNameAndPort = s.nextToken();
if (isAppletContext)
{
filesCached = Long.parseLong(s.nextToken());
bytesCached = Long.parseLong(s.nextToken());
bytesFree = Long.parseLong(s.nextToken());
hits = Long.parseLong(s.nextToken());
misses = Long.parseLong(s.nextToken());
}
//
// Update bytesFree to reflect the change in cache size.
// Note that free bytes can be below min free level now.
//
bytesFree = cacheSize - bytesCached;
}
}
Daemon.c*java
/******************************************************************
*** File Daemon.c*java
***
***/
import java.net.*;
import java.io.*;
import c.net.*;
import c.io.*;
//
// Class: Daemon
// Abstract: Web daemon thread. creates main socket on port 8080
// and listens on it forever. For each client request,
// creates proxy thread to handle the request.
//
public class Daemon extends Thread
{
//
// Member variables
//
static ServerSocket MainSocket = null;
static Cache cache = null;
static Config config;
static String adminPath;
final static int defaultDaemonPort = 8080;
final static int maxDaemonPort = 65536;
//
// Member methods
//
// Application starts here
public static void main(String args[])
{
int daemonPort;
// Parse command line
switch (args.length)
{
case 0: daemonPort = defaultDaemonPort;
break;
case 1: try
{
daemonPort = Integer.parseInt(args[0]);
}
catch (NumberFormatException e)
{
System.out.println("Error: Invalid daemon port");
return;
}
if (daemonPort > maxDaemonPort)
{
System.out.println("Error: Invalid daemon port");
return;
}
break;
default:System.out.println("Usage: Proxy [daemon port]");
return;
}
try
{
// Create the Cache Manager and Configuration objects
System.out.println("Initializing...");
System.out.print("Creating Config Object...");
config = new Config();
config.setIsAppletContext(false);
config.setLocalHost(InetAddress.getLocalHost().getHostName());
String tmp = InetAddress.getLocalHost().toString();
config.setLocalIP(tmp.substring(tmp.indexOf('/')+1));
config.setProxyMachineNameAndPort(InetAddress.getLocalHost().getHostName()+":"+daemonPort);
File adminDir = new File("Applet");
config.setAdminPath(adminDir.getAbsolutePath());
System.out.println("OK");
System.out.print("Creating Cache Manager...");
cache = new Cache(config);
System.out.println("OK");
// Start the admin thread
System.out.print("Creating Admin Thread...");
Admin adminThd = new Admin(config,cache);
adminThd.start();
System.out.println(" port " + config.getAdminPort() + " OK");
// Create main socket
System.out.print("Creating Daemon Socket...");
MainSocket = new ServerSocket(daemonPort);
System.out.println(" port " + daemonPort + " OK");
if (config.getIsFatherProxy())
{
System.out.println("Using Father Proxy "+
config.getFatherProxyHost()+
":"+config.getFatherProxyPort()+" .");
}
else
{
System.out.println("Not Using Father Proxy .");
}
System.out.println("Proxy up and running!");
// Main loop
while (true)
{
// Listen on main socket
Socket ClientSocket = MainSocket.accept();
// Pass request to new proxy thread
Proxy thd = new Proxy(ClientSocket,cache,config);
thd.start();
}
}
catch (Exception e)
{}
finally
{
try
{
MainSocket.close();
}
catch (Exception exc)
{
}
}
}
}
HttpReplyHdr.java
/******************************************************************
*** File HttpReplyHdr.java
***
***/
import java.net.*;
import java.io.*;
//
// Class: HttpReplyHdr
// Abstract: The headers of the server HTTP reply.
//
/**
* Generates a http reply header. The header indicates the outcome
* of a http request.
*
*/
public class HttpReplyHdr
{
static String CR="\r\n";
static String HTTP_PROTOCOL="HTTP/1.0";
static String HTTP_SERVER="Java Proxy Server";
String lastModified ="";
long contentLength=0;
String extraErrorString ="";
/**
* Sets the last modified date for a header;
*
* @param date A string holding an interner date
* @return true
*/
public boolean setModifiedDate(String date)
{
lastModified = date;
return true;
}
/**
* Adds an extra explanation. This extra information is
* Added to the http headers failure explanation.
*
* @param str Description to add.
* @return true.
*/
public boolean addErrorDescription(String str)
{
extraErrorString = str;
return true;
}
/**
* Forms a http ok reply header
*
* @param ContentType The mime-type of the content
* @return A string with the header in it
*/
public String formOk(String ContentType,long ContentLength)
{
contentLength = ContentLength;
String out =new String();
out += HTTP_PROTOCOL + " 200 Ok" + CR;
out += "Server: " + HTTP_SERVER + CR;
out += "MIME-version: 1.0" + CR;
if (0 < ContentType.length())
out += "Content-type: " + ContentType + CR;
else
out += "Content-Type: text/html" + CR;
if (0 != contentLength)
out += "Content-Length: " + Long.toString(contentLength) + CR;
if (0 < lastModified.length())
out +="Last-Modified: " + lastModified + CR;
out +=CR;
return out;
}
/**
* private! builds an http document describing a headers reason.
*
* @param Error Error name.
* @param Description Errors description.
* @return A string with the HTML description body
*/
private String formErrorBody(String Error,String Description)
{
String out;
//Generate Error Body
out ="<HTML><HEAD><TITLE>";
out += Error ;
out +="</TITLE></HEAD>";
out +="<BODY><H2>" + Error +"</H2>\n";
out +="</P></H3>";
out += Description;
out +="</BODY></HTML>";
return out;
}
/**
* builds an http document describing an error.
*
* @param Error Error name.
* @param Description Errors description.
* @return A string with the HTML description body
*/
private String formError(String Error, String Description)
{
/* A HTTP RESPONCE HEADER LOOKS ALOT LIKE:
*
* HTTP/1.0 200 OK
* Date: Wednesday, 02-Feb-94 23:04:12 GMT
* Server: NCSA/1.1
* MIME-version: 1.0
* Last-modified: Monday, 15-Nov-93 23:33:16 GMT
* Content-type: text/html
* Content-length: 2345
* \r\n
*/
String body=formErrorBody(Error,Description);
String header =new String();
header +=HTTP_PROTOCOL +" " + Error + CR;
header +="Server: " + HTTP_SERVER + CR;
header +="MIME-version: 1.0" + CR;
header +="Content-type: text/html" + CR;
if (0 < lastModified.length())
header +="Last-Modified: " + lastModified +CR;
header +="Content-Length: " + String.valueOf(body.length())+ CR;
header += CR;
header += body;
return header;
}
/**
* Indicates a new file was created.
*
* @return The header in a string;
*/
public String formCreated()
{
return formError("201 Created","Object was created");
}
/**
* Indicates the document was accepted.
*
* @return The header in a string;
*/
public String formAccepted()
{
return formError("202 Accepted","Object checked in");
}
/**
* Indicates only a partial responce was sent.
*
* @return The header in a string;
*/
public String formPartial()
{
return formError("203 Partial","Only partail document available");
}
/**
* Indicates a requested URL has moved to a new address or name.
*
* @return The header in a string;
*/
public String formMoved()
{
//300 codes tell client to do actions
return formError("301 Moved","File has moved");
}
/**
* Never seen this used.
*
* @return The header in a string;
*/
public String formFound()
{
return formError("302 Found","Object was found");
}
/**
* The requested method is not implemented by the server.
*
* @return The header in a string;
*/
public String formMethod()
{
return formError("303 Method unseported","Method unseported");
}
/**
* Indicates remote copy of the requested object is current.
*
* @return The header in a string;
*/
public String formNotModified()
{
return formError("304 Not modified","Use local copy");
}
/**
* Client not otherized for the request.
*
* @return The header in a string;
*/
public String formUnautorized()
{
return formError("401 Unathorized","Unathorized use of this service");
}
/**
* Payment is required for service.
*
* @return The header in a string;
*/
public String formPaymentNeeded()
{
return formError("402 Payment required","Payment is required");
}
/**
* Client if forbidden to get the request service.
*
* @return The header in a string;
*/
public String formForbidden()
{
return formError("403 Forbidden","You need permission for this service");
}
/**
* The requested object was not found.
*
* @return The header in a string;
*/
public String formNotFound()
{
return formError("404 Not_found","Requested object was not found");
}
/**
* The server had a problem and could not fulfill the request.
*
* @return The header in a string;
*/
public String formInternalError()
{
return formError("500 Internal server error","Server broke");
}
/**
* Server does not do the requested feature.
*
* @return The header in a string;
*/
public String formNotImplemented()
{
return formError("501 Method not implemented","Service not implemented, programer was lazy");
}
/**
* Server is overloaded, client should try again latter.
*
* @return The header in a string;
*/
public String formOverloaded()
{
return formError("502 Server overloaded","Try again latter");
}
/**
* Indicates the request took to long.
*
* @return The header in a string;
*/
public String formTimeout()
{
return formError("503 Gateway timeout","The connection timed out");
}
/**
* Indicates the client's proxies could not locate a server.
*
* @return The header in a string;
*/
public String formServerNotFound()
{
return formError("503 Gateway timeout","The requested server was not found");
}
/**
* Indicates the client is not allowed to access the object.
*
* @return The header in a string;
*/
public String formNotAllowed()
{
return formError("403 Access Denied","Access is not allowed");
}
}
HttpRequestHdr.java
/******************************************************************
*** File HttpRequestHdr.java
***
***/
import java.io.InputStream;
import java.io.DataInputStream;
import java.util.StringTokenizer;
//
// Class: HttpRequestHdr
// Abstract: The headers of the client HTTP request.
//
/**
* Parses and stores a http server request.
*
*/
public class HttpRequestHdr
{
/**
* Http Request method. Such as get or post.
*/
public String method = new String();
/**
* The requested url. The universal resource locator that
* hopefully uniquely describes the object or service the
* client is requesting.
*/
public String url = new String();
/**
* Version of http being used. Such as HTTP/1.0
*/
public String version = new String();
/**
* The client's browser's name.
*/
public String userAgent = new String();
/**
* The requesting documents that contained the url link.
*/
public String referer = new String();
/**
* A internet address date of the remote copy.
*/
public String ifModifiedSince = new String();
/**
* A list of mime types the client can accept.
*/
public String accept = new String();
/**
* The clients authorization. Don't belive it.
*/
public String authorization = new String();
/**
* The type of content following the request header.
* Normally there is no content and this is blank, however
* the post method usually does have a content and a content
* length.
*/
public String contentType = new String();
/**
* The length of the content following the header. Usually
* blank.
*/
public int contentLength = -1;
/**
* The content length of a remote copy of the requested object.
*/
public int oldContentLength = -1;
/**
* Anything in the header that was unrecognized by this class.
*/
public String unrecognized = new String();
/**
* Indicates that no cached versions of the requested object are
* to be sent. Usually used to tell proxy not to send a cached copy.
* This may also effect servers that are front end for data bases.
*/
public boolean pragmaNoCache = false;
static String CR ="\r\n";
/**
* Parses a http header from a stream.
*
* @param in The stream to parse.
* @return true if parsing sucsessfull.
*/
public boolean parse(InputStream In)
{
String CR ="\r\n";
/*
* Read by lines
*/
DataInputStream lines;
StringTokenizer tz;
try
{
lines = new DataInputStream(In);
tz = new StringTokenizer(lines.readLine());
}
catch (Exception e)
{
return false;
}
/*
* HTTP COMMAND LINE < <METHOD==get> <URL> <HTTP_VERSION> >
*/
method = getToken(tz).toUpperCase();
url = getToken(tz);
version= getToken(tz);
while (true)
{
try
{
tz = new StringTokenizer(lines.readLine());
}
catch (Exception e)
{
return false;
}
String Token = getToken(tz);
// look for termination of HTTP command
if (0 == Token.length())
break;
if (Token.equalsIgnoreCase("USER-AGENT:")) {
// line =<User-Agent: <Agent Description>>
userAgent = getRemainder(tz);
} else if (Token.equalsIgnoreCase("ACCEPT:")) {
// line=<Accept: <Type>/<Form>
// examp: Accept image/jpeg
accept += " " + getRemainder(tz);
} else if (Token.equalsIgnoreCase("REFERER:")) {
// line =<Referer: <URL>>
referer = getRemainder(tz);
} else if (Token.equalsIgnoreCase("PRAGMA:")) {
// Pragma: <no-cache>
Token = getToken(tz);
if (Token.equalsIgnoreCase("NO-CACHE"))
pragmaNoCache = true;
else
unrecognized += "Pragma:" + Token + " "
+getRemainder(tz) +"\n";
} else if (Token.equalsIgnoreCase("AUTHORIZATION:")) {
// Authenticate: Basic UUENCODED
authorization= getRemainder(tz);
} else if (Token.equalsIgnoreCase("IF-MODIFIED-SINCE:")) {
// line =<If-Modified-Since: <http date>
// *** Conditional GET replaces HEAD method ***
String str = getRemainder(tz);
int index = str.indexOf(";");
if (index == -1) {
ifModifiedSince =str;
} else {
ifModifiedSince =str.substring(0,index);
index = str.indexOf("=");
if (index != -1) {
str = str.substring(index+1);
oldContentLength =Integer.parseInt(str);
}
}
} else if (Token.equalsIgnoreCase("CONTENT-LENGTH:")) {
Token = getToken(tz);
contentLength =Integer.parseInt(Token);
} else if (Token.equalsIgnoreCase("CONTENT-TYPE:")) {
contentType = getRemainder(tz);
} else {
unrecognized += Token + " " + getRemainder(tz) + CR;
}
}
return true;
}
/*
* Rebuilds the header in a string
* @returns The header in a string.
*/
public String toString(boolean sendUnknowen) {
String Request;
if (0 == method.length())
method = "GET";
Request = method +" "+ url + " HTTP/1.0" + CR;
if (0 < userAgent.length())
Request +="User-Agent:" + userAgent + CR;
if (0 < referer.length())
Request+= "Referer:"+ referer + CR;
if (pragmaNoCache)
Request+= "Pragma: no-cache" + CR;
if (0 < ifModifiedSince.length())
Request+= "If-Modified-Since: " + ifModifiedSince + CR;
// ACCEPT TYPES //
if (0 < accept.length())
Request += "Accept: " + accept + CR;
else
Request += "Accept: */"+"* \r\n";
if (0 < contentType.length())
Request += "Content-Type: " + contentType + CR;
if (0 < contentLength)
Request += "Content-Length: " + contentLength + CR;
if (0 != authorization.length())
Request += "Authorization: " + authorization + CR;
if (sendUnknowen) {
if (0 != unrecognized.length())
Request += unrecognized;
}
Request += CR;
return Request;
}
/**
* (Re)builds the header in a string.
*
* @returns The header in a string.
*/
public String toString() {
return toString(true);
}
/**
* Returns the next token in a string
*
* @param tk String that is partially tokenized.
* @returns The remainder
*/
String getToken(StringTokenizer tk){
String str ="";
if (tk.hasMoreTokens())
str =tk.nextToken();
return str;
}
/**
* Returns the remainder of a tokenized string
*
* @param tk String that is partially tokenized.
* @returns The remainder
*/
String getRemainder(StringTokenizer tk){
String str ="";
if (tk.hasMoreTokens())
str =tk.nextToken();
while (tk.hasMoreTokens()){
str +=" " + tk.nextToken();
}
return str;
}
}
Proxy.java
/******************************************************************
*** File Proxy.java
***
***/
import java.net.*;
import java.io.*;
import java.util.*;
//
// Class: Proxy
// Abstract: Thread to handle one client request. get the requested
// object from the web server or from the cache, and delivers
// the bits to client.
//
public class Proxy extends Thread
{
//
// Member variables
//
Socket ClientSocket = null; // Socket to client
Socket SrvrSocket = null; // Socket to web server
Cache cache = null; // Static cache manager object
String localHostName = null; // Local machine name
String localHostIP = null; // Local machine IP address
String adminPath = null; // Path of admin applet
Config config = null; // Config object
//
// Public member methods
//
//
// Constructor
//
Proxy(Socket clientSocket, Cache CacheManager, Config configObject)
{
//
// Initialize member variables
//
config = configObject;
ClientSocket = clientSocket;
cache = CacheManager;
localHostName = config.getLocalHost();
localHostIP = config.getLocalIP();
adminPath = config.getAdminPath();
}
//
// run - Main work is done here:
//
public void run()
{
String serverName ="";
URL url;
byte line[];
HttpRequestHdr request = new HttpRequestHdr();
HttpReplyHdr reply = new HttpReplyHdr();
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
boolean TakenFromCache = false;
boolean isCachable = false;
try
{
//
// Read HTTP Request from client
//
request.parse(ClientSocket.getInputStream());
url = new URL(request.url);
System.out.println("Request = " + url);
//
// Send Web page with applet to administrator
//
if (url.getFile().equalsIgnoreCase("/admin") &&
(url.getHost().equalsIgnoreCase(localHostName) ||
url.getHost().equalsIgnoreCase(localHostIP) ))
{
sendAppletWebPage();
return;
}
//
// Send Applet Files to administrator
//
if ((url.getHost().equalsIgnoreCase(localHostName) ||
url.getHost().equalsIgnoreCase(localHostIP) ))
{
sendAppletClass(url.getFile());
return;
}
//
// Check if accessing the URL is allowed by administrator
//
String[] denied = config.getDeniedHosts();
for (int i=0; i<denied.length; i++)
{
if (url.toString().indexOf(denied[i]) != -1)
{
System.out.println("Access not allowed...");
DataOutputStream out =
new DataOutputStream(ClientSocket.getOutputStream());
out.writeBytes(reply.formNotAllowed());
out.flush();
ClientSocket.close();
return;
}
}
//
// Client wants a web page - let's see if we have it in cache
//
if (cache.IsCached(url.toString()))
{
//
// Client request is allready cached - get it from file
//
System.out.println("Hit! Getting from cache!!!");
config.increaseHits();
TakenFromCache = true;
// Get FileInputStream from Cache Manager
fileInputStream = cache.getFileInputStream(url.toString());
OutputStream out = ClientSocket.getOutputStream();
// Send the bits to client
byte data[] = new byte[2000];
int count;
while (-1 < ( count = fileInputStream.read(data)))
{
out.write(data,0,count);
}
out.flush();
fileInputStream.close();
}
//
// We do not have the page in cache
//
else
{
//
// Open socket to web server (or father proxy)
//
if (config.getIsFatherProxy())
{
System.out.println("Miss! Forwarding to father proxy " +
config.getFatherProxyHost() + ":" +
config.getFatherProxyPort() + "...");
config.increaseMisses();
SrvrSocket = new Socket(config.getFatherProxyHost(),
config.getFatherProxyPort());
}
else
{
serverName = url.getHost();
System.out.println("Miss! Forwarding to server "+
serverName + "...");
config.increaseMisses();
SrvrSocket = new Socket(serverName(request.url),
serverPort(request.url));
request.url = serverUrl(request.url);
}
DataOutputStream srvOut =
new DataOutputStream(SrvrSocket.getOutputStream());
//
// Send the url to web server (or father proxy)
//
srvOut.writeBytes(request.toString(false));
srvOut.flush();
//
// Send data to server (needed for post method)
//
for (int i =0; i < request.contentLength; i++)
{
SrvrSocket.getOutputStream().write(ClientSocket.getInputStream().read());
}
SrvrSocket.getOutputStream().flush();
//
// Find if reply should be cached -
// First, check if caching is on.
//
isCachable = config.getIsCaching();
String reasonForNotCaching = "Caching is OFF.";
// Second, parse the URL for special characters.
if (isCachable)
{
isCachable = cache.IsCachable(url.toString());
reasonForNotCaching = "URL not cacheable";
}
// Third, check reply headers (we must read first
// line of headers for that).
DataInputStream Din =
new DataInputStream(SrvrSocket.getInputStream());
DataOutputStream Dout =
new DataOutputStream(ClientSocket.getOutputStream());
String str = Din.readLine();
StringTokenizer s = new StringTokenizer(str);
String retCode = s.nextToken(); // first token is HTTP protocol
retCode = s.nextToken(); // second is return code
// Return codes 200,302,304 are OK to cache
if (isCachable)
{
if (!retCode.equals("200") && !retCode.equals("302")
&& !retCode.equals("304"))
{
isCachable = false;
reasonForNotCaching = "Return Code is "+retCode;
}
}
// Fourth, check if URL is cache-allowed by administrator
if (isCachable)
{
String[] denyCache = config.getCacheMasks();
for (int i=0; i<denyCache.length; i++)
{
if (url.toString().indexOf(denyCache[i]) != -1)
{
isCachable = false;
reasonForNotCaching = "Caching this URL is not allowed";
break;
}
}
}
if (isCachable)
{
System.out.println("Caching the reply...");
fileOutputStream = cache.getFileOutputStream(url.toString());
}
else
{
System.out.println("NOT Caching the reply. Reason:"
+reasonForNotCaching);
}
//
// First line was read - send it to client and cache it
//
String tempStr = new String(str+"\r\n");
Dout.writeBytes(tempStr);
if (isCachable)
{
// Translate reply string to bytes
line = new byte[tempStr.length()];
tempStr.getBytes(0,tempStr.length(),line,0);
// Write bits to file
fileOutputStream.write(line);
cache.DecrementFreeSpace(line.length,url.toString());
}
//
// Read next lines in reply header, send them to
// client and cache them
//
if (str.length() > 0)
while (true)
{
str = Din.readLine();
tempStr = new String(str+"\r\n");
// Send bits to client
Dout.writeBytes(tempStr);
if (isCachable)
{
// Translate reply string to bytes
line = new byte[tempStr.length()];
tempStr.getBytes(0,tempStr.length(),line,0);
// Write bits to file
fileOutputStream.write(line);
cache.DecrementFreeSpace(line.length,url.toString());
}
if (str.length() <= 0)
break;
}
Dout.flush();
//
// With the HTTP reply body do:
// (1) Send it to client.
// (2) Cache it.
//
InputStream in = SrvrSocket.getInputStream();
OutputStream out = ClientSocket.getOutputStream();
byte data[] = new byte[2000];
int count;
while (( count = in.read(data)) > 0)
{
// Send bits to client
out.write(data,0,count);
if (isCachable)
{
// Write bits to file
line = new byte[count];
System.arraycopy(data,0,line,0,count);
fileOutputStream.write(line);
cache.DecrementFreeSpace(count,url.toString());
}
}
out.flush();
if (isCachable)
{
fileOutputStream.close();
// Add new entry to hash table
cache.AddToTable(url.toString());
}
}
}
catch (UnknownHostException uhe)
{
//
// Requested Server could not be located
//
System.out.println("Server Not Found.");
try
{
// Notify client that server not found
DataOutputStream out =
new DataOutputStream(ClientSocket.getOutputStream());
out.writeBytes(reply.formServerNotFound());
out.flush();
}
catch (Exception uhe2)
{}
}
catch (Exception e)
{
try
{
if (TakenFromCache)
fileInputStream.close();
else if (isCachable)
fileOutputStream.close();
// Notify client that internal error accured in proxy
DataOutputStream out =
new DataOutputStream(ClientSocket.getOutputStream());
out.writeBytes(reply.formTimeout());
out.flush();
}
catch (Exception uhe2)
{}
}
finally
{
try
{
ClientSocket.getOutputStream().flush();
ClientSocket.close();
}
catch (Exception e)
{}
}
}
//
// Private methods
//
//
// Send to administrator web page containing reference to applet
//
private void sendAppletWebPage()
{
System.out.println("Sending the applet...");
String page = "";
try
{
File appletHtmlPage = new File(config.getAdminPath() +
File.separator + "Admin.html");
DataInputStream in = new DataInputStream(new FileInputStream(appletHtmlPage));
String s = null;
while((s = in.readLine()) != null)
page += s;
page = page.substring(0,page.indexOf("PORT")) +
config.getAdminPort() +
page.substring(page.indexOf("PORT")+4);
in.close();
DataOutputStream out = new DataOutputStream(ClientSocket.getOutputStream());
out.writeBytes(page);
out.flush();
out.close();
}
catch (Exception e)
{
System.out.println("Error: can't open applet html page");
}
}
//
// Send the applet to administrator
//
private void sendAppletClass(String className)
{
try
{
byte data[] = new byte[2000];
int count;
HttpReplyHdr reply = new HttpReplyHdr();
File appletFile = new File(adminPath + File.separatorChar + className);
long length = appletFile.length();
FileInputStream in = new FileInputStream(appletFile);
DataOutputStream out = new DataOutputStream(ClientSocket.getOutputStream());
out.writeBytes(reply.formOk("application/octet-stream",length));
while (-1 < ( count = in.read(data)))
{
out.write(data,0,count);
}
out.flush();
in.close();
out.close();
}
catch (Exception e)
{}
}
//
// Parsing Methods
//
/**
* Find the //server.name from an url.
*
* @return Servers internet name
*/
private String serverName(String str)
{
// chop to "server.name:x/thing"
int i = str.indexOf("//");
if (i< 0) return "";
str = str.substring(i+2);
// chop to server.name:xx
i = str.indexOf("/");
if (0 < i) str = str.substring(0,i);
// chop to server.name
i = str.indexOf(":");
if (0 < i) str = str.substring(0,i);
return str;
}
/**
* Find the :PORT form http://server.ect:PORT/some/file.xxx
*
* @return Servers internet name
*/
private int serverPort(String str)
{
// chop to "server.name:x/thing"
int i = str.indexOf("//");
if (i< 0) return 80;
str = str.substring(i+2);
// chop to server.name:xx
i = str.indexOf("/");
if (0 < i) str = str.substring(0,i);
// chop XX
i = str.indexOf(":");
if (0 < i)
{
return Integer.parseInt(str.substring(i).trim());
}
return 80;
}
/**
* Find the /some/file.xxxx form http://server.ect:PORT/some/file.xxx
*
* @return the deproxied url
*/
private String serverUrl(String str)
{
int i = str.indexOf("//");
if (i< 0) return str;
str = str.substring(i+2);
i = str.indexOf("/");
if (i< 0) return str;
return str.substring(i);
}
}