]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java
Merge "Update notes about password security"
[gigi.git] / lib / jetty / org / eclipse / jetty / server / session / HashSessionManager.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2016 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.server.session;
20
21 import java.io.DataInputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.TimeUnit;
32
33 import javax.servlet.ServletContext;
34 import javax.servlet.http.HttpServletRequest;
35
36 import org.eclipse.jetty.server.handler.ContextHandler;
37 import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
38 import org.eclipse.jetty.util.log.Logger;
39 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
40 import org.eclipse.jetty.util.thread.Scheduler;
41
42
43 /* ------------------------------------------------------------ */
44 /** 
45  * HashSessionManager
46  * 
47  * An in-memory implementation of SessionManager.
48  * <p>
49  * This manager supports saving sessions to disk, either periodically or at shutdown.
50  * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
51  * <p>
52  * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
53  * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
54  *
55  */
56 public class HashSessionManager extends AbstractSessionManager
57 {
58     final static Logger LOG = SessionHandler.LOG;
59
60     protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
61     private Scheduler _timer;
62     private Scheduler.Task _task;
63     long _scavengePeriodMs=30000;
64     long _savePeriodMs=0; //don't do period saves by default
65     long _idleSavePeriodMs = 0; // don't idle save sessions by default.
66     private Scheduler.Task _saveTask;
67     File _storeDir;
68     private boolean _lazyLoad=false;
69     private volatile boolean _sessionsLoaded=false;
70     private boolean _deleteUnrestorableSessions=false;
71
72
73     /**
74      * Scavenger
75      *
76      */
77     protected class Scavenger implements Runnable
78     {
79         @Override
80         public void run()
81         {
82             try
83             {
84                 scavenge();
85             }
86             finally
87             {
88                 if (_timer != null && _timer.isRunning()) {
89                     _task = _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
90                 }
91             }
92         }
93     }
94
95     /**
96      * Saver
97      *
98      */
99     protected class Saver implements Runnable
100     {
101         @Override
102         public void run()
103         {
104             try
105             {
106                 saveSessions(true);
107             }
108             catch (Exception e)
109             {       
110                 LOG.warn(e);
111             }
112             finally
113             {
114                 if (_timer != null && _timer.isRunning())
115                     _saveTask = _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
116             }
117         }        
118     }
119
120
121     /* ------------------------------------------------------------ */
122     public HashSessionManager()
123     {
124         super();
125     }
126
127     /* ------------------------------------------------------------ */
128     /**
129      * @see AbstractSessionManager#doStart()
130      */
131     @Override
132     public void doStart() throws Exception
133     {
134         //try shared scheduler from Server first
135         _timer = getSessionHandler().getServer().getBean(Scheduler.class);
136         if (_timer == null)
137         {
138             //try one passed into the context
139             ServletContext context = ContextHandler.getCurrentContext();
140             if (context!=null)
141                 _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");   
142         }    
143       
144         if (_timer == null)
145         {
146             //make a scheduler if none useable
147             _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
148             addBean(_timer,true);
149         }
150         else
151             addBean(_timer,false);
152         
153         super.doStart();
154
155         setScavengePeriod(getScavengePeriod());
156
157         if (_storeDir!=null)
158         {
159             if (!_storeDir.exists())
160                 _storeDir.mkdirs();
161
162             if (!_lazyLoad)
163                 restoreSessions();
164         }
165
166         setSavePeriod(getSavePeriod());
167     }
168
169     /* ------------------------------------------------------------ */
170     /**
171      * @see AbstractSessionManager#doStop()
172      */
173     @Override
174     public void doStop() throws Exception
175     {
176         // stop the scavengers
177         synchronized(this)
178         {
179             if (_saveTask!=null)
180                 _saveTask.cancel();
181
182             _saveTask=null;
183             if (_task!=null)
184                 _task.cancel();
185
186             _task=null;
187             _timer=null;
188         }
189        
190
191         // This will callback invalidate sessions - where we decide if we will save
192         super.doStop();
193
194         _sessions.clear();
195
196     }
197
198     /* ------------------------------------------------------------ */
199     /**
200      * @return the period in seconds at which a check is made for sessions to be invalidated.
201      */
202     public int getScavengePeriod()
203     {
204         return (int)(_scavengePeriodMs/1000);
205     }
206
207
208     /* ------------------------------------------------------------ */
209     @Override
210     public int getSessions()
211     {
212         int sessions=super.getSessions();
213         if (LOG.isDebugEnabled())
214         {
215             if (_sessions.size()!=sessions)
216                 LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
217         }
218         return sessions;
219     }
220
221     /* ------------------------------------------------------------ */
222     /**
223      * @return seconds Idle period after which a session is saved
224      */
225     public int getIdleSavePeriod()
226     {
227       if (_idleSavePeriodMs <= 0)
228         return 0;
229
230       return (int)(_idleSavePeriodMs / 1000);
231     }
232
233     /* ------------------------------------------------------------ */
234     /**
235      * Configures the period in seconds after which a session is deemed idle and saved
236      * to save on session memory.
237      *
238      * The session is persisted, the values attribute map is cleared and the session set to idled.
239      *
240      * @param seconds Idle period after which a session is saved
241      */
242     public void setIdleSavePeriod(int seconds)
243     {
244       _idleSavePeriodMs = seconds * 1000L;
245     }
246
247     /* ------------------------------------------------------------ */
248     @Override
249     public void setMaxInactiveInterval(int seconds)
250     {
251         super.setMaxInactiveInterval(seconds);
252         if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
253             setScavengePeriod((_dftMaxIdleSecs+9)/10);
254     }
255
256     /* ------------------------------------------------------------ */
257     /**
258      * @param seconds the period is seconds at which sessions are periodically saved to disk
259      */
260     public void setSavePeriod (int seconds)
261     {
262         long period = (seconds * 1000L);
263         if (period < 0)
264             period=0;
265         _savePeriodMs=period;
266
267         if (_timer!=null)
268         {
269             synchronized (this)
270             {
271                 if (_saveTask!=null)
272                     _saveTask.cancel();
273                 _saveTask = null;
274                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
275                 {
276                     _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
277                 }
278             }
279         }
280     }
281
282     /* ------------------------------------------------------------ */
283     /**
284      * @return the period in seconds at which sessions are periodically saved to disk
285      */
286     public int getSavePeriod ()
287     {
288         if (_savePeriodMs<=0)
289             return 0;
290
291         return (int)(_savePeriodMs/1000);
292     }
293
294     /* ------------------------------------------------------------ */
295     /**
296      * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
297      */
298     public void setScavengePeriod(int seconds)
299     { 
300         if (seconds==0)
301             seconds=60;
302
303         long old_period=_scavengePeriodMs;
304         long period=seconds*1000L;
305         if (period>60000)
306             period=60000;
307         if (period<1000)
308             period=1000;
309
310         _scavengePeriodMs=period;
311     
312         synchronized (this)
313         {
314             if (_timer!=null && (period!=old_period || _task==null))
315             {
316                 if (_task!=null)
317                 {
318                     _task.cancel();
319                     _task = null;
320                 }
321
322                 _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
323             }
324         }
325     }
326
327     /* -------------------------------------------------------------- */
328     /**
329      * Find sessions that have timed out and invalidate them. This runs in the
330      * SessionScavenger thread.
331      */
332     protected void scavenge()
333     {
334         //don't attempt to scavenge if we are shutting down
335         if (isStopping() || isStopped())
336             return;
337
338         Thread thread=Thread.currentThread();
339         ClassLoader old_loader=thread.getContextClassLoader();
340         try
341         {      
342             if (_loader!=null)
343                 thread.setContextClassLoader(_loader);
344
345             // For each session
346             long now=System.currentTimeMillis();
347             __log.debug("Scavenging sessions at {}", now); 
348             
349             for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
350             {
351                 HashedSession session=i.next();
352                 long idleTime=session.getMaxInactiveInterval()*1000L; 
353                 if (idleTime>0&&session.getAccessed()+idleTime<now)
354                 {
355                     // Found a stale session, add it to the list
356                     try
357                     {
358                         session.timeout();
359                     }
360                     catch (Exception e)
361                     {
362                         __log.warn("Problem scavenging sessions", e);
363                     }
364                 }
365                 else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
366                 {
367                     try
368                     {
369                         session.idle();
370                     }
371                     catch (Exception e)
372                     {
373                         __log.warn("Problem idling session "+ session.getId(), e);
374                     }
375                 }
376             }
377         }       
378         finally
379         {
380             thread.setContextClassLoader(old_loader);
381         }
382     }
383
384     /* ------------------------------------------------------------ */
385     @Override
386     protected void addSession(AbstractSession session)
387     {
388         if (isRunning())
389             _sessions.put(session.getClusterId(),(HashedSession)session);
390     }
391
392     /* ------------------------------------------------------------ */
393     @Override
394     public AbstractSession getSession(String idInCluster)
395     {
396         if ( _lazyLoad && !_sessionsLoaded)
397         {
398             try
399             {
400                 restoreSessions();
401             }
402             catch(Exception e)
403             {
404                 LOG.warn(e);
405             }
406         }
407
408         Map<String,HashedSession> sessions=_sessions;
409         if (sessions==null)
410             return null;
411
412         HashedSession session = sessions.get(idInCluster);
413
414         if (session == null && _lazyLoad)
415             session=restoreSession(idInCluster);
416         if (session == null)
417             return null;
418
419         if (_idleSavePeriodMs!=0)
420             session.deIdle();
421
422         return session;
423     }
424
425     /* ------------------------------------------------------------ */
426     @Override
427     protected void shutdownSessions() throws Exception
428     {   
429         // Invalidate all sessions to cause unbind events
430         ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
431         int loop=100;
432         while (sessions.size()>0 && loop-->0)
433         {
434             // If we are called from doStop
435             if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
436             {
437                 // Then we only save and remove the session from memory- it is not invalidated.
438                 for (HashedSession session : sessions)
439                 {
440                     session.save(false);
441                     _sessions.remove(session.getClusterId());
442                 }
443             }
444             else
445             {
446                 for (HashedSession session : sessions)
447                     session.invalidate();
448             }
449
450             // check that no new sessions were created while we were iterating
451             sessions=new ArrayList<HashedSession>(_sessions.values());
452         }
453     }
454     
455     
456     
457     /* ------------------------------------------------------------ */
458     /**
459      * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
460      */
461     @Override
462     public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
463     {
464         try
465         {
466             Map<String,HashedSession> sessions=_sessions;
467             if (sessions == null)
468                 return;
469
470             HashedSession session = sessions.remove(oldClusterId);
471             if (session == null)
472                 return;
473
474             session.remove(); //delete any previously saved session
475             session.setClusterId(newClusterId); //update ids
476             session.setNodeId(newNodeId);
477             session.save(); //save updated session: TODO consider only saving file if idled
478             sessions.put(newClusterId, session);
479             
480             super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
481         }
482         catch (Exception e)
483         {
484             LOG.warn(e);
485         }
486     }
487
488     /* ------------------------------------------------------------ */
489     @Override
490     protected AbstractSession newSession(HttpServletRequest request)
491     {
492         return new HashedSession(this, request);
493     }
494
495     /* ------------------------------------------------------------ */
496     protected AbstractSession newSession(long created, long accessed, String clusterId)
497     {
498         return new HashedSession(this, created,accessed, clusterId);
499     }
500
501     /* ------------------------------------------------------------ */
502     @Override
503     protected boolean removeSession(String clusterId)
504     {
505         return _sessions.remove(clusterId)!=null;
506     }
507
508     /* ------------------------------------------------------------ */
509     public void setStoreDirectory (File dir) throws IOException
510     { 
511         // CanonicalFile is used to capture the base store directory in a way that will
512         // work on Windows.  Case differences may through off later checks using this directory.
513         _storeDir=dir.getCanonicalFile();
514     }
515
516     /* ------------------------------------------------------------ */
517     public File getStoreDirectory ()
518     {
519         return _storeDir;
520     }
521
522     /* ------------------------------------------------------------ */
523     public void setLazyLoad(boolean lazyLoad)
524     {
525         _lazyLoad = lazyLoad;
526     }
527
528     /* ------------------------------------------------------------ */
529     public boolean isLazyLoad()
530     {
531         return _lazyLoad;
532     }
533
534     /* ------------------------------------------------------------ */
535     public boolean isDeleteUnrestorableSessions()
536     {
537         return _deleteUnrestorableSessions;
538     }
539
540     /* ------------------------------------------------------------ */
541     public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
542     {
543         _deleteUnrestorableSessions = deleteUnrestorableSessions;
544     }
545
546     /* ------------------------------------------------------------ */
547     public void restoreSessions () throws Exception
548     {
549         _sessionsLoaded = true;
550
551         if (_storeDir==null || !_storeDir.exists())
552         {
553             return;
554         }
555
556         if (!_storeDir.canRead())
557         {
558             LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
559             return;
560         }
561
562         String[] files = _storeDir.list();
563         for (int i=0;files!=null&&i<files.length;i++)
564         {
565             restoreSession(files[i]);
566         }
567     }
568
569     /* ------------------------------------------------------------ */
570     protected synchronized HashedSession restoreSession(String idInCuster)
571     {        
572         File file = new File(_storeDir,idInCuster);
573
574         Exception error = null;
575         if (!file.exists())
576         {
577             if (LOG.isDebugEnabled())
578             {
579                 LOG.debug("Not loading: {}",file);
580             }
581             return null;
582         }
583         
584         try (FileInputStream in = new FileInputStream(file))
585         {
586             HashedSession session = restoreSession(in,null);
587             addSession(session,false);
588             session.didActivate();
589             return session;
590         }
591         catch (Exception e)
592         {
593            error = e;
594         }
595         finally
596         {
597             if (error != null)
598             {
599                 if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
600                 {
601                     file.delete();
602                     LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
603                 }
604                 else
605                 {
606                     __log.warn("Problem restoring session "+idInCuster, error);
607                 }
608             }
609             else
610             {
611                 // delete successfully restored file
612                 file.delete();
613             }
614         }
615         return null;
616     }
617
618     /* ------------------------------------------------------------ */
619     public void saveSessions(boolean reactivate) throws Exception
620     {
621         if (_storeDir==null || !_storeDir.exists())
622         {
623             return;
624         }
625
626         if (!_storeDir.canWrite())
627         {
628             LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
629             return;
630         }
631
632         for (HashedSession session : _sessions.values())
633             session.save(reactivate);
634     }
635     
636
637     /* ------------------------------------------------------------ */
638     public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
639     {
640         DataInputStream di = new DataInputStream(is);
641
642         String clusterId = di.readUTF();
643         di.readUTF(); // nodeId
644
645         long created = di.readLong();
646         long accessed = di.readLong();
647         int requests = di.readInt();
648
649         if (session == null)
650             session = (HashedSession)newSession(created, accessed, clusterId);
651         
652         session.setRequests(requests);
653
654         // Attributes
655         int size = di.readInt();
656
657         restoreSessionAttributes(di, size, session);
658
659         try
660         {
661             int maxIdle = di.readInt();
662             session.setMaxInactiveInterval(maxIdle);
663         }
664         catch (IOException e)
665         {
666             LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
667             LOG.ignore(e);
668         }
669
670         return session;
671     }
672
673
674     @SuppressWarnings("resource")
675     private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
676     throws Exception
677     {
678         if (size>0)
679         {
680             // input stream should not be closed here
681             ClassLoadingObjectInputStream ois =  new ClassLoadingObjectInputStream(is);
682             for (int i=0; i<size;i++)
683             {
684                 String key = ois.readUTF();
685                 Object value = ois.readObject();
686                 session.setAttribute(key,value);
687             }
688         }
689     }
690 }