1   //========================================================================
2   //Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
3   //------------------------------------------------------------------------
4   //Licensed under the Apache License, Version 2.0 (the "License");
5   //you may not use this file except in compliance with the License.
6   //You may obtain a copy of the License at 
7   //http://www.apache.org/licenses/LICENSE-2.0
8   //Unless required by applicable law or agreed to in writing, software
9   //distributed under the License is distributed on an "AS IS" BASIS,
10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  //See the License for the specific language governing permissions and
12  //limitations under the License.
13  //========================================================================
14  
15  package org.mortbay.servlet;
16  
17  import java.io.IOException;
18  import java.util.Queue;
19  import java.util.concurrent.Semaphore;
20  import java.util.concurrent.TimeUnit;
21  
22  import javax.servlet.Filter;
23  import javax.servlet.FilterChain;
24  import javax.servlet.FilterConfig;
25  import javax.servlet.ServletContext;
26  import javax.servlet.ServletException;
27  import javax.servlet.ServletRequest;
28  import javax.servlet.ServletResponse;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  import javax.servlet.http.HttpSession;
32  
33  import org.mortbay.util.ArrayQueue;
34  
35  /**
36   * Quality of Service Filter.
37   * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10).
38   * If more requests are received, they are suspended and placed on priority queues.  Priorities are determined by 
39   * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" 
40   * init parameter (default 10), with higher values having higher priority.
41   * <p>
42   * This filter is ideal to prevent wasting threads waiting for slow/limited 
43   * resources such as a JDBC connection pool.  It avoids the situation where all of a 
44   * containers thread pool may be consumed blocking on such a slow resource.
45   * By limiting the number of active threads, a smaller thread pool may be used as 
46   * the threads are not wasted waiting.  Thus more memory may be available for use by 
47   * the active threads.
48   * <p>
49   * Furthermore, this filter uses a priority when resuming waiting requests. So that if
50   * a container is under load, and there are many requests waiting for resources,
51   * the {@link #getPriority(ServletRequest)} method is used, so that more important 
52   * requests are serviced first.     For example, this filter could be deployed with a 
53   * maxRequest limit slightly smaller than the containers thread pool and a high priority 
54   * allocated to admin users.  Thus regardless of load, admin users would always be
55   * able to access the web application.
56   * <p>
57   * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire
58   * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be
59   * avoided if the semaphore is shortly available.  If the semaphore cannot be obtained, the request will be suspended
60   * for the default suspend period of the container or the valued set as the "suspendMs" init parameter.
61   * 
62   * @author gregw
63   *
64   */
65  public class QoSFilter implements Filter
66  {
67      final static int __DEFAULT_MAX_PRIORITY=10;
68      final static int __DEFAULT_PASSES=10;
69      final static int __DEFAULT_WAIT_MS=50;
70      final static long __DEFAULT_TIMEOUT_MS = -1;
71      
72      final static String MAX_REQUESTS_INIT_PARAM="maxRequests";
73      final static String MAX_PRIORITY_INIT_PARAM="maxPriority";
74      final static String MAX_WAIT_INIT_PARAM="waitMs";
75      final static String SUSPEND_INIT_PARAM="suspendMs";
76      
77      ServletContext _context;
78      long _waitMs;
79      long _suspendMs;
80      Semaphore _passes;
81      Queue<ServletRequest>[] _queue;
82      String _suspended="QoSFilter@"+this.hashCode();
83      
84      public void init(FilterConfig filterConfig) 
85      {
86          _context=filterConfig.getServletContext();
87          
88          int max_priority=__DEFAULT_MAX_PRIORITY;
89          if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)!=null)
90              max_priority=Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM));
91          _queue=new Queue[max_priority+1];
92          for (int p=0;p<_queue.length;p++)
93              _queue[p]=new ArrayQueue<ServletRequest>();
94          
95          int passes=__DEFAULT_PASSES;
96          if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)!=null)
97              passes=Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM));
98          _passes=new Semaphore(passes,true);
99          
100         long wait = __DEFAULT_WAIT_MS;
101         if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)!=null)
102             wait=Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
103         _waitMs=wait;
104         
105         long suspend = __DEFAULT_TIMEOUT_MS;
106         if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM)!=null)
107             suspend=Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM));
108         _suspendMs=suspend;
109     }
110     
111     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
112     throws IOException, ServletException
113     {
114         boolean accepted=false;
115         try
116         {
117             if (request.isInitial() || request.getAttribute(_suspended)==null)
118             {
119                 accepted=_passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
120                 if (accepted)
121                 {
122                     request.setAttribute(_suspended,Boolean.FALSE);
123                 }
124                 else
125                 {
126                     request.setAttribute(_suspended,Boolean.TRUE);
127                     if (_suspendMs<=0)
128                         request.suspend();
129                     else
130                         request.suspend(_suspendMs);
131                     int priority = getPriority(request);
132                     _queue[priority].add(request);
133                     return;
134                 }
135             }
136             else
137             {
138                 Boolean suspended=(Boolean)request.getAttribute(_suspended);
139                 
140                 if (suspended.booleanValue())
141                 {
142                     request.setAttribute(_suspended,Boolean.FALSE);
143                     if (request.isResumed())
144                     {
145                         _passes.acquire();
146                         accepted=true;
147                     }
148                     else 
149                     {
150                         // Timeout! try 1 more time.
151                         accepted = _passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
152                     }
153                 }
154                 else
155                 {
156                     // pass through resume of previously accepted request
157                     _passes.acquire();
158                     accepted = true;
159                 }
160             }
161             
162 
163             if (accepted)
164             {
165                 chain.doFilter(request,response);
166             }
167             else
168             {
169                 ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
170             }
171             
172             
173             
174         }
175         catch(InterruptedException e)
176         {
177             _context.log("QoS",e);
178             ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
179         }
180         finally
181         {
182             if (accepted)
183             {
184                 for (int p=_queue.length;p-->0;)
185                 {
186                     ServletRequest r=_queue[p].poll();
187                     if (r!=null)
188                     {
189                         r.resume();
190                         break;
191                     }
192                 }
193                 _passes.release();
194             }
195         }
196     }
197 
198     /** 
199      * Get the request Priority.
200      * <p> The default implementation assigns the following priorities:<ul>
201      * <li> 2 - for a authenticated request
202      * <li> 1 - for a request with valid /non new session 
203      * <li> 0 - for all other requests.
204      * </ul>
205      * This method may be specialised to provide application specific priorities.
206      * 
207      * @param request
208      * @return
209      */
210     protected int getPriority(ServletRequest request)
211     {
212         HttpServletRequest base_request = (HttpServletRequest)request;
213         if (base_request.getUserPrincipal() != null )
214             return 2;
215         else 
216         {
217             HttpSession session = base_request.getSession(false);
218             if (session!=null && !session.isNew()) 
219                 return 1;
220             else
221                 return 0;
222         }
223     }
224 
225 
226     public void destroy(){}
227 
228 }