View Javadoc

1   // ========================================================================
2   // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.websocket;
15  
16  import java.io.IOException;
17  import java.security.MessageDigest;
18  import java.security.NoSuchAlgorithmException;
19  
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpServletResponse;
22  
23  import org.eclipse.jetty.io.AsyncEndPoint;
24  import org.eclipse.jetty.io.Buffer;
25  import org.eclipse.jetty.io.ByteArrayBuffer;
26  import org.eclipse.jetty.io.Connection;
27  import org.eclipse.jetty.io.EndPoint;
28  import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
29  import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
30  import org.eclipse.jetty.util.log.Log;
31  
32  public class WebSocketConnectionD00 implements WebSocketConnection
33  {
34      final IdleCheck _idle;
35      final EndPoint _endp;
36      final WebSocketParser _parser;
37      final WebSocketGenerator _generator;
38      final long _timestamp;
39      final WebSocket _websocket;
40      String _key1;
41      String _key2;
42      ByteArrayBuffer _hixieBytes;
43  
44      public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint,int draft)
45          throws IOException
46      {
47          this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
48      }
49      
50      public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
51          throws IOException
52      {
53          // TODO - can we use the endpoint idle mechanism?
54          if (endpoint instanceof AsyncEndPoint)
55              ((AsyncEndPoint)endpoint).cancelIdle();
56          
57          _endp = endpoint;
58          _endp.setMaxIdleTime(maxIdleTime);
59          
60          _timestamp = timestamp;
61          _websocket = websocket;
62  
63          // Select the parser/generators to use
64          switch(draft)
65          {
66              case 1:
67                  _generator = new WebSocketGeneratorD01(buffers, _endp);
68                  _parser = new WebSocketParserD01(buffers, endpoint, new FrameHandlerD1(this,_websocket));
69                  break;
70              default:
71                  _generator = new WebSocketGeneratorD00(buffers, _endp);
72                  _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD0(_websocket));
73          }
74  
75          // TODO should these be AsyncEndPoint checks/calls?
76          if (_endp instanceof SelectChannelEndPoint)
77          {
78              final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp;
79              scep.cancelIdle();
80              _idle=new IdleCheck()
81              {
82                  public void access(EndPoint endp)
83                  {
84                      scep.scheduleIdle();
85                  }
86              };
87              scep.scheduleIdle();
88          }
89          else
90          {
91              _idle = new IdleCheck()
92              {
93                  public void access(EndPoint endp)
94                  {}
95              };
96          }
97      }
98      
99      public void setHixieKeys(String key1,String key2)
100     {
101         _key1=key1;
102         _key2=key2;
103         _hixieBytes=new IndirectNIOBuffer(16);
104     }
105 
106     public Connection handle() throws IOException
107     {
108         try
109         {
110             // handle stupid hixie random bytes
111             if (_hixieBytes!=null)
112             { 
113                 
114                 // take any available bytes from the parser buffer, which may have already been read
115                 Buffer buffer=_parser.getBuffer();
116                 if (buffer!=null && buffer.length()>0)
117                 {
118                     int l=buffer.length();
119                     if (l>(8-_hixieBytes.length()))
120                         l=8-_hixieBytes.length();
121                     _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
122                     buffer.skip(l);
123                 }
124                 
125                 // while we are not blocked
126                 while(_endp.isOpen())
127                 {
128                     // do we now have enough
129                     if (_hixieBytes.length()==8)
130                     {
131                         // we have the silly random bytes
132                         // so let's work out the stupid 16 byte reply.
133                         doTheHixieHixieShake();
134                         _endp.flush(_hixieBytes);
135                         _hixieBytes=null;
136                         _endp.flush();
137                         break;
138                     }
139 
140                     // no, then let's fill
141                     int filled=_endp.fill(_hixieBytes);
142                     if (filled<0)
143                     {
144                         _endp.close();
145                         break;
146                     }
147                 }
148 
149                 _websocket.onConnect(this);
150                 return this;
151             }
152             
153             // handle the framing protocol
154             boolean progress=true;
155 
156             while (progress)
157             {
158                 int flushed=_generator.flush();
159                 int filled=_parser.parseNext();
160 
161                 progress = flushed>0 || filled>0;
162 
163                 if (filled<0 || flushed<0)
164                 {
165                     _endp.close();
166                     break;
167                 }
168             }
169         }
170         catch(IOException e)
171         {
172             try
173             {
174                 _endp.close();
175             }
176             catch(IOException e2)
177             {
178                 Log.ignore(e2);
179             }
180             throw e;
181         }
182         finally
183         {
184             if (_endp.isOpen())
185             {
186                 _idle.access(_endp);
187                 checkWriteable();
188             }
189         }
190         return this;
191     }
192 
193     private void doTheHixieHixieShake()
194     {          
195         byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
196                 WebSocketConnectionD00.hixieCrypt(_key1),
197                 WebSocketConnectionD00.hixieCrypt(_key2),
198                 _hixieBytes.asArray());
199         _hixieBytes.clear();
200         _hixieBytes.put(result);
201     }
202     
203     public boolean isOpen()
204     {
205         return _endp!=null&&_endp.isOpen();
206     }
207 
208     public boolean isIdle()
209     {
210         return _parser.isBufferEmpty() && _generator.isBufferEmpty();
211     }
212 
213     public boolean isSuspended()
214     {
215         return false;
216     }
217 
218     public void closed()
219     {
220         _websocket.onDisconnect();
221     }
222 
223     public long getTimeStamp()
224     {
225         return _timestamp;
226     }
227 
228     /* ------------------------------------------------------------ */
229     /**
230      * @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(java.lang.String)
231      */
232     public void sendMessage(String content) throws IOException
233     {
234         sendMessage(WebSocket.SENTINEL_FRAME,content);
235     }
236 
237     /* ------------------------------------------------------------ */
238     /**
239      * @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(byte, java.lang.String)
240      */
241     public void sendMessage(byte frame, String content) throws IOException
242     {
243         _generator.addFrame(frame,content,_endp.getMaxIdleTime());
244         _generator.flush();
245         checkWriteable();
246         _idle.access(_endp);
247     }
248 
249     /* ------------------------------------------------------------ */
250     /**
251      * @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(byte, byte[], int, int)
252      */
253     public void sendMessage(byte opcode, byte[] content, int offset, int length) throws IOException
254     {
255         _generator.addFrame(opcode,content,offset,length,_endp.getMaxIdleTime());
256         _generator.flush();
257         checkWriteable();
258         _idle.access(_endp);
259     }
260 
261     /* ------------------------------------------------------------ */
262     /**
263      * @see org.eclipse.jetty.websocket.WebSocketConnection#sendFragment(boolean, byte, byte[], int, int)
264      */
265     public void sendFragment(boolean more,byte opcode, byte[] content, int offset, int length) throws IOException
266     {
267         _generator.addFragment(more,opcode,content,offset,length,_endp.getMaxIdleTime());
268         _generator.flush();
269         checkWriteable();
270         _idle.access(_endp);
271     }
272 
273     public void disconnect()
274     {
275         try
276         {
277             _generator.flush(_endp.getMaxIdleTime());
278             _endp.close();
279         }
280         catch(IOException e)
281         {
282             Log.ignore(e);
283         }
284     }
285 
286     public void fillBuffersFrom(Buffer buffer)
287     {
288         _parser.fill(buffer);
289     }
290 
291 
292     private void checkWriteable()
293     {
294         if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
295             ((AsyncEndPoint)_endp).scheduleWrite();
296     }
297 
298     /* ------------------------------------------------------------ */
299     static long hixieCrypt(String key)
300     {
301         // Don't ask me what all this is about.
302         // I think it's pretend secret stuff, kind of
303         // like talking in pig latin!
304         long number=0;
305         int spaces=0;
306         for (char c : key.toCharArray())
307         {
308             if (Character.isDigit(c))
309                 number=number*10+(c-'0');
310             else if (c==' ')
311                 spaces++;
312         }
313         return number/spaces;
314     }
315 
316     public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
317     {            
318         try
319         {
320             MessageDigest md = MessageDigest.getInstance("MD5");
321             byte [] fodder = new byte[16];
322             
323             fodder[0]=(byte)(0xff&(key1>>24));
324             fodder[1]=(byte)(0xff&(key1>>16));
325             fodder[2]=(byte)(0xff&(key1>>8));
326             fodder[3]=(byte)(0xff&key1);
327             fodder[4]=(byte)(0xff&(key2>>24));
328             fodder[5]=(byte)(0xff&(key2>>16));
329             fodder[6]=(byte)(0xff&(key2>>8));
330             fodder[7]=(byte)(0xff&key2);
331             for (int i=0;i<8;i++)
332                 fodder[8+i]=key3[i];
333             md.update(fodder);
334             byte[] result=md.digest();
335             return result;
336         }
337         catch (NoSuchAlgorithmException e)
338         {
339             throw new IllegalStateException(e);
340         }
341     }
342 
343     private interface IdleCheck
344     {
345         void access(EndPoint endp);
346     }
347 
348     public void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException
349     {
350         String uri=request.getRequestURI();
351         String query=request.getQueryString();
352         if (query!=null && query.length()>0)
353             uri+="?"+query;
354         String host=request.getHeader("Host");
355         
356         String key1 = request.getHeader("Sec-WebSocket-Key1");
357         if (key1!=null)
358         {
359             String key2 = request.getHeader("Sec-WebSocket-Key2");
360             setHixieKeys(key1,key2);
361 
362             response.setHeader("Upgrade","WebSocket");
363             response.addHeader("Connection","Upgrade");
364             response.addHeader("Sec-WebSocket-Origin",origin);
365             response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
366             if (subprotocol!=null)
367                 response.addHeader("Sec-WebSocket-Protocol",subprotocol);
368             response.sendError(101,"WebSocket Protocol Handshake");
369         }
370         else
371         {
372             response.setHeader("Upgrade","WebSocket");
373             response.addHeader("Connection","Upgrade");
374             response.addHeader("WebSocket-Origin",origin);
375             response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
376             if (subprotocol!=null)
377                 response.addHeader("WebSocket-Protocol",subprotocol);
378             response.sendError(101,"Web Socket Protocol Handshake");
379             response.flushBuffer();
380             _websocket.onConnect(this);
381         }
382     }
383 }