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 }