View Javadoc

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