View Javadoc

1   package org.eclipse.jetty.websocket;
2   
3   import java.io.BufferedReader;
4   import java.io.BufferedWriter;
5   import java.io.IOException;
6   import java.io.InputStreamReader;
7   import java.io.OutputStreamWriter;
8   import java.net.Socket;
9   import java.security.SecureRandom;
10  import java.util.Arrays;
11  import java.util.Random;
12  import java.util.concurrent.BlockingQueue;
13  import java.util.concurrent.LinkedBlockingQueue;
14  
15  import org.eclipse.jetty.io.Buffer;
16  import org.eclipse.jetty.io.bio.SocketEndPoint;
17  import org.eclipse.jetty.util.B64Code;
18  import org.eclipse.jetty.util.StringUtil;
19  import org.eclipse.jetty.util.TypeUtil;
20  import org.eclipse.jetty.util.log.Log;
21  
22  /**
23   * @version $Revision: 3052 $ $Date: 2011-05-02 11:42:05 +1000 (Mon, 02 May 2011) $
24   * 
25   * This is not a general purpose websocket client.
26   * It's only for testing the websocket server and is hardwired to a specific draft version of the protocol.
27   */
28  public class TestClient
29  {
30      private final static Random __random = new SecureRandom();
31      private static boolean _verbose=false;
32      private final String _host;
33      private final int _port;
34      private final String _protocol;
35      private int _size=64;
36      private final Socket _socket;
37      private final BufferedWriter _output;
38      private final BufferedReader _input;
39      private final SocketEndPoint _endp;
40      private final WebSocketGeneratorD07 _generator;
41      private final WebSocketParserD07 _parser;
42      private int _framesSent;
43      private int _messagesSent;
44      private int _framesReceived;
45      private int _messagesReceived;
46      private long _totalTime;
47      private long _minDuration=Long.MAX_VALUE;
48      private long _maxDuration=Long.MIN_VALUE;
49      private long _start;
50      private BlockingQueue<Long> _starts = new LinkedBlockingQueue<Long>();
51      int _messageBytes;
52      int _frames;
53      byte _opcode=-1;
54      private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler()
55      {
56          public synchronized void onFrame(byte flags, byte opcode, Buffer buffer)
57          {
58              try
59              {
60                  _framesReceived++;
61                  _frames++;
62                  if (opcode == WebSocketConnectionD07.OP_CLOSE)
63                  {
64                      byte[] data=buffer.asArray();
65                      // System.err.println("CLOSED: "+((0xff&data[0])*0x100+(0xff&data[1]))+" "+new String(data,2,data.length-2,StringUtil.__UTF8));
66                      _generator.addFrame((byte)0x8,WebSocketConnectionD07.OP_CLOSE,data,0,data.length);
67                      _generator.flush();
68                      _socket.shutdownOutput();
69                      _socket.close();
70                      return;
71                  }
72                  else if (opcode == WebSocketConnectionD07.OP_PING)
73                  {
74                      _generator.addFrame((byte)0x8,WebSocketConnectionD07.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
75                      _generator.flush();
76                  }
77                  
78                  _messageBytes+=buffer.length();
79                  
80                  if (_opcode==-1)
81                      _opcode=opcode;
82                  
83  
84                  if (WebSocketConnectionD07.isLastFrame(flags))
85                  {
86                      _messagesReceived++;
87                      Long start=_starts.take();
88  
89                      long duration = System.nanoTime()-start.longValue();
90                      if (duration>_maxDuration)
91                          _maxDuration=duration;
92                      if (duration<_minDuration)
93                          _minDuration=duration;
94                      _totalTime+=duration;
95                      System.out.printf("%d bytes from %s: frames=%d req=%d time=%.1fms opcode=0x%s\n",_messageBytes,_host,_frames,_messagesReceived,((double)duration/1000000.0),TypeUtil.toHexString(_opcode));
96                      _frames=0;
97                      _messageBytes=0;
98                      _opcode=-1;
99                  }
100                 
101             }
102             catch(Exception e)
103             {
104                 e.printStackTrace();
105             }
106 
107         }
108 
109         public void close(int code,String message)
110         {
111         }
112     };
113     
114     
115     public TestClient(String host, int port,String protocol, int timeoutMS) throws IOException
116     {
117         _host=host;
118         _port=port;
119         _protocol=protocol;
120         _socket = new Socket(host, port);
121         _socket.setSoTimeout(timeoutMS);
122         _output = new BufferedWriter(new OutputStreamWriter(_socket.getOutputStream(), "ISO-8859-1"));
123         _input = new BufferedReader(new InputStreamReader(_socket.getInputStream(), "ISO-8859-1"));
124 
125         _endp=new SocketEndPoint(_socket);
126         _generator = new WebSocketGeneratorD07(new WebSocketBuffers(32*1024),_endp,new WebSocketGeneratorD07.FixedMaskGen(new byte[4]));
127         _parser = new WebSocketParserD07(new WebSocketBuffers(32*1024),_endp,_handler,false);
128     }
129 
130     public int getSize()
131     {
132         return _size;
133     }
134 
135     public void setSize(int size)
136     {
137         _size = size;
138     }
139 
140     private void open() throws IOException
141     {
142         System.out.println("Jetty WebSocket PING "+_host+":"+_port+
143                 " ("+_socket.getRemoteSocketAddress()+") " +_size+" bytes of data.");
144         byte[] key = new byte[16];
145         __random.nextBytes(key);
146         
147         
148         _output.write("GET /chat HTTP/1.1\r\n"+
149                 "Host: "+_host+":"+_port+"\r\n"+
150                 "Upgrade: websocket\r\n"+
151                 "Connection: Upgrade\r\n"+
152                 "Sec-WebSocket-Key: "+new String(B64Code.encode(key))+"\r\n"+
153                 "Sec-WebSocket-Origin: http://example.com\r\n"+
154                 "Sec-WebSocket-Protocol: "+_protocol+"\r\n" +
155                 "Sec-WebSocket-Version: 7\r\n"+
156         "\r\n");
157         _output.flush();
158 
159         String responseLine = _input.readLine();
160         if(!responseLine.startsWith("HTTP/1.1 101 Switching Protocols"))
161             throw new IOException(responseLine);
162         // Read until we find Response key
163         String line;
164         boolean accepted=false;
165         String protocol="";
166         while ((line = _input.readLine()) != null)
167         {
168             if (line.length() == 0)
169                 break;
170             if (line.startsWith("Sec-WebSocket-Accept:"))
171             {
172                 String accept=line.substring(21).trim();
173                 accepted=accept.equals(WebSocketConnectionD07.hashKey(new String(B64Code.encode(key))));
174             }
175             else if (line.startsWith("Sec-WebSocket-Protocol:"))
176             {
177                 protocol=line.substring(24).trim();
178             }
179         }
180         
181         if (!accepted)
182             throw new IOException("Bad Sec-WebSocket-Accept");
183         System.out.println("handshake OK for protocol '"+protocol+"'");
184         
185         new Thread()
186         {
187             public void run()
188             {
189                 while (_endp.isOpen())
190                 {
191                     _parser.parseNext();
192                 }
193             }
194         }.start();
195     }
196 
197     public void ping(int count,byte opcode,int fragment)
198     {
199         try
200         {
201             _start=System.currentTimeMillis();
202             for (int i=0;i<count && !_socket.isClosed();i++)
203             {
204                 if (_socket.isClosed())
205                     break;
206                 byte data[]=null;
207 
208                 if (opcode==WebSocketConnectionD07.OP_TEXT)
209                 {
210                     StringBuilder b = new StringBuilder();
211                     while (b.length()<_size)
212                         b.append('A'+__random.nextInt(26));
213                     data=b.toString().getBytes(StringUtil.__UTF8);
214                 }
215                 else
216                 {             
217                     data= new byte[_size];
218                     __random.nextBytes(data);
219                 }
220                 _starts.add(System.nanoTime());
221 
222                 int off=0;
223                 int len=data.length;
224                 if (fragment>0&& len>fragment)
225                     len=fragment;
226                 _messagesSent++;
227                 while(off<data.length)
228                 {                    
229                     _framesSent++;
230                     byte flags= (byte)(off+len==data.length?0x8:0);
231                     byte op=(byte)(off==0?opcode:WebSocketConnectionD07.OP_CONTINUATION);
232 
233                     if (_verbose)                
234                         System.err.printf("%s#addFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len));
235                     _generator.addFrame(flags,op,data,off,len);
236 
237                     off+=len;
238                     if(data.length-off>len)
239                         len=data.length-off;
240                     if (fragment>0&& len>fragment)
241                         len=fragment;
242                 }
243 
244                 _generator.flush();
245 
246                 Thread.sleep(1000);
247             }
248         }
249         catch (Exception x)
250         {
251             throw new RuntimeException(x);
252         }
253     }
254 
255     public void dump() throws Exception
256     {
257         for (int i=0;i<250;i++)
258         {
259             if (_messagesSent==_messagesReceived)
260                 break;
261             _generator.flush();
262             Thread.sleep(100);
263         }
264         
265         _socket.close();
266         long duration=System.currentTimeMillis()-_start;
267         System.out.println("--- "+_host+" websocket ping statistics using 1 connection ---");
268         System.out.println(_framesSent+" frames transmitted, "+_framesReceived+" received, "+
269                 _messagesSent+" messages transmitted, "+_messagesReceived+" received, "+
270                 "time "+duration+"ms");
271         System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",_minDuration/1000000.0,_messagesReceived==0?0.0:(_totalTime/_messagesReceived/1000000.0),_maxDuration/1000000.0);
272     }
273     
274 
275     private static void usage(String[] args)
276     {
277         System.err.println("ERROR: "+Arrays.asList(args));
278         System.err.println("USAGE: java -cp CLASSPATH "+TestClient.class+" [ OPTIONS ]");
279         System.err.println("  -h|--host HOST  (default localhost)");
280         System.err.println("  -p|--port PORT  (default 8080)");
281         System.err.println("  -b|--binary");
282         System.err.println("  -v|--verbose");
283         System.err.println("  -c|--count n    (default 10)");
284         System.err.println("  -s|--size n     (default 64)");
285         System.err.println("  -f|--fragment n (default 4000) ");
286         System.err.println("  -P|--protocol echo|echo-assemble|echo-fragment|echo-broadcast");
287         System.exit(1);
288     }
289     
290     public static void main(String[] args)
291     {
292         try
293         {
294             String host="localhost";
295             int port=8080;
296             String protocol=null;
297             int count=10;
298             int size=64;
299             int fragment=4000;
300             boolean binary=false;
301             
302             for (int i=0;i<args.length;i++)
303             {
304                 String a=args[i];
305                 if ("-p".equals(a)||"--port".equals(a))
306                     port=Integer.parseInt(args[++i]);
307                 else if ("-h".equals(a)||"--host".equals(a))
308                     port=Integer.parseInt(args[++i]);
309                 else if ("-c".equals(a)||"--count".equals(a))
310                     count=Integer.parseInt(args[++i]);
311                 else if ("-s".equals(a)||"--size".equals(a))
312                     size=Integer.parseInt(args[++i]);
313                 else if ("-f".equals(a)||"--fragment".equals(a))
314                     fragment=Integer.parseInt(args[++i]);
315                 else if ("-P".equals(a)||"--protocol".equals(a))
316                     protocol=args[++i];
317                 else if ("-v".equals(a)||"--verbose".equals(a))
318                     _verbose=true;
319                 else if ("-b".equals(a)||"--binary".equals(a))
320                     binary=true;
321                 else if (a.startsWith("-"))
322                     usage(args);
323             }
324             
325 
326             TestClient client = new TestClient(host,port,protocol==null?null:protocol,10000);
327             client.setSize(size);
328 
329             try
330             {
331                 client.open();
332                 if (protocol!=null && protocol.startsWith("echo"))
333                     client.ping(count,binary?WebSocketConnectionD07.OP_BINARY:WebSocketConnectionD07.OP_TEXT,fragment);
334                 else
335                     client.ping(count,WebSocketConnectionD07.OP_PING,-1);
336             }
337             finally
338             {
339                 client.dump();
340             }
341             
342         }
343         catch (Exception e)
344         {
345             Log.warn(e);
346         }
347     }
348     
349     
350     
351 }