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  
52  @ManagedObject
53  public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames
54  {
55      private static final Logger LOG = Log.getLogger(WebSocketSession.class);
56      private final URI requestURI;
57      private final EventDriver websocket;
58      private final LogicalConnection connection;
59      private ExtensionFactory extensionFactory;
60      private long maximumMessageSize;
61      private String protocolVersion;
62      private long timeout;
63      private Map<String, String[]> parameterMap = new HashMap<>();
64      private WebSocketRemoteEndpoint remote;
65      private IncomingFrames incomingHandler;
66      private OutgoingFrames outgoingHandler;
67      private WebSocketPolicy policy;
68      private UpgradeRequest upgradeRequest;
69      private UpgradeResponse upgradeResponse;
70  
71      public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
72      {
73          if (requestURI == null)
74          {
75              throw new RuntimeException("Request URI cannot be null");
76          }
77  
78          this.requestURI = requestURI;
79          this.websocket = websocket;
80          this.connection = connection;
81          this.outgoingHandler = connection;
82          this.incomingHandler = websocket;
83  
84          // Get the parameter map (use the jetty MultiMap to do this right)
85          MultiMap<String> params = new MultiMap<>();
86          String query = requestURI.getQuery();
87          if (StringUtil.isNotBlank(query))
88          {
89              UrlEncoded.decodeTo(query,params,StringUtil.__UTF8_CHARSET,-1);
90          }
91  
92          for (String name : params.keySet())
93          {
94              List<String> valueList = params.getValues(name);
95              String valueArr[] = new String[valueList.size()];
96              valueArr = valueList.toArray(valueArr);
97              parameterMap.put(name,valueArr);
98          }
99      }
100 
101     @Override
102     public void close() throws IOException
103     {
104         connection.close();
105     }
106 
107     @Override
108     public void close(CloseStatus closeStatus)
109     {
110         this.close(closeStatus.getCode(),closeStatus.getPhrase());
111     }
112 
113     @Override
114     public void close(int statusCode, String reason)
115     {
116         connection.close(statusCode,reason);
117         websocket.onClose(new CloseInfo(statusCode,reason));
118     }
119 
120     /**
121      * Harsh disconnect
122      */
123     @Override
124     public void disconnect()
125     {
126         connection.disconnect();
127 
128         // notify of harsh disconnect
129         websocket.onClose(new CloseInfo(StatusCode.NO_CLOSE,"Harsh disconnect"));
130     }
131 
132     @Override
133     public void dump(Appendable out, String indent) throws IOException
134     {
135         super.dump(out,indent);
136         out.append(indent).append(" +- incomingHandler : ");
137         if (incomingHandler instanceof Dumpable)
138         {
139             ((Dumpable)incomingHandler).dump(out,indent + "    ");
140         }
141         else
142         {
143             out.append(incomingHandler.toString()).append('\n');
144         }
145 
146         out.append(indent).append(" +- outgoingHandler : ");
147         if (outgoingHandler instanceof Dumpable)
148         {
149             ((Dumpable)outgoingHandler).dump(out,indent + "    ");
150         }
151         else
152         {
153             out.append(outgoingHandler.toString()).append('\n');
154         }
155     }
156 
157     public LogicalConnection getConnection()
158     {
159         return connection;
160     }
161 
162     public ExtensionFactory getExtensionFactory()
163     {
164         return extensionFactory;
165     }
166 
167     /**
168      * The idle timeout in seconds
169      */
170     @Override
171     public long getIdleTimeout()
172     {
173         return timeout;
174     }
175 
176     @ManagedAttribute(readonly = true)
177     public IncomingFrames getIncomingHandler()
178     {
179         return incomingHandler;
180     }
181 
182     @Override
183     public InetSocketAddress getLocalAddress()
184     {
185         return connection.getLocalAddress();
186     }
187 
188     @Override
189     public long getMaximumMessageSize()
190     {
191         return maximumMessageSize;
192     }
193 
194     @ManagedAttribute(readonly = true)
195     public OutgoingFrames getOutgoingHandler()
196     {
197         return outgoingHandler;
198     }
199 
200     @Override
201     public WebSocketPolicy getPolicy()
202     {
203         return policy;
204     }
205 
206     @Override
207     public String getProtocolVersion()
208     {
209         return protocolVersion;
210     }
211 
212     @Override
213     public RemoteEndpoint getRemote()
214     {
215         if (!isOpen())
216         {
217             throw new WebSocketException("Session has not been opened yet");
218         }
219         return remote;
220     }
221 
222     @Override
223     public InetSocketAddress getRemoteAddress()
224     {
225         return remote.getInetSocketAddress();
226     }
227 
228     @Override
229     public UpgradeRequest getUpgradeRequest()
230     {
231         return this.upgradeRequest;
232     }
233 
234     @Override
235     public UpgradeResponse getUpgradeResponse()
236     {
237         return this.upgradeResponse;
238     }
239 
240     /**
241      * Incoming Errors from Parser
242      */
243     @Override
244     public void incomingError(WebSocketException e)
245     {
246         if (connection.getIOState().isInputClosed())
247         {
248             return; // input is closed
249         }
250         // Forward Errors to User WebSocket Object
251         websocket.incomingError(e);
252     }
253 
254     /**
255      * Incoming Raw Frames from Parser
256      */
257     @Override
258     public void incomingFrame(Frame frame)
259     {
260         if (connection.getIOState().isInputClosed())
261         {
262             return; // input is closed
263         }
264 
265         // Forward Frames Through Extension List
266         incomingHandler.incomingFrame(frame);
267     }
268 
269     @Override
270     public boolean isOpen()
271     {
272         if (this.connection == null)
273         {
274             return false;
275         }
276         return this.connection.isOpen();
277     }
278 
279     @Override
280     public boolean isSecure()
281     {
282         if (upgradeRequest == null)
283         {
284             throw new IllegalStateException("No valid UpgradeRequest yet");
285         }
286 
287         URI requestURI = upgradeRequest.getRequestURI();
288 
289         return "wss".equalsIgnoreCase(requestURI.getScheme());
290     }
291 
292     /**
293      * Open/Activate the session
294      * 
295      * @throws IOException
296      */
297     public void open()
298     {
299         if (remote != null)
300         {
301             // already opened
302             return;
303         }
304 
305         // Connect remote
306         remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
307 
308         // Open WebSocket
309         websocket.openSession(this);
310 
311         if (LOG.isDebugEnabled())
312         {
313             LOG.debug("open -> {}",dump());
314         }
315     }
316 
317     public void setExtensionFactory(ExtensionFactory extensionFactory)
318     {
319         this.extensionFactory = extensionFactory;
320     }
321 
322     /**
323      * Set the timeout in seconds
324      */
325     @Override
326     public void setIdleTimeout(long seconds)
327     {
328         this.timeout = seconds;
329     }
330 
331     @Override
332     public void setMaximumMessageSize(long length)
333     {
334         this.maximumMessageSize = length;
335     }
336 
337     public void setOutgoingHandler(OutgoingFrames outgoing)
338     {
339         this.outgoingHandler = outgoing;
340     }
341 
342     public void setPolicy(WebSocketPolicy policy)
343     {
344         this.policy = policy;
345     }
346 
347     public void setUpgradeRequest(UpgradeRequest request)
348     {
349         this.upgradeRequest = request;
350     }
351 
352     public void setUpgradeResponse(UpgradeResponse response)
353     {
354         this.upgradeResponse = response;
355     }
356 
357     @Override
358     public SuspendToken suspend()
359     {
360         // TODO Auto-generated method stub
361         return null;
362     }
363 
364     @Override
365     public String toString()
366     {
367         StringBuilder builder = new StringBuilder();
368         builder.append("WebSocketSession[");
369         builder.append("websocket=").append(websocket);
370         builder.append(",behavior=").append(policy.getBehavior());
371         builder.append(",connection=").append(connection);
372         builder.append(",remote=").append(remote);
373         builder.append(",incoming=").append(incomingHandler);
374         builder.append(",outgoing=").append(outgoingHandler);
375         builder.append("]");
376         return builder.toString();
377     }
378 }