--- /dev/null
+// Client.java\r
+// $Id: Client.java,v 1.1 2010/06/15 12:25:44 smhuang Exp $\r
+// (c) COPYRIGHT MIT, INRIA and Keio, 2001.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.www.protocol.http.cache.push;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.io.DataInputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.ByteArrayInputStream;\r
+import java.io.ByteArrayOutputStream;\r
+\r
+import java.net.Socket;\r
+import java.net.InetAddress;\r
+import java.net.ConnectException;\r
+import java.net.UnknownHostException;\r
+\r
+/**\r
+ * PushCache Client - send messages to push cache server \r
+ *\r
+ * @author Paul Henshaw, The Fantastic Corporation, Paul.Henshaw@fantastic.com\r
+ * @version $Revision: 1.1 $\r
+ * $Id: Client.java,v 1.1 2010/06/15 12:25:44 smhuang Exp $\r
+ */\r
+public class Client {\r
+ /** \r
+ * Return/exit values\r
+ */\r
+ public static final int OK=0, NO=1, FAILED=-1;\r
+\r
+ /**\r
+ * How many times should we try to reconnect? 10\r
+ */\r
+ public static final int MAX_TRIES=10;\r
+ \r
+ /**\r
+ * Default hostname - localhost \r
+ */\r
+ public static final String DEFAULT_SERVER="localhost";\r
+\r
+ /**\r
+ * Version Control Revision\r
+ */\r
+ public static final String VC_REVISION="$Revision: 1.1 $";\r
+\r
+ /**\r
+ * Version Control Id \r
+ */\r
+ public static final String VC_ID=\r
+ "$Id: Client.java,v 1.1 2010/06/15 12:25:44 smhuang Exp $";\r
+\r
+ private int _verbose=0;\r
+ private int _port=-1;\r
+ private String _host_name="";\r
+ private byte[] _buffer=null;\r
+ private byte[] _text_buffer=null;\r
+ private Socket _socket=null;\r
+ private InputStream _in=null;\r
+ private OutputStream _out=null;\r
+ private DataInputStream _data_stream=null;\r
+ private ByteArrayOutputStream _baos=null;\r
+ private DataOutputStream _dos=null;\r
+ private int _reply_com=-1;\r
+ private String _reply_command;\r
+\r
+ /**\r
+ * Construct a Client for the default hostname and port number \r
+ */\r
+ public Client() {\r
+ _host_name=DEFAULT_SERVER;\r
+ _port=PushCacheFilter.DEFAULT_PORT_NUM;\r
+ }\r
+\r
+ /**\r
+ * Construct a Client for the specified hostname and port number\r
+ */\r
+ public Client(String hostname, int port) {\r
+ _host_name=hostname;\r
+ _port=port;\r
+ }\r
+\r
+ /**\r
+ * Construct client with command line arguments and execute\r
+ * command. Called by main, for use as stand alone program - \r
+ * calls System.exit at the end of execution \r
+ */\r
+ public Client(String[] argv) throws IOException {\r
+ handleArgs(argv);\r
+ }\r
+\r
+ /**\r
+ * Display version information\r
+ */\r
+ protected void version() {\r
+ System.err.println("Jigsaw Push Cache Java Client 1.0 "+\r
+ "by Paul Henshaw, The Fantastic Corporation\n"+\r
+ VC_REVISION+"\n"+VC_ID+"\n");\r
+ }\r
+\r
+ /**\r
+ * Display usage/help message\r
+ */\r
+ protected void usage() {\r
+ System.err.println("Usage: -c <command> [-u <url>] [ -p <path>] "+\r
+ "[ -s <server>] [ -P <port> ] "+\r
+ "[ -v ] [ -h ] [ -V ]\n"+\r
+ "\t-s\t\t connect to specified server, default is "+\r
+ DEFAULT_SERVER+".\n"+\r
+ "\t-P\t\t connect to specified port, default is "+\r
+ PushCacheFilter.DEFAULT_PORT_NUM+".\n"+\r
+ "\t-v\t\t verbose operation, repeat for more verbosity.\n"+\r
+ "\t-h\t\t show this help message.\n"+\r
+ "\t-V\t\t show version information.\n\n"+\r
+ "Commands recognised:\n"+\r
+ "\tADD\t\t add <path> to cache as if downloaded from <url>.\n"+\r
+ "\tDEL\t\t delete <url> from cache.\n"+\r
+ "\tPRS\t\t check if <url> is present in cache.\n"+\r
+ "\tCLN\t\t clean cache.\n"+\r
+ "\tNOP\t\t do nothing.\n\n"+\r
+ "Notes:\n"+\r
+ "\tURLs should be fully qualified including trailing slashes\n"+\r
+ "\tfor directories, e.g.:\n"+\r
+ "\t\thttp://www.fantastic.com/\n\trather than:"+\r
+ "\n\t\thttp://www.fantastic.com\n\n"+\r
+ "\tBy default the operation of the PRS command is silent, \n"+\r
+ "\tuse the -v flag to display a text message indicating the\n"+\r
+ "\tpresence of a URL in the cache.\n\n"+\r
+ "\tWhen ADDing a file only the <path> is sent to the server\n"+\r
+ "\tNOT the contents of the file, this means that files can\n"+\r
+ "\tbe added to remote caches only if the client and server\n"+\r
+ "\tshare a file system via NFS or similar.\n\n"+\r
+ "Exit Value:\n"+\r
+ "\tThis program exits with one of the following values:\n"+\r
+ "\t0\t OK, command successful (PRS: URL present)\n"+\r
+ "\t1\t (PRS only) command successful, URL not present\n"+\r
+ "\t255\t command unsuccessful\n");\r
+ }\r
+\r
+ /**\r
+ * Interpret command line arguments\r
+ */\r
+ protected void handleArgs(String[] argv) throws IOException {\r
+ String url=null,path=null,com=null;\r
+ char c;\r
+ boolean got_com=false;\r
+ boolean error=false;\r
+\r
+ for(int i=0; i<argv.length; i++) {\r
+ if(argv[i].charAt(0)!='-' || argv[i].length()!=2) {\r
+ error=true;\r
+ break;\r
+ }\r
+ c=argv[i].charAt(1);\r
+ switch(c) {\r
+ case 'c':\r
+ if(i==argv.length-1) {\r
+ System.err.println("Missing argument for -"+c);\r
+ error=true;\r
+ break;\r
+ }\r
+ com=argv[++i].toUpperCase()+"\0";\r
+ got_com=true;\r
+ break;\r
+\r
+ case 'u':\r
+ if(i==argv.length-1) {\r
+ System.err.println("Missing argument for -"+c);\r
+ error=true;\r
+ break;\r
+ }\r
+ url=argv[++i];\r
+ break;\r
+ \r
+ case 'p':\r
+ if(i==argv.length-1) {\r
+ System.err.println("Missing argument for -"+c);\r
+ error=true;\r
+ break;\r
+ }\r
+ path=argv[++i];\r
+ break;\r
+ \r
+ case 's':\r
+ if(i==argv.length-1) {\r
+ System.err.println("Missing argument for -"+c);\r
+ error=true;\r
+ break;\r
+ }\r
+ _host_name=argv[++i];\r
+ break;\r
+ \r
+ case 'P':\r
+ if(i==argv.length-1) {\r
+ System.err.println("Missing argument for -"+c);\r
+ error=true;\r
+ break;\r
+ }\r
+ try {\r
+ _port=Integer.parseInt(argv[++i]);\r
+ }\r
+ catch(Exception e) {\r
+ System.err.println("Invalid port number \""+argv[i]+"\"");\r
+ _port=-1;\r
+ error=true;\r
+ }\r
+ break;\r
+ \r
+ case 'v':\r
+ ++_verbose;\r
+ break;\r
+ \r
+ case 'h':\r
+ usage();\r
+ System.exit(OK);\r
+ \r
+ case 'V':\r
+ version();\r
+ System.exit(OK);\r
+ \r
+ default:\r
+ error=true;\r
+ }\r
+ }\r
+\r
+ if(!got_com) {\r
+ System.err.println("No command specified");\r
+ error=true;\r
+ }\r
+\r
+ if(error) {\r
+ usage();\r
+ System.exit(FAILED);\r
+ }\r
+\r
+ if(_port==-1) {\r
+ _port=PushCacheFilter.DEFAULT_PORT_NUM;\r
+ }\r
+\r
+ if(_host_name.length()==0) {\r
+ _host_name=DEFAULT_SERVER;\r
+ }\r
+\r
+ int ev=0;\r
+ try {\r
+ switch(PushCacheProtocol.instance().parseCommand(com)) {\r
+ case PushCacheProtocol.ADD:\r
+ add(path,url);\r
+ break;\r
+ case PushCacheProtocol.DEL:\r
+ del(url);\r
+ break;\r
+ case PushCacheProtocol.PRS:\r
+ if(!isPresent(url)) {\r
+ ev=1;\r
+ }\r
+ break;\r
+ default:\r
+ simpleCommand(com);\r
+ }\r
+ }\r
+ catch(IllegalArgumentException e) {\r
+ System.err.println(e.getMessage());\r
+ usage();\r
+ ev=FAILED;\r
+ }\r
+ sendBye();\r
+ System.exit(ev);\r
+ }\r
+\r
+ /**\r
+ * Connect to server - try up to MAX_TRIES times, then give up\r
+ */\r
+ protected void connect() throws UnknownHostException, IOException {\r
+ int tries=0;\r
+ while(_socket==null) {\r
+ try {\r
+ _socket=new Socket(_host_name,_port);\r
+ _in=_socket.getInputStream();\r
+ _out=_socket.getOutputStream();\r
+ }\r
+ catch(ConnectException e) {\r
+ System.err.println("Failed to connect to "+_host_name+\r
+ " on port "+_port+": "+e.getMessage());\r
+ if(++tries>MAX_TRIES) {\r
+ throw new \r
+ ConnectException("Failed to connect to "+_host_name+\r
+ " on port "+_port+" after "+ \r
+ MAX_TRIES+" attempts:\n"+e);\r
+ }\r
+\r
+ System.err.println("Retrying....");\r
+ try {\r
+ Thread.sleep(2000);\r
+ }\r
+ catch(InterruptedException ex) {\r
+ // IGNORE\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Write header information into packet buffer\r
+ */\r
+ protected void writeHeader() throws IOException {\r
+ _baos=new ByteArrayOutputStream();\r
+ _dos=new DataOutputStream(_baos);\r
+ _dos.write(PushCacheProtocol.instance().header(),0,\r
+ PushCacheProtocol.instance().header().length);\r
+ }\r
+\r
+ /**\r
+ * Read remaining payload length bytes into buffer\r
+ */\r
+ protected void readPayload() throws IOException {\r
+ _data_stream.skipBytes(PushCacheProtocol.COMMAND_LEN);\r
+ int rlen=_data_stream.readInt();\r
+ \r
+ if(rlen==0) {\r
+ return;\r
+ }\r
+ _text_buffer=new byte[rlen];\r
+\r
+ int sofar=0;\r
+ int toread=rlen;\r
+\r
+ sofar=0;\r
+ toread=rlen;\r
+ int rv=-1;\r
+ while(toread>0) {\r
+ rv=_in.read(_text_buffer,sofar,toread);\r
+ if(rv<0) {\r
+ throw new IOException("read returned "+rv);\r
+ }\r
+ sofar+=rv;\r
+ toread-=rv;\r
+ }\r
+ _data_stream=new DataInputStream(new \r
+ ByteArrayInputStream(_text_buffer));\r
+ }\r
+\r
+ /**\r
+ * Await reply packet\r
+ */\r
+ protected void readReply() throws IOException {\r
+ // Read packet\r
+ _buffer=new byte[PushCacheProtocol.PACKET_LEN];\r
+ int rv=_in.read(_buffer);\r
+ if(rv<0) {\r
+ throw new IOException("read returned "+rv);\r
+ }\r
+ if(rv<PushCacheProtocol.PACKET_LEN) {\r
+ throw new IOException("read returned less than PACKET_LEN bytes "+\r
+ rv);\r
+ }\r
+\r
+ // Check protocol tag\r
+ if(!PushCacheProtocol.instance().isValidProtocolTag(_buffer)) {\r
+ throw new IOException("Bad protocol tag");\r
+ }\r
+\r
+ // Use a DataInputStream to read bytes and shorts\r
+ _data_stream=\r
+ new DataInputStream(new ByteArrayInputStream(_buffer));\r
+ _data_stream.skipBytes(PushCacheProtocol.TAG_LEN);\r
+\r
+ // Check protol version\r
+ short maj=_data_stream.readShort();\r
+ short min=_data_stream.readShort();\r
+ if(maj!=PushCacheProtocol.MAJ_PROTO_VERSION || \r
+ min>PushCacheProtocol.MIN_PROTO_VERSION) {\r
+ throw new IOException("Bad protocol version");\r
+ }\r
+\r
+ //\r
+ // Check command\r
+ // Note that check includes NULL characters they are\r
+ // part of the command. \r
+ //\r
+ _reply_command=new String(_buffer, PushCacheProtocol.HEADER_LEN, \r
+ PushCacheProtocol.COMMAND_LEN);\r
+ _reply_com=PushCacheProtocol.instance().parseCommand(_reply_command);\r
+ readPayload();\r
+ }\r
+\r
+ /**\r
+ * Handle an ERR message from server - cleanup, throw IOException\r
+ */\r
+ protected void serverError() throws IOException {\r
+ cleanup();\r
+ throw new IOException("Recieved error message from server:\n"\r
+ +new String(_text_buffer));\r
+ }\r
+\r
+ /**\r
+ * Handle an unexpected message from server - send BYE, throw IOException\r
+ */\r
+ protected void unexpectedReply() throws IOException {\r
+ sendBye();\r
+ throw new IOException("Unexpected reply from server "+\r
+ _reply_command);\r
+ }\r
+\r
+ /**\r
+ * Cleanup, shutdown and close socket and streams\r
+ */\r
+ protected void cleanup() {\r
+ try {\r
+ if(_in!=null) {\r
+ _in.close();\r
+ _in=null;\r
+ }\r
+ if(_out!=null) {\r
+ _out.close();\r
+ _out=null;\r
+ }\r
+ if(_socket!=null) {\r
+// _socket.shutdownInput();\r
+// _socket.shutdownOutput();\r
+ _socket.close();\r
+ _socket=null;\r
+ }\r
+ }\r
+ catch(java.io.IOException e) {\r
+ // IGNORE\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Send a BYE packet, and cleanup\r
+ */\r
+ public void sendBye() throws IOException {\r
+ if(_out!=null) {\r
+ writeHeader();\r
+ _dos.writeBytes("BYE\0");\r
+ _dos.writeInt(0);\r
+ _baos.writeTo(_out);\r
+ cleanup();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Add file into cache as url\r
+ */\r
+ public void add(String path, String url) \r
+ throws IOException, IllegalArgumentException\r
+ {\r
+ if(url==null || url.length()==0 || path==null || path.length()==0) {\r
+ throw new IllegalArgumentException("Zero length path or url");\r
+ }\r
+\r
+ connect();\r
+ writeHeader();\r
+ _dos.writeBytes("ADD\0"); \r
+ // ulen+plen+2*'\0'+ 2*int\r
+ _dos.writeInt(url.length()+path.length()+10); \r
+ _dos.writeInt(path.length()+1);\r
+ _dos.writeInt(url.length()+1);\r
+ _dos.writeBytes(path+"\0");\r
+ _dos.writeBytes(url+"\0");\r
+ _baos.writeTo(_out);\r
+\r
+ readReply();\r
+ switch(_reply_com) {\r
+ case PushCacheProtocol.OK:\r
+ break;\r
+ case PushCacheProtocol.ERR:\r
+ serverError();\r
+ break;\r
+ default:\r
+ unexpectedReply();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Throws IllegalArgumentException iff com is not a null \r
+ * terminated string of the correct length\r
+ */\r
+ protected void checkCommand(String com) throws IllegalArgumentException {\r
+ if(com.length()!=PushCacheProtocol.COMMAND_LEN) {\r
+ throw new IllegalArgumentException("Command \""+com+\r
+ "\" is wrong length");\r
+ }\r
+ if(com.charAt(3)!='\0') {\r
+ throw new IllegalArgumentException("Command \""+com+\r
+ "\" is not null terminated");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Send a packet with specified command and a url\r
+ */\r
+ public void urlCommand(String com, String url) \r
+ throws IOException, IllegalArgumentException \r
+ {\r
+ if(url==null || url.length()==0) {\r
+ throw new IllegalArgumentException("Zero length url");\r
+ }\r
+ checkCommand(com);\r
+ connect();\r
+ writeHeader();\r
+ _dos.writeBytes(com);\r
+ _dos.writeInt(url.length()+1);\r
+ _dos.writeBytes(url+"\0");\r
+ _baos.writeTo(_out);\r
+ }\r
+\r
+ /**\r
+ * Remove url from cache\r
+ */\r
+ public void del(String url) throws IOException {\r
+ urlCommand("DEL\0",url);\r
+ readReply();\r
+ switch(_reply_com) {\r
+ case PushCacheProtocol.OK:\r
+ break;\r
+ case PushCacheProtocol.ERR:\r
+ serverError();\r
+ break;\r
+ default:\r
+ unexpectedReply();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * True iff url is present in cache\r
+ */ \r
+ public boolean isPresent(String url) throws IOException {\r
+ urlCommand("PRS\0", url);\r
+ readReply();\r
+ switch(_reply_com) {\r
+ case PushCacheProtocol.OK:\r
+ if(_verbose>0) {\r
+ System.err.println(url+" is present");\r
+ }\r
+ return(true);\r
+\r
+ case PushCacheProtocol.NO:\r
+ if(_verbose>0) {\r
+ System.err.println(url+" is not present");\r
+ }\r
+ return(false);\r
+\r
+ case PushCacheProtocol.ERR:\r
+ serverError();\r
+ break;\r
+ default:\r
+ unexpectedReply();\r
+ }\r
+ return(false);\r
+ }\r
+\r
+ /**\r
+ * Send a simple packet with specified command\r
+ */\r
+ public void simpleCommand(String com) throws IOException {\r
+ checkCommand(com);\r
+ connect();\r
+ writeHeader();\r
+ _dos.writeBytes(com);\r
+ _dos.writeInt(0);\r
+ _baos.writeTo(_out);\r
+\r
+ readReply();\r
+ switch(_reply_com) {\r
+ case PushCacheProtocol.OK:\r
+ break;\r
+ case PushCacheProtocol.ERR:\r
+ serverError();\r
+ break;\r
+ default:\r
+ unexpectedReply();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Send a NOP packet\r
+ */\r
+ public void nop() throws IOException {\r
+ simpleCommand("NOP\0");\r
+ }\r
+\r
+ /**\r
+ * Clean cache - remove all entries\r
+ */\r
+ public void clean() throws IOException {\r
+ simpleCommand("CLN\0");\r
+ }\r
+\r
+ /**\r
+ * For use as stand alone program - constructs Client with argv\r
+ */\r
+ public static void main(String[] argv) {\r
+ try {\r
+ Client c=new Client(argv);\r
+ }\r
+ catch(Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+}\r