View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.common;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.net.URI;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.eclipse.jetty.util.MultiMap;
29  import org.eclipse.jetty.util.StringUtil;
30  import org.eclipse.jetty.util.UrlEncoded;
31  import org.eclipse.jetty.util.annotation.ManagedAttribute;
32  import org.eclipse.jetty.util.annotation.ManagedObject;
33  import org.eclipse.jetty.util.component.ContainerLifeCycle;
34  import org.eclipse.jetty.util.component.Dumpable;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  import org.eclipse.jetty.websocket.api.CloseStatus;
38  import org.eclipse.jetty.websocket.api.RemoteEndpoint;
39  import org.eclipse.jetty.websocket.api.Session;
40  import org.eclipse.jetty.websocket.api.StatusCode;
41  import org.eclipse.jetty.websocket.api.SuspendToken;
42  import org.eclipse.jetty.websocket.api.UpgradeRequest;
43  import org.eclipse.jetty.websocket.api.UpgradeResponse;
44  import org.eclipse.jetty.websocket.api.WebSocketException;
45  import org.eclipse.jetty.websocket.api.WebSocketPolicy;
46  import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
47  import org.eclipse.jetty.websocket.api.extensions.Frame;
48  import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
49  import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
50  import org.eclipse.jetty.websocket.common.events.EventDriver;
51  import org.eclipse.jetty.websocket.common.io.IOState;
52  import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
53  
54  @ManagedObject("A Jetty WebSocket Session")
55  public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener
56  {
57      private static final Logger LOG = Log.getLogger(WebSocketSession.class);
58      private final URI requestURI;
59      private final EventDriver websocket;
60      private final LogicalConnection connection;
61      private ExtensionFactory extensionFactory;
62      private long maximumMessageSize;
63      private String protocolVersion;
64      private Map<String, String[]> parameterMap = new HashMap<>();
65      private WebSocketRemoteEndpoint remote;
66      private IncomingFrames incomingHandler;
67      private OutgoingFrames outgoingHandler;
68      private WebSocketPolicy policy;
69      private UpgradeRequest upgradeRequest;
70      private UpgradeResponse upgradeResponse;
71  
72      public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
73      {
74          if (requestURI == null)
75          {
76              throw new RuntimeException("Request URI cannot be null");
77          }
78  
79          this.requestURI = requestURI;
80          this.websocket = websocket;
81          this.connection = connection;
82          this.outgoingHandler = connection;
83          this.incomingHandler = websocket;
84  
85          this.connection.getIOState().addListener(this);
86  
87          // Get the parameter map (use the jetty MultiMap to do this right)
88          MultiMap<String> params = new MultiMap<>();
89          String query = requestURI.getQuery();
90          if (StringUtil.isNotBlank(query))
91          {
92              UrlEncoded.decodeTo(query,params,StringUtil.__UTF8_CHARSET,-1);
93          }
94  
95          for (String name : params.keySet())
96          {
97              List<String> valueList = params.getValues(name);
98              String valueArr[] = new String[valueList.size()];
99              valueArr = valueList.toArray(valueArr);
100             parameterMap.put(name,valueArr);
101         }
102     }
103 
104     @Override
105     public void close() throws IOException
106     {
107         this.close(StatusCode.NORMAL,null);
108     }
109 
110     @Override
111     public void close(CloseStatus closeStatus)
112     {
113         this.close(closeStatus.getCode(),closeStatus.getPhrase());
114     }
115 
116     @Override
117     public void close(int statusCode, String reason)
118     {
119         connection.close(statusCode,reason);
120         notifyClose(statusCode,reason);
121     }
122 
123     /**
124      * Harsh disconnect
125      */
126     @Override
127     public void disconnect()
128     {
129         connection.disconnect();
130 
131         // notify of harsh disconnect
132         notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
133     }
134 
135     @Override
136     public void dump(Appendable out, String indent) throws IOException
137     {
138         super.dump(out,indent);
139         out.append(indent).append(" +- incomingHandler : ");
140         if (incomingHandler instanceof Dumpable)
141         {
142             ((Dumpable)incomingHandler).dump(out,indent + "    ");
143         }
144         else
145         {
146             out.append(incomingHandler.toString()).append('\n');
147         }
148 
149         out.append(indent).append(" +- outgoingHandler : ");
150         if (outgoingHandler instanceof Dumpable)
151         {
152             ((Dumpable)outgoingHandler).dump(out,indent + "    ");
153         }
154         else
155         {
156             out.append(outgoingHandler.toString()).append('\n');
157         }
158     }
159 
160     @Override
161     public boolean equals(Object obj)
162     {
163         if (this == obj)
164         {
165             return true;
166         }
167         if (obj == null)
168         {
169             return false;
170         }
171         if (getClass() != obj.getClass())
172         {
173             return false;
174         }
175         WebSocketSession other = (WebSocketSession)obj;
176         if (connection == null)
177         {
178             if (other.connection != null)
179             {
180                 return false;
181             }
182         }
183         else if (!connection.equals(other.connection))
184         {
185             return false;
186         }
187         return true;
188     }
189 
190     public LogicalConnection getConnection()
191     {
192         return connection;
193     }
194 
195     public ExtensionFactory getExtensionFactory()
196     {
197         return extensionFactory;
198     }
199 
200     /**
201      * The idle timeout in milliseconds
202      */
203     @Override
204     public long getIdleTimeout()
205     {
206         return connection.getMaxIdleTimeout();
207     }
208 
209     @ManagedAttribute(readonly = true)
210     public IncomingFrames getIncomingHandler()
211     {
212         return incomingHandler;
213     }
214 
215     @Override
216     public InetSocketAddress getLocalAddress()
217     {
218         return connection.getLocalAddress();
219     }
220 
221     @Override
222     public long getMaximumMessageSize()
223     {
224         return maximumMessageSize;
225     }
226 
227     @ManagedAttribute(readonly = true)
228     public OutgoingFrames getOutgoingHandler()
229     {
230         return outgoingHandler;
231     }
232 
233     @Override
234     public WebSocketPolicy getPolicy()
235     {
236         return policy;
237     }
238 
239     @Override
240     public String getProtocolVersion()
241     {
242         return protocolVersion;
243     }
244 
245     @Override
246     public RemoteEndpoint getRemote()
247     {
248         ConnectionState state = connection.getIOState().getConnectionState();
249 
250         if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
251         {
252             return remote;
253         }
254 
255         throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
256     }
257 
258     @Override
259     public InetSocketAddress getRemoteAddress()
260     {
261         return remote.getInetSocketAddress();
262     }
263 
264     public URI getRequestURI()
265     {
266         return requestURI;
267     }
268 
269     @Override
270     public UpgradeRequest getUpgradeRequest()
271     {
272         return this.upgradeRequest;
273     }
274 
275     @Override
276     public UpgradeResponse getUpgradeResponse()
277     {
278         return this.upgradeResponse;
279     }
280 
281     @Override
282     public int hashCode()
283     {
284         final int prime = 31;
285         int result = 1;
286         result = (prime * result) + ((connection == null)?0:connection.hashCode());
287         return result;
288     }
289 
290     /**
291      * Incoming Errors from Parser
292      */
293     @Override
294     public void incomingError(WebSocketException e)
295     {
296         if (connection.getIOState().isInputAvailable())
297         {
298             // Forward Errors to User WebSocket Object
299             websocket.incomingError(e);
300         }
301     }
302 
303     /**
304      * Incoming Raw Frames from Parser
305      */
306     @Override
307     public void incomingFrame(Frame frame)
308     {
309         if (connection.getIOState().isInputAvailable())
310         {
311             // Forward Frames Through Extension List
312             incomingHandler.incomingFrame(frame);
313         }
314     }
315 
316     @Override
317     public boolean isOpen()
318     {
319         if (this.connection == null)
320         {
321             return false;
322         }
323         return this.connection.isOpen();
324     }
325 
326     @Override
327     public boolean isSecure()
328     {
329         if (upgradeRequest == null)
330         {
331             throw new IllegalStateException("No valid UpgradeRequest yet");
332         }
333 
334         URI requestURI = upgradeRequest.getRequestURI();
335 
336         return "wss".equalsIgnoreCase(requestURI.getScheme());
337     }
338 
339     public void notifyClose(int statusCode, String reason)
340     {
341         websocket.onClose(new CloseInfo(statusCode,reason));
342     }
343 
344     @Override
345     public void onConnectionStateChange(ConnectionState state)
346     {
347         if (state == ConnectionState.CLOSED)
348         {
349             IOState ioState = this.connection.getIOState();
350             // The session only cares about abnormal close, as we need to notify
351             // the endpoint of this close scenario.
352             if (ioState.wasAbnormalClose())
353             {
354                 CloseInfo close = ioState.getCloseInfo();
355                 LOG.debug("Detected abnormal close: {}",close);
356                 // notify local endpoint
357                 notifyClose(close.getStatusCode(),close.getReason());
358             }
359         }
360     }
361 
362     /**
363      * Open/Activate the session
364      * 
365      * @throws IOException
366      */
367     public void open()
368     {
369         if (remote != null)
370         {
371             // already opened
372             return;
373         }
374 
375         // Upgrade success
376         connection.getIOState().onConnected();
377 
378         // Connect remote
379         remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
380 
381         // Open WebSocket
382         websocket.openSession(this);
383 
384         // Open connection
385         connection.getIOState().onOpened();
386 
387         if (LOG.isDebugEnabled())
388         {
389             LOG.debug("open -> {}",dump());
390         }
391     }
392 
393     public void setExtensionFactory(ExtensionFactory extensionFactory)
394     {
395         this.extensionFactory = extensionFactory;
396     }
397 
398     /**
399      * Set the timeout in milliseconds
400      */
401     @Override
402     public void setIdleTimeout(long ms)
403     {
404         connection.setMaxIdleTimeout(ms);
405     }
406 
407     @Override
408     public void setMaximumMessageSize(long length)
409     {
410         this.maximumMessageSize = length;
411     }
412 
413     public void setOutgoingHandler(OutgoingFrames outgoing)
414     {
415         this.outgoingHandler = outgoing;
416     }
417 
418     public void setPolicy(WebSocketPolicy policy)
419     {
420         this.policy = policy;
421     }
422 
423     public void setUpgradeRequest(UpgradeRequest request)
424     {
425         this.upgradeRequest = request;
426     }
427 
428     public void setUpgradeResponse(UpgradeResponse response)
429     {
430         this.upgradeResponse = response;
431     }
432 
433     @Override
434     public SuspendToken suspend()
435     {
436         // TODO Auto-generated method stub
437         return null;
438     }
439 
440     @Override
441     public String toString()
442     {
443         StringBuilder builder = new StringBuilder();
444         builder.append("WebSocketSession[");
445         builder.append("websocket=").append(websocket);
446         builder.append(",behavior=").append(policy.getBehavior());
447         builder.append(",connection=").append(connection);
448         builder.append(",remote=").append(remote);
449         builder.append(",incoming=").append(incomingHandler);
450         builder.append(",outgoing=").append(outgoingHandler);
451         builder.append("]");
452         return builder.toString();
453     }
454 }