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