View Javadoc

1   // ========================================================================
2   // Copyright (c) 2006-2009 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.server.session;
15  
16  import java.security.NoSuchAlgorithmException;
17  import java.security.SecureRandom;
18  import java.util.Random;
19  
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpSession;
22  
23  import org.eclipse.jetty.server.SessionIdManager;
24  import org.eclipse.jetty.server.session.AbstractSessionManager.Session;
25  import org.eclipse.jetty.util.MultiMap;
26  import org.eclipse.jetty.util.component.AbstractLifeCycle;
27  import org.eclipse.jetty.util.log.Log;
28  
29  /* ------------------------------------------------------------ */
30  /**
31   * HashSessionIdManager. An in-memory implementation of the session ID manager.
32   */
33  public class HashSessionIdManager extends AbstractLifeCycle implements SessionIdManager
34  {
35      private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";  
36      protected final static String SESSION_ID_RANDOM_ALGORITHM = "SHA1PRNG";
37      protected final static String SESSION_ID_RANDOM_ALGORITHM_ALT = "IBMSecureRandom";
38  
39      MultiMap<String> _sessions;
40      protected Random _random;
41      private boolean _weakRandom;
42      private String _workerName;
43  
44      /* ------------------------------------------------------------ */
45      public HashSessionIdManager()
46      {
47      }
48  
49      /* ------------------------------------------------------------ */
50      public HashSessionIdManager(Random random)
51      {
52          _random=random;
53        
54      }
55  
56      /* ------------------------------------------------------------ */
57      /**
58       * Get the workname. If set, the workername is dot appended to the session
59       * ID and can be used to assist session affinity in a load balancer.
60       * 
61       * @return String or null
62       */
63      public String getWorkerName()
64      {
65          return _workerName;
66      }
67  
68      /* ------------------------------------------------------------ */
69      /**
70       * Set the workname. If set, the workername is dot appended to the session
71       * ID and can be used to assist session affinity in a load balancer.
72       * 
73       * @param workerName
74       */
75      public void setWorkerName(String workerName)
76      {
77          _workerName=workerName;
78      }
79  
80      /* ------------------------------------------------------------ */
81      /** Get the session ID with any worker ID.
82       * 
83       * @param request
84       * @return sessionId plus any worker ID.
85       */
86      public String getNodeId(String clusterId,HttpServletRequest request) 
87      {
88          String worker=request==null?null:(String)request.getAttribute("org.eclipse.http.ajp.JVMRoute");
89          if (worker!=null) 
90              return clusterId+'.'+worker; 
91          
92          if (_workerName!=null) 
93              return clusterId+'.'+_workerName;
94         
95          return clusterId;
96      }
97  
98      /* ------------------------------------------------------------ */
99      /** Get the session ID without any worker ID.
100      * 
101      * @param request
102      * @return sessionId without any worker ID.
103      */
104     public String getClusterId(String nodeId) 
105     {
106         int dot=nodeId.lastIndexOf('.');
107         return (dot>0)?nodeId.substring(0,dot):nodeId;
108     }
109     
110     /* ------------------------------------------------------------ */
111     protected void doStart()
112     {
113         if (_random==null)
114         {      
115             try 
116             {
117                 //This operation may block on some systems with low entropy. See this page
118                 //for workaround suggestions:
119                 //http://docs.codehaus.org/display/JETTY/Connectors+slow+to+startup
120                 Log.debug("Init SecureRandom."); 
121                 _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM);
122             }
123             catch (NoSuchAlgorithmException e)
124             {
125                 try
126                 {
127                     _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT);
128                     _weakRandom=false;
129                 }
130                 catch (NoSuchAlgorithmException e_alt)
131                 {
132                     Log.warn("Could not generate SecureRandom for session-id randomness",e);
133                     _random=new Random();
134                     _weakRandom=true;
135                 }
136             }
137         }
138         _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
139         _sessions=new MultiMap<String>(true);
140     }
141 
142     /* ------------------------------------------------------------ */
143     protected void doStop()
144     {
145         if (_sessions!=null)
146             _sessions.clear(); // Maybe invalidate?
147         _sessions=null;
148     }
149 
150     /* ------------------------------------------------------------ */
151     /*
152      * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#idInUse(java.lang.String)
153      */
154     public boolean idInUse(String id)
155     {
156         return _sessions.containsKey(id);
157     }
158 
159     /* ------------------------------------------------------------ */
160     /*
161      * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#addSession(javax.servlet.http.HttpSession)
162      */
163     public void addSession(HttpSession session)
164     {
165         _sessions.add(getClusterId(session.getId()),session);
166     }
167 
168     /* ------------------------------------------------------------ */
169     /*
170      * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#addSession(javax.servlet.http.HttpSession)
171      */
172     public void removeSession(HttpSession session)
173     {
174         _sessions.removeValue(getClusterId(session.getId()),session);
175     }
176 
177     /* ------------------------------------------------------------ */
178     /*
179      * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#invalidateAll(java.lang.String)
180      */
181     public void invalidateAll(String id)
182     {
183 	// Do not use interators as this method tends to be called recursively 
184 	// by the invalidate calls.
185 	while (_sessions.containsKey(id))
186 	{
187 	    Session session=(Session)_sessions.getValue(id,0);
188 	    if (session.isValid())
189 		session.invalidate();
190 	    else
191 		_sessions.removeValue(id,session);
192 	}
193     }
194 
195     /* ------------------------------------------------------------ */
196     /*
197      * new Session ID. If the request has a requestedSessionID which is unique,
198      * that is used. The session ID is created as a unique random long XORed with
199      * connection specific information, base 36.
200      * @param request 
201      * @param created 
202      * @return Session ID.
203      */
204     public String newSessionId(HttpServletRequest request, long created)
205     {
206         synchronized (this)
207         {
208             // A requested session ID can only be used if it is in use already.
209             String requested_id=request.getRequestedSessionId();
210 
211             if (requested_id!=null)
212             {
213                 String cluster_id=getClusterId(requested_id);
214                 if (idInUse(cluster_id))
215                     return cluster_id;
216             }
217 
218             // Else reuse any new session ID already defined for this request.
219             String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
220             if (new_id!=null&&idInUse(new_id))
221                 return new_id;
222 
223             // pick a new unique ID!
224             String id=null;
225             while (id==null||id.length()==0||idInUse(id))
226             {
227                 long r=_weakRandom
228                 ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
229                 :_random.nextLong();
230                 r^=created;
231                 if (request.getRemoteAddr()!=null)
232                     r^=request.getRemoteAddr().hashCode();
233                 if (r<0)
234                     r=-r;
235                 id=Long.toString(r,36);
236             }
237 
238             request.setAttribute(__NEW_SESSION_ID,id);
239             return id;
240         }
241     }
242 
243     /* ------------------------------------------------------------ */
244     public Random getRandom()
245     {
246         return _random;
247     }
248 
249     /* ------------------------------------------------------------ */
250     public void setRandom(Random random)
251     {
252         _random=random;
253         _weakRandom=false;
254     }
255 
256 }