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.io.UnsupportedEncodingException;
18  import java.security.MessageDigest;
19  import java.util.Collections;
20  import java.util.List;
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  
24  import org.eclipse.jetty.io.AbstractConnection;
25  import org.eclipse.jetty.io.AsyncEndPoint;
26  import org.eclipse.jetty.io.Buffer;
27  import org.eclipse.jetty.io.ByteArrayBuffer;
28  import org.eclipse.jetty.io.Connection;
29  import org.eclipse.jetty.io.EndPoint;
30  import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
31  import org.eclipse.jetty.util.B64Code;
32  import org.eclipse.jetty.util.StringUtil;
33  import org.eclipse.jetty.util.Utf8Appendable;
34  import org.eclipse.jetty.util.Utf8StringBuilder;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
38  import org.eclipse.jetty.websocket.WebSocket.OnControl;
39  import org.eclipse.jetty.websocket.WebSocket.OnFrame;
40  import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
41  
42  public class WebSocketConnectionD13 extends AbstractConnection implements WebSocketConnection
43  {
44      private static final Logger LOG = Log.getLogger(WebSocketConnectionD13.class);
45      private static final boolean STRICT=true;
46      
47      final static byte OP_CONTINUATION = 0x00;
48      final static byte OP_TEXT = 0x01;
49      final static byte OP_BINARY = 0x02;
50      final static byte OP_EXT_DATA = 0x03;
51  
52      final static byte OP_CONTROL = 0x08;
53      final static byte OP_CLOSE = 0x08;
54      final static byte OP_PING = 0x09;
55      final static byte OP_PONG = 0x0A;
56      final static byte OP_EXT_CTRL = 0x0B;
57  
58      final static int CLOSE_NORMAL=1000;
59      final static int CLOSE_SHUTDOWN=1001;
60      final static int CLOSE_PROTOCOL=1002;
61      final static int CLOSE_BAD_DATA=1003;
62      final static int CLOSE_UNDEFINED=1004;
63      final static int CLOSE_NO_CODE=1005;
64      final static int CLOSE_NO_CLOSE=1006;
65      final static int CLOSE_NOT_UTF8=1007;
66      final static int CLOSE_POLICY_VIOLATION=1008;
67      final static int CLOSE_MESSAGE_TOO_LARGE=1009;
68      final static int CLOSE_REQUIRED_EXTENSION=1010;
69  
70      final static int FLAG_FIN=0x8;
71  
72      final static int VERSION=13;
73  
74      static boolean isLastFrame(byte flags)
75      {
76          return (flags&FLAG_FIN)!=0;
77      }
78  
79      static boolean isControlFrame(byte opcode)
80      {
81          return (opcode&OP_CONTROL)!=0;
82      }
83  
84      private final static byte[] MAGIC;
85      private final IdleCheck _idle;
86      private final List<Extension> _extensions;
87      private final WebSocketParserD13 _parser;
88      private final WebSocketGeneratorD13 _generator;
89      private final WebSocketGenerator _outbound;
90      private final WebSocket _webSocket;
91      private final OnFrame _onFrame;
92      private final OnBinaryMessage _onBinaryMessage;
93      private final OnTextMessage _onTextMessage;
94      private final OnControl _onControl;
95      private final String _protocol;
96      private final int _draft;
97      private final ClassLoader _context;
98      private volatile int _closeCode;
99      private volatile String _closeMessage;
100     private volatile boolean _closedIn;
101     private volatile boolean _closedOut;
102     private int _maxTextMessageSize=-1;
103     private int _maxBinaryMessageSize=-1;
104 
105     static
106     {
107         try
108         {
109             MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
110         }
111         catch (UnsupportedEncodingException e)
112         {
113             throw new RuntimeException(e);
114         }
115     }
116 
117     private final WebSocket.FrameConnection _connection = new WSFrameConnection();
118 
119 
120     /* ------------------------------------------------------------ */
121     public WebSocketConnectionD13(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
122         throws IOException
123     {
124         this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
125     }
126 
127     /* ------------------------------------------------------------ */
128     public WebSocketConnectionD13(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
129         throws IOException
130     {
131         super(endpoint,timestamp);
132 
133         _context=Thread.currentThread().getContextClassLoader();
134 
135         if (endpoint instanceof AsyncEndPoint)
136             ((AsyncEndPoint)endpoint).cancelIdle();
137 
138         _draft=draft;
139         _endp.setMaxIdleTime(maxIdleTime);
140 
141         _webSocket = websocket;
142         _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
143         _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
144         _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
145         _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
146         _generator = new WebSocketGeneratorD13(buffers, _endp,maskgen);
147 
148         _extensions=extensions;
149         WebSocketParser.FrameHandler frameHandler = new WSFrameHandler();
150         if (_extensions!=null)
151         {
152             int e=0;
153             for (Extension extension : _extensions)
154             {
155                 extension.bind(
156                         _connection,
157                         e==extensions.size()-1? frameHandler :extensions.get(e+1),
158                         e==0?_generator:extensions.get(e-1));
159                 e++;
160             }
161         }
162 
163         _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
164         WebSocketParser.FrameHandler inbound = (_extensions == null || _extensions.size() == 0) ? frameHandler : extensions.get(0);
165 
166         _parser = new WebSocketParserD13(buffers, endpoint, inbound,maskgen==null);
167 
168         _protocol=protocol;
169 
170         // TODO should these be AsyncEndPoint checks/calls?
171         if (_endp instanceof SelectChannelEndPoint)
172         {
173             final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp;
174             scep.cancelIdle();
175             _idle=new IdleCheck()
176             {
177                 public void access(EndPoint endp)
178                 {
179                     scep.scheduleIdle();
180                 }
181             };
182             scep.scheduleIdle();
183         }
184         else
185         {
186             _idle = new IdleCheck()
187             {
188                 public void access(EndPoint endp)
189                 {}
190             };
191         }
192     }
193 
194     /* ------------------------------------------------------------ */
195     public WebSocket.Connection getConnection()
196     {
197         return _connection;
198     }
199 
200     /* ------------------------------------------------------------ */
201     public List<Extension> getExtensions()
202     {
203         if (_extensions==null)
204             return Collections.emptyList();
205 
206         return _extensions;
207     }
208 
209     /* ------------------------------------------------------------ */
210     public Connection handle() throws IOException
211     {
212         Thread current = Thread.currentThread();
213         ClassLoader oldcontext = current.getContextClassLoader();
214         current.setContextClassLoader(_context);
215         try
216         {
217             // handle the framing protocol
218             boolean progress=true;
219 
220             while (progress)
221             {
222                 int flushed=_generator.flushBuffer();
223                 int filled=_parser.parseNext();
224 
225                 progress = flushed>0 || filled>0;
226 
227                 if (filled<0 || flushed<0)
228                 {
229                     _endp.close();
230                     break;
231                 }
232             }
233         }
234         catch(IOException e)
235         {
236             try
237             {
238                 _endp.close();
239             }
240             catch(IOException e2)
241             {
242                 LOG.ignore(e2);
243             }
244             throw e;
245         }
246         finally
247         {
248             current.setContextClassLoader(oldcontext);
249             _parser.returnBuffer();
250             _generator.returnBuffer();
251             if (_endp.isOpen())
252             {
253                 _idle.access(_endp);
254                 if (_closedIn && _closedOut && _outbound.isBufferEmpty())
255                     _endp.close();
256                 else if (_endp.isInputShutdown() && !_closedIn)
257                     closeIn(CLOSE_NO_CLOSE,null);
258                 else
259                     checkWriteable();
260             }
261         }
262         return this;
263     }
264 
265     /* ------------------------------------------------------------ */
266     public boolean isIdle()
267     {
268         return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
269     }
270 
271     /* ------------------------------------------------------------ */
272     @Override
273     public void idleExpired()
274     {
275         long idle = System.currentTimeMillis()-((SelectChannelEndPoint)_endp).getIdleTimestamp();
276         closeOut(WebSocketConnectionD13.CLOSE_NORMAL,"Idle for "+idle+"ms > "+_endp.getMaxIdleTime()+"ms");
277     }
278 
279     /* ------------------------------------------------------------ */
280     public boolean isSuspended()
281     {
282         return false;
283     }
284 
285     /* ------------------------------------------------------------ */
286     public void closed()
287     {
288         final boolean closed;
289         synchronized (this)
290         {
291             closed=_closeCode==0;
292             if (closed)
293                 _closeCode=WebSocketConnectionD13.CLOSE_NO_CLOSE;
294         }
295         if (closed)
296             _webSocket.onClose(WebSocketConnectionD13.CLOSE_NO_CLOSE,"closed");
297     }
298 
299     /* ------------------------------------------------------------ */
300     public void closeIn(int code,String message)
301     {
302         LOG.debug("ClosedIn {} {}",this,message);
303 
304         final boolean close;
305         final boolean tell_app;
306         synchronized (this)
307         {
308             close=_closedOut;
309             _closedIn=true;
310             tell_app=_closeCode==0;
311             if (tell_app)
312             {
313                 _closeCode=code;
314                 _closeMessage=message;
315             }
316         }
317 
318         try
319         {
320             if (tell_app)
321                 _webSocket.onClose(code,message);
322         }
323         finally
324         {
325             try
326             {
327                 if (close)
328                     _endp.close();
329                 else
330                     closeOut(code,message);
331             }
332             catch(IOException e)
333             {
334                 LOG.ignore(e);
335             }
336         }
337     }
338 
339     /* ------------------------------------------------------------ */
340     public void closeOut(int code,String message)
341     {
342         LOG.debug("ClosedOut {} {}",this,message);
343 
344         final boolean close;
345         final boolean tell_app;
346         final boolean send_close;
347         synchronized (this)
348         {
349             close=_closedIn;
350             send_close=!_closedOut;
351             _closedOut=true;
352             tell_app=_closeCode==0;
353             if (tell_app)
354             {
355                 _closeCode=code;
356                 _closeMessage=message;
357             }
358         }
359 
360         try
361         {
362             if (tell_app)
363                 _webSocket.onClose(code,message);
364         }
365         finally
366         {
367             try
368             {
369                 if (send_close)
370                 {
371                     if (code<=0)
372                         code=WebSocketConnectionD13.CLOSE_NORMAL;
373                     byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
374                     bytes[0]=(byte)(code/0x100);
375                     bytes[1]=(byte)(code%0x100);
376                     _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_CLOSE,bytes,0,bytes.length);
377                     _outbound.flush();
378                     if (close)
379                         _endp.shutdownOutput();
380                 }
381                 else if (close)
382                     _endp.close();
383 
384             }
385             catch(IOException e)
386             {
387                 LOG.ignore(e);
388             }
389         }
390     }
391 
392     /* ------------------------------------------------------------ */
393     public void fillBuffersFrom(Buffer buffer)
394     {
395         _parser.fill(buffer);
396     }
397 
398     /* ------------------------------------------------------------ */
399     private void checkWriteable()
400     {
401         if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
402         {
403             ((AsyncEndPoint)_endp).scheduleWrite();
404         }
405     }
406 
407     /* ------------------------------------------------------------ */
408     /* ------------------------------------------------------------ */
409     /* ------------------------------------------------------------ */
410     private class WSFrameConnection implements WebSocket.FrameConnection
411     {
412         volatile boolean _disconnecting;
413 
414         /* ------------------------------------------------------------ */
415         public void sendMessage(String content) throws IOException
416         {
417             if (_closedOut)
418                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
419             byte[] data = content.getBytes(StringUtil.__UTF8);
420             _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_TEXT,data,0,data.length);
421             checkWriteable();
422             _idle.access(_endp);
423         }
424 
425         /* ------------------------------------------------------------ */
426         public void sendMessage(byte[] content, int offset, int length) throws IOException
427         {
428             if (_closedOut)
429                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
430             _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_BINARY,content,offset,length);
431             checkWriteable();
432             _idle.access(_endp);
433         }
434 
435         /* ------------------------------------------------------------ */
436         public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
437         {
438             if (_closedOut)
439                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
440             _outbound.addFrame(flags,opcode,content,offset,length);
441             checkWriteable();
442             _idle.access(_endp);
443         }
444 
445         /* ------------------------------------------------------------ */
446         public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
447         {
448             if (_closedOut)
449                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
450             _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
451             checkWriteable();
452             _idle.access(_endp);
453         }
454 
455         /* ------------------------------------------------------------ */
456         public boolean isMessageComplete(byte flags)
457         {
458             return isLastFrame(flags);
459         }
460 
461         /* ------------------------------------------------------------ */
462         public boolean isOpen()
463         {
464             return _endp!=null&&_endp.isOpen();
465         }
466 
467         /* ------------------------------------------------------------ */
468         public void close(int code, String message)
469         {
470             if (_disconnecting)
471                 return;
472             _disconnecting=true;
473             WebSocketConnectionD13.this.closeOut(code,message);
474         }
475 
476         /* ------------------------------------------------------------ */
477         public void setMaxIdleTime(int ms)
478         {
479             try
480             {
481                 _endp.setMaxIdleTime(ms);
482             }
483             catch(IOException e)
484             {
485                 LOG.warn(e);
486             }
487         }
488 
489         /* ------------------------------------------------------------ */
490         public void setMaxTextMessageSize(int size)
491         {
492             _maxTextMessageSize=size;
493         }
494 
495         /* ------------------------------------------------------------ */
496         public void setMaxBinaryMessageSize(int size)
497         {
498             _maxBinaryMessageSize=size;
499         }
500 
501         /* ------------------------------------------------------------ */
502         public int getMaxIdleTime()
503         {
504             return _endp.getMaxIdleTime();
505         }
506 
507         /* ------------------------------------------------------------ */
508         public int getMaxTextMessageSize()
509         {
510             return _maxTextMessageSize;
511         }
512 
513         /* ------------------------------------------------------------ */
514         public int getMaxBinaryMessageSize()
515         {
516             return _maxBinaryMessageSize;
517         }
518 
519         /* ------------------------------------------------------------ */
520         public String getProtocol()
521         {
522             return _protocol;
523         }
524 
525         /* ------------------------------------------------------------ */
526         public byte binaryOpcode()
527         {
528             return OP_BINARY;
529         }
530 
531         /* ------------------------------------------------------------ */
532         public byte textOpcode()
533         {
534             return OP_TEXT;
535         }
536 
537         /* ------------------------------------------------------------ */
538         public byte continuationOpcode()
539         {
540             return OP_CONTINUATION;
541         }
542 
543         /* ------------------------------------------------------------ */
544         public byte finMask()
545         {
546             return FLAG_FIN;
547         }
548 
549         /* ------------------------------------------------------------ */
550         public boolean isControl(byte opcode)
551         {
552             return isControlFrame(opcode);
553         }
554 
555         /* ------------------------------------------------------------ */
556         public boolean isText(byte opcode)
557         {
558             return opcode==OP_TEXT;
559         }
560 
561         /* ------------------------------------------------------------ */
562         public boolean isBinary(byte opcode)
563         {
564             return opcode==OP_BINARY;
565         }
566 
567         /* ------------------------------------------------------------ */
568         public boolean isContinuation(byte opcode)
569         {
570             return opcode==OP_CONTINUATION;
571         }
572 
573         /* ------------------------------------------------------------ */
574         public boolean isClose(byte opcode)
575         {
576             return opcode==OP_CLOSE;
577         }
578 
579         /* ------------------------------------------------------------ */
580         public boolean isPing(byte opcode)
581         {
582             return opcode==OP_PING;
583         }
584 
585         /* ------------------------------------------------------------ */
586         public boolean isPong(byte opcode)
587         {
588             return opcode==OP_PONG;
589         }
590 
591         /* ------------------------------------------------------------ */
592         public void disconnect()
593         {
594             close(CLOSE_NORMAL,null);
595         }
596 
597         /* ------------------------------------------------------------ */
598         public void setAllowFrameFragmentation(boolean allowFragmentation)
599         {
600             _parser.setFakeFragments(allowFragmentation);
601         }
602 
603         /* ------------------------------------------------------------ */
604         public boolean isAllowFrameFragmentation()
605         {
606             return _parser.isFakeFragments();
607         }
608 
609         /* ------------------------------------------------------------ */
610         @Override
611         public String toString()
612         {
613             return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
614         }
615     }
616 
617     /* ------------------------------------------------------------ */
618     /* ------------------------------------------------------------ */
619     /* ------------------------------------------------------------ */
620     private class WSFrameHandler implements WebSocketParser.FrameHandler
621     {
622         private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity
623         private ByteArrayBuffer _aggregate;
624         private byte _opcode=-1;
625 
626         public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
627         {
628             boolean lastFrame = isLastFrame(flags);
629 
630             synchronized(WebSocketConnectionD13.this)
631             {
632                 // Ignore incoming after a close
633                 if (_closedIn)
634                     return;
635             }
636             try
637             {
638                 byte[] array=buffer.array();
639 
640                 if (STRICT)
641                 {
642                     if (isControlFrame(opcode) && buffer.length()>125)
643                     {
644                         _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Control frame too large");
645                         return;
646                     }
647 
648                     if ((flags&0x7)!=0)
649                     {
650                         _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags));
651                         return;
652                     }
653 
654                     if (_opcode!=-1 && opcode!=WebSocketConnectionD13.OP_CONTINUATION)
655                     {
656                         _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad continuation"+Integer.toHexString(opcode));
657                         return;
658                     }
659 
660                     // Ignore all frames after error close
661                     if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE)
662                         return;
663                 }
664                 
665                 // Deliver frame if websocket is a FrameWebSocket
666                 if (_onFrame!=null)
667                 {
668                     if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
669                         return;
670                 }
671 
672                 if (_onControl!=null && isControlFrame(opcode))
673                 {
674                     if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
675                         return;
676                 }
677                 
678                 switch(opcode)
679                 {
680                     case WebSocketConnectionD13.OP_CONTINUATION:
681                     {
682                         if (_opcode==-1)
683                         {
684                             _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad Continuation");
685                             return;
686                         }
687                         
688                         // If text, append to the message buffer
689                         if (_onTextMessage!=null && _opcode==WebSocketConnectionD13.OP_TEXT)
690                         {
691                             if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
692                             {
693                                 // If this is the last fragment, deliver the text buffer
694                                 if (lastFrame)
695                                 {
696                                     _opcode=-1;
697                                     String msg =_utf8.toString();
698                                     _utf8.reset();
699                                     _onTextMessage.onMessage(msg);
700                                 }
701                             }
702                             else
703                                 textMessageTooLarge();
704                         }
705 
706                         if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
707                         {
708                             if (_aggregate!=null && checkBinaryMessageSize(_aggregate.length(),buffer.length()))
709                             {
710                                 _aggregate.put(buffer);
711 
712                                 // If this is the last fragment, deliver
713                                 if (lastFrame && _onBinaryMessage!=null)
714                                 {
715                                     try
716                                     {
717                                         _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
718                                     }
719                                     finally
720                                     {
721                                         _opcode=-1;
722                                         _aggregate.clear();
723                                     }
724                                 }
725                             }
726                         }
727                         break;
728                     }
729                     case WebSocketConnectionD13.OP_PING:
730                     {
731                         LOG.debug("PING {}",this);
732                         if (!_closedOut)
733                             _connection.sendControl(WebSocketConnectionD13.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
734                         break;
735                     }
736 
737                     case WebSocketConnectionD13.OP_PONG:
738                     {
739                         LOG.debug("PONG {}",this);
740                         break;
741                     }
742 
743                     case WebSocketConnectionD13.OP_CLOSE:
744                     {
745                         int code=WebSocketConnectionD13.CLOSE_NO_CODE;
746                         String message=null;
747                         if (buffer.length()>=2)
748                         {
749                             code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]);
750                             if (buffer.length()>2)
751                                 message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
752                         }
753                         closeIn(code,message);
754                         break;
755                     }
756 
757                     case WebSocketConnectionD13.OP_TEXT:
758                     {
759                         if(_onTextMessage!=null)
760                         {
761                             if (_connection.getMaxTextMessageSize()<=0)
762                             {
763                                 // No size limit, so handle only final frames
764                                 if (lastFrame)
765                                     _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
766                                 else
767                                 {
768                                     LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp);
769                                     _connection.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled");
770                                 }
771                             }
772                             // append bytes to message buffer (if they fit)
773                             else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
774                             {
775                                 if (lastFrame)
776                                 {
777                                     String msg =_utf8.toString();
778                                     _utf8.reset();
779                                     _onTextMessage.onMessage(msg);
780                                 }
781                                 else
782                                 {
783                                     _opcode=WebSocketConnectionD13.OP_TEXT;
784                                 }
785                             }
786                             else
787                                 textMessageTooLarge();
788                         }
789                         break;
790                     }
791 
792                     case WebSocketConnectionD13.OP_BINARY:
793                     {
794                         if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
795                         {
796                             if (lastFrame)
797                             {
798                                 _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
799                             }
800                             else if (_connection.getMaxBinaryMessageSize()>=0)
801                             {
802                                 _opcode=opcode;
803                                 // TODO use a growing buffer rather than a fixed one.
804                                 if (_aggregate==null)
805                                     _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
806                                 _aggregate.put(buffer);
807                             }
808                             else
809                             {
810                                 LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
811                                 _connection.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled");
812                             }
813                         }
814                         break;
815                     }
816                     
817                     default:
818                         if (STRICT)
819                             _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
820                         return;
821                         
822                 }
823             }
824             catch(Utf8Appendable.NotUtf8Exception notUtf8)
825             {
826                 LOG.warn(notUtf8);
827                 LOG.warn("{} for {}",notUtf8,_endp);
828                 LOG.debug(notUtf8);
829                 _connection.close(WebSocketConnectionD13.CLOSE_NOT_UTF8,"Invalid UTF-8");
830             }
831             catch(ThreadDeath th)
832             {
833                 throw th;
834             }
835             catch(Throwable th)
836             {
837                 LOG.warn(th);
838             }
839         }
840 
841         private boolean checkBinaryMessageSize(int bufferLen, int length)
842         {
843             int max = _connection.getMaxBinaryMessageSize();
844             if (max>0 && (bufferLen+length)>max)
845             {
846                 LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
847                 _connection.close(WebSocketConnectionD13.CLOSE_MESSAGE_TOO_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
848                 _opcode=-1;
849                 if (_aggregate!=null)
850                     _aggregate.clear();
851                 return false;
852             }
853             return true;
854         }
855 
856         private void textMessageTooLarge()
857         {
858             LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
859             _connection.close(WebSocketConnectionD13.CLOSE_MESSAGE_TOO_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
860 
861             _opcode=-1;
862             _utf8.reset();
863         }
864 
865         public void close(int code,String message)
866         {
867             if (code!=CLOSE_NORMAL)
868                 LOG.warn("Close: "+code+" "+message);
869             _connection.close(code,message);
870         }
871 
872         @Override
873         public String toString()
874         {
875             return WebSocketConnectionD13.this.toString()+"FH";
876         }
877     }
878 
879     /* ------------------------------------------------------------ */
880     private interface IdleCheck
881     {
882         void access(EndPoint endp);
883     }
884 
885     /* ------------------------------------------------------------ */
886     public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
887     {
888         String key = request.getHeader("Sec-WebSocket-Key");
889 
890         response.setHeader("Upgrade","WebSocket");
891         response.addHeader("Connection","Upgrade");
892         response.addHeader("Sec-WebSocket-Accept",hashKey(key));
893         if (subprotocol!=null)
894             response.addHeader("Sec-WebSocket-Protocol",subprotocol);
895 
896         for(Extension ext : _extensions)
897             response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
898 
899         response.sendError(101);
900 
901         if (_onFrame!=null)
902             _onFrame.onHandshake(_connection);
903         _webSocket.onOpen(_connection);
904     }
905 
906     /* ------------------------------------------------------------ */
907     public static String hashKey(String key)
908     {
909         try
910         {
911             MessageDigest md = MessageDigest.getInstance("SHA1");
912             md.update(key.getBytes("UTF-8"));
913             md.update(MAGIC);
914             return new String(B64Code.encode(md.digest()));
915         }
916         catch (Exception e)
917         {
918             throw new RuntimeException(e);
919         }
920     }
921 
922     /* ------------------------------------------------------------ */
923     @Override
924     public String toString()
925     {
926          return "WS/D"+_draft+"-"+_endp;
927     }
928 }