1   package org.mortbay.servlet;
2   
3   import java.io.IOException;
4   import java.util.Queue;
5   import java.util.concurrent.Semaphore;
6   import java.util.concurrent.TimeUnit;
7   
8   import javax.servlet.Filter;
9   import javax.servlet.FilterChain;
10  import javax.servlet.FilterConfig;
11  import javax.servlet.ServletContext;
12  import javax.servlet.ServletException;
13  import javax.servlet.ServletRequest;
14  import javax.servlet.ServletResponse;
15  import javax.servlet.http.HttpServletResponse;
16  
17  import org.mortbay.util.ArrayQueue;
18  
19  /**
20   * Quality of Service Filter.
21   * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10).
22   * If more requests are received, they are suspended and placed on priority queues.  Priorities are determined by 
23   * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" 
24   * init parameter (default 10), with higher values having higher priority.
25   * <p>
26   * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire
27   * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be
28   * avoided if the semaphore is shortly available.
29   * 
30   * @author gregw
31   *
32   */
33  public class QoSFilter implements Filter
34  {
35      final static int __DEFAULT_MAX_PRIORITY=10;
36      final static int __DEFAULT_PASSES=10;
37      final static int __DEFAULT_WAIT_MS=50;
38      
39      ServletContext _context;
40      long _waitMs;
41      Semaphore _passes;
42      Queue<ServletRequest>[] _queue;
43      String _suspended="QoSFilter@"+this.hashCode();
44      
45      public void init(FilterConfig filterConfig) 
46      {
47          _context=filterConfig.getServletContext();
48          
49          int max_priority=__DEFAULT_MAX_PRIORITY;
50          if (filterConfig.getInitParameter("maxPriority")!=null)
51              max_priority=Integer.parseInt(filterConfig.getInitParameter("maxPriority"));
52          _queue=new Queue[max_priority+1];
53          for (int p=0;p<_queue.length;p++)
54              _queue[p]=new ArrayQueue<ServletRequest>();
55          
56          int passes=__DEFAULT_PASSES;
57          if (filterConfig.getInitParameter("maxRequests")!=null)
58              passes=Integer.parseInt(filterConfig.getInitParameter("maxRequests"));
59          _passes=new Semaphore(passes,true);
60          
61          long wait = __DEFAULT_WAIT_MS;
62          if (filterConfig.getInitParameter("waitMs")!=null)
63              wait=Integer.parseInt(filterConfig.getInitParameter("waitMs"));
64          _waitMs=wait;
65      }
66      
67      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
68      throws IOException, ServletException
69      {
70          boolean accepted=false;
71  
72          try
73          {
74              if (request.isInitial() || request.getAttribute(_suspended)==null)
75              {
76                  accepted=_passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
77                  
78                  if (accepted)
79                  {
80                      request.setAttribute(_suspended,Boolean.FALSE);
81                  }
82                  else
83                  {
84                      request.setAttribute(_suspended,Boolean.TRUE);
85                      request.suspend();
86                      int priority = getPriority(request);
87                      _queue[priority].add(request);
88                      return;
89                  }
90              }
91              else
92              {
93                  Boolean suspended=(Boolean)request.getAttribute(_suspended);
94                  
95                  if (suspended.booleanValue())
96                  {
97                      request.setAttribute(_suspended,Boolean.FALSE);
98                      if (request.isResumed())
99                      {
100                         _passes.acquire();
101                         accepted=true;
102                     }
103                     else 
104                     {
105                         // Timeout! try 1 more time.
106                         accepted = _passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
107                     }
108                 }
109                 else
110                 {
111                     // pass through resume of previously accepted request
112                     _passes.acquire();
113                     accepted = true;
114                 }
115             }
116             
117 
118             if (accepted)
119                 chain.doFilter(request,response);
120             else
121                 ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
122             
123             
124             
125         }
126         catch(InterruptedException e)
127         {
128             _context.log("QoS",e);
129             ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
130         }
131         finally
132         {
133             if (accepted)
134             {
135                 for (int p=_queue.length;p-->0;)
136                 {
137                     ServletRequest r=_queue[p].poll();
138                     if (r!=null)
139                     {
140                         r.resume();
141                         break;
142                     }
143                 }
144                 _passes.release();
145             }
146         }
147     }
148 
149     /**
150      * @param request
151      * @return
152      */
153     protected int getPriority(ServletRequest request)
154     {
155         return 0;
156     }
157 
158 
159     public void destroy(){}
160 
161 }