View Javadoc

1   // ========================================================================
2   // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.websocket;
15  
16  import java.io.IOException;
17  import java.security.MessageDigest;
18  import java.security.NoSuchAlgorithmException;
19  
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpServletResponse;
22  
23  import org.eclipse.jetty.io.AbstractConnection;
24  import org.eclipse.jetty.io.AsyncEndPoint;
25  import org.eclipse.jetty.io.Buffer;
26  import org.eclipse.jetty.io.ByteArrayBuffer;
27  import org.eclipse.jetty.io.Connection;
28  import org.eclipse.jetty.io.EndPoint;
29  import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
30  import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
31  import org.eclipse.jetty.util.StringUtil;
32  import org.eclipse.jetty.util.Utf8StringBuilder;
33  import org.eclipse.jetty.util.log.Log;
34  
35  public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.Connection
36  {
37      public final static byte LENGTH_FRAME=(byte)0x80;
38      public final static byte SENTINEL_FRAME=(byte)0x00;
39  
40      
41      final IdleCheck _idle;
42      final WebSocketParser _parser;
43      final WebSocketGenerator _generator;
44      final WebSocket _websocket;
45      final String _protocol;
46      String _key1;
47      String _key2;
48      ByteArrayBuffer _hixieBytes;
49      
50      public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, int draft)
51          throws IOException
52      {
53          super(endpoint,timestamp);
54          // TODO - can we use the endpoint idle mechanism?
55          if (endpoint instanceof AsyncEndPoint)
56              ((AsyncEndPoint)endpoint).cancelIdle();
57          
58          _endp.setMaxIdleTime(maxIdleTime);
59          
60          _websocket = websocket;
61          _protocol=protocol;
62  
63          // Select the parser/generators to use
64          switch(draft)
65          {
66              case 1:
67                  _generator = new WebSocketGeneratorD01(buffers, _endp);
68                  _parser = new WebSocketParserD01(buffers, endpoint, new FrameHandlerD1(_websocket));
69                  break;
70              default:
71                  _generator = new WebSocketGeneratorD00(buffers, _endp);
72                  _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD0(_websocket));
73          }
74  
75          // TODO should these be AsyncEndPoint checks/calls?
76          if (_endp instanceof SelectChannelEndPoint)
77          {
78              final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp;
79              scep.cancelIdle();
80              _idle=new IdleCheck()
81              {
82                  public void access(EndPoint endp)
83                  {
84                      scep.scheduleIdle();
85                  }
86              };
87              scep.scheduleIdle();
88          }
89          else
90          {
91              _idle = new IdleCheck()
92              {
93                  public void access(EndPoint endp)
94                  {}
95              };
96          }
97      }
98      
99      public void setHixieKeys(String key1,String key2)
100     {
101         _key1=key1;
102         _key2=key2;
103         _hixieBytes=new IndirectNIOBuffer(16);
104     }
105 
106     public Connection handle() throws IOException
107     {
108         try
109         {
110             // handle stupid hixie random bytes
111             if (_hixieBytes!=null)
112             { 
113                 
114                 // take any available bytes from the parser buffer, which may have already been read
115                 Buffer buffer=_parser.getBuffer();
116                 if (buffer!=null && buffer.length()>0)
117                 {
118                     int l=buffer.length();
119                     if (l>(8-_hixieBytes.length()))
120                         l=8-_hixieBytes.length();
121                     _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
122                     buffer.skip(l);
123                 }
124                 
125                 // while we are not blocked
126                 while(_endp.isOpen())
127                 {
128                     // do we now have enough
129                     if (_hixieBytes.length()==8)
130                     {
131                         // we have the silly random bytes
132                         // so let's work out the stupid 16 byte reply.
133                         doTheHixieHixieShake();
134                         _endp.flush(_hixieBytes);
135                         _hixieBytes=null;
136                         _endp.flush();
137                         break;
138                     }
139 
140                     // no, then let's fill
141                     int filled=_endp.fill(_hixieBytes);
142                     if (filled<0)
143                     {
144                         _endp.close();
145                         break;
146                     }
147                 }
148 
149                 _websocket.onConnect(this);
150                 return this;
151             }
152             
153             // handle the framing protocol
154             boolean progress=true;
155 
156             while (progress)
157             {
158                 int flushed=_generator.flush();
159                 int filled=_parser.parseNext();
160 
161                 progress = flushed>0 || filled>0;
162 
163                 if (filled<0 || flushed<0)
164                 {
165                     _endp.close();
166                     break;
167                 }
168             }
169         }
170         catch(IOException e)
171         {
172             try
173             {
174                 _endp.close();
175             }
176             catch(IOException e2)
177             {
178                 Log.ignore(e2);
179             }
180             throw e;
181         }
182         finally
183         {
184             if (_endp.isOpen())
185             {
186                 _idle.access(_endp);
187                 checkWriteable();
188             }
189         }
190         return this;
191     }
192 
193     private void doTheHixieHixieShake()
194     {          
195         byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
196                 WebSocketConnectionD00.hixieCrypt(_key1),
197                 WebSocketConnectionD00.hixieCrypt(_key2),
198                 _hixieBytes.asArray());
199         _hixieBytes.clear();
200         _hixieBytes.put(result);
201     }
202     
203     public boolean isOpen()
204     {
205         return _endp!=null&&_endp.isOpen();
206     }
207 
208     public boolean isIdle()
209     {
210         return _parser.isBufferEmpty() && _generator.isBufferEmpty();
211     }
212 
213     public boolean isSuspended()
214     {
215         return false;
216     }
217 
218     public void closed()
219     {
220         _websocket.onDisconnect(0,"");
221     }
222 
223     /* ------------------------------------------------------------ */
224     /**
225      */
226     public void sendMessage(String content) throws IOException
227     {
228         byte[] data = content.getBytes(StringUtil.__UTF8);
229         _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length,_endp.getMaxIdleTime());
230         _generator.flush();
231         checkWriteable();
232         _idle.access(_endp);
233     }
234 
235     /* ------------------------------------------------------------ */
236     public void sendMessage(byte[] data, int offset, int length) throws IOException
237     {
238         _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length,_endp.getMaxIdleTime());
239         _generator.flush();
240         checkWriteable();
241         _idle.access(_endp);
242     }
243 
244     /* ------------------------------------------------------------ */
245     public boolean isMore(byte flags)
246     {
247         return (flags&0x8) != 0;
248     }
249 
250     /* ------------------------------------------------------------ */
251     /**
252      * {@inheritDoc}
253      */
254     public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
255     {
256     }
257 
258     /* ------------------------------------------------------------ */
259     public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
260     {
261         _generator.addFrame((byte)0,opcode,content,offset,length,_endp.getMaxIdleTime());
262         _generator.flush();
263         checkWriteable();
264         _idle.access(_endp);
265     }
266 
267     /* ------------------------------------------------------------ */
268     public void disconnect(int code, String message)
269     {
270         throw new UnsupportedOperationException();
271     }
272 
273     /* ------------------------------------------------------------ */
274     public void disconnect()
275     {
276         try
277         {
278             _generator.flush(_endp.getMaxIdleTime());
279             _endp.close();
280         }
281         catch(IOException e)
282         {
283             Log.ignore(e);
284         }
285     }
286 
287     public void fillBuffersFrom(Buffer buffer)
288     {
289         _parser.fill(buffer);
290     }
291 
292 
293     private void checkWriteable()
294     {
295         if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
296             ((AsyncEndPoint)_endp).scheduleWrite();
297     }
298 
299     /* ------------------------------------------------------------ */
300     static long hixieCrypt(String key)
301     {
302         // Don't ask me what all this is about.
303         // I think it's pretend secret stuff, kind of
304         // like talking in pig latin!
305         long number=0;
306         int spaces=0;
307         for (char c : key.toCharArray())
308         {
309             if (Character.isDigit(c))
310                 number=number*10+(c-'0');
311             else if (c==' ')
312                 spaces++;
313         }
314         return number/spaces;
315     }
316 
317     public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
318     {            
319         try
320         {
321             MessageDigest md = MessageDigest.getInstance("MD5");
322             byte [] fodder = new byte[16];
323             
324             fodder[0]=(byte)(0xff&(key1>>24));
325             fodder[1]=(byte)(0xff&(key1>>16));
326             fodder[2]=(byte)(0xff&(key1>>8));
327             fodder[3]=(byte)(0xff&key1);
328             fodder[4]=(byte)(0xff&(key2>>24));
329             fodder[5]=(byte)(0xff&(key2>>16));
330             fodder[6]=(byte)(0xff&(key2>>8));
331             fodder[7]=(byte)(0xff&key2);
332             for (int i=0;i<8;i++)
333                 fodder[8+i]=key3[i];
334             md.update(fodder);
335             byte[] result=md.digest();
336             return result;
337         }
338         catch (NoSuchAlgorithmException e)
339         {
340             throw new IllegalStateException(e);
341         }
342     }
343 
344     private interface IdleCheck
345     {
346         void access(EndPoint endp);
347     }
348 
349     public void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException
350     {
351         String uri=request.getRequestURI();
352         String query=request.getQueryString();
353         if (query!=null && query.length()>0)
354             uri+="?"+query;
355         String host=request.getHeader("Host");
356         
357         String key1 = request.getHeader("Sec-WebSocket-Key1");
358         if (key1!=null)
359         {
360             String key2 = request.getHeader("Sec-WebSocket-Key2");
361             setHixieKeys(key1,key2);
362 
363             response.setHeader("Upgrade","WebSocket");
364             response.addHeader("Connection","Upgrade");
365             response.addHeader("Sec-WebSocket-Origin",origin);
366             response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
367             if (subprotocol!=null)
368                 response.addHeader("Sec-WebSocket-Protocol",subprotocol);
369             response.sendError(101,"WebSocket Protocol Handshake");
370         }
371         else
372         {
373             response.setHeader("Upgrade","WebSocket");
374             response.addHeader("Connection","Upgrade");
375             response.addHeader("WebSocket-Origin",origin);
376             response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
377             if (subprotocol!=null)
378                 response.addHeader("WebSocket-Protocol",subprotocol);
379             response.sendError(101,"Web Socket Protocol Handshake");
380             response.flushBuffer();
381             _websocket.onConnect(this);
382         }
383     }
384 
385     public void setMaxTextMessageSize(int size)
386     {
387     }
388 
389     public void setMaxBinaryMessageSize(int size)
390     {
391     }
392 
393     public int getMaxTextMessageSize()
394     {
395         return -1;
396     }
397 
398     public int getMaxBinaryMessageSize()
399     {
400         return -1;
401     }
402 
403     public String getProtocol()
404     {
405         return _protocol;
406     }
407     
408     class FrameHandlerD0 implements WebSocketParser.FrameHandler
409     {
410         final WebSocket _websocket;
411         final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
412 
413         FrameHandlerD0(WebSocket websocket)
414         {
415             _websocket=websocket;
416         }
417         
418         public void onFrame(byte flags, byte opcode, Buffer buffer)
419         {
420             try
421             {
422                 byte[] array=buffer.array();
423                 
424                 if (opcode==0)
425                 {
426                     if (_websocket instanceof WebSocket.OnTextMessage)
427                         ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
428                 }
429                 else
430                 {
431                     if (_websocket instanceof WebSocket.OnBinaryMessage)
432                         ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
433                 }
434             }
435             catch(ThreadDeath th)
436             {
437                 throw th;
438             }
439             catch(Throwable th)
440             {
441                 Log.warn(th);
442             }
443         }
444         
445         public void close(int code,String message)
446         {
447             disconnect(code,message);
448         }
449     }
450     
451     class FrameHandlerD1 implements WebSocketParser.FrameHandler
452     {
453         public final static byte PING=1;
454         public final static byte PONG=1;
455 
456         final WebSocket _websocket;
457         final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
458         boolean _fragmented=false;
459 
460         FrameHandlerD1(WebSocket websocket)
461         {
462             _websocket=websocket;
463         }
464         
465         public void onFrame(byte flags, byte opcode, Buffer buffer)
466         {
467             try
468             {
469                 byte[] array=buffer.array();
470                 
471                 if (opcode==0)
472                 {
473                     if (isMore(flags))
474                     {
475                         _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
476                         _fragmented=true;
477                     }
478                     else if (_fragmented)
479                     {
480                         _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
481                         if (_websocket instanceof WebSocket.OnTextMessage)
482                             ((WebSocket.OnTextMessage)_websocket).onMessage(_utf8.toString());
483                         _utf8.reset();
484                         _fragmented=false;
485                     }
486                     else
487                     {
488                         if (_websocket instanceof WebSocket.OnTextMessage)
489                             ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
490                     }
491                 }
492                 else if (opcode==PING)
493                 {
494                     sendFrame(flags,PONG,buffer.array(),buffer.getIndex(),buffer.length());
495                 }
496                 else if (opcode==PONG)
497                 {
498                     
499                 }
500                 else
501                 {
502                     if (isMore(flags))
503                     {
504                         if (_websocket instanceof WebSocket.OnFrame)
505                             ((WebSocket.OnFrame)_websocket).onFrame(flags,opcode,array,buffer.getIndex(),buffer.length());
506                     }
507                     else if (_fragmented)
508                     {
509                         if (_websocket instanceof WebSocket.OnFrame)
510                             ((WebSocket.OnFrame)_websocket).onFrame(flags,opcode,array,buffer.getIndex(),buffer.length());
511                     }
512                     else
513                     {
514                         if (_websocket instanceof WebSocket.OnBinaryMessage)
515                             ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
516                     }
517                 }
518             }
519             catch(ThreadDeath th)
520             {
521                 throw th;
522             }
523             catch(Throwable th)
524             {
525                 Log.warn(th);
526             }
527         }
528 
529         public void close(int code,String message)
530         {
531             disconnect(code,message);
532         }
533     }
534 
535 }