1
2
3
4
5
6
7
8
9
10
11
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
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
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);
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
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
661 if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE)
662 return;
663 }
664
665
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
689 if (_onTextMessage!=null && _opcode==WebSocketConnectionD13.OP_TEXT)
690 {
691 if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
692 {
693
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
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
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
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
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 }