protected void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { if (!isEnabled()) { filterChain.doFilter(request, response); return; } // Look for the rate tracker for this request. RateTracker tracker = (RateTracker) request.getAttribute(__TRACKER); if (tracker == null) { // This is the first time we have seen this request. if (LOG.isDebugEnabled()) LOG.debug("Filtering {}", request); // Get a rate tracker associated with this request, and record one hit. tracker = getRateTracker(request); // Calculate the rate and check it is over the allowed limit final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis()); // Pass it through if we are not currently over the rate limit. if (!overRateLimit) { if (LOG.isDebugEnabled()) LOG.debug("Allowing {}", request); doFilterChain(filterChain, request, response); return; } // We are over the limit. // So either reject it, delay it or throttle it. long delayMs = getDelayMs(); boolean insertHeaders = isInsertHeaders(); switch ((int) delayMs) { case -1: { // Reject this request. LOG.warn( "DOS ALERT: Request rejected ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal()); if (insertHeaders) response.addHeader("DoSFilter", "unavailable"); response.sendError(getTooManyCode()); return; } case 0: { // Fall through to throttle the request. LOG.warn( "DOS ALERT: Request throttled ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal()); request.setAttribute(__TRACKER, tracker); break; } default: { // Insert a delay before throttling the request, // using the suspend+timeout mechanism of AsyncContext. LOG.warn( "DOS ALERT: Request delayed={}ms, ip={}, session={}, user={}", delayMs, request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal()); if (insertHeaders) response.addHeader("DoSFilter", "delayed"); request.setAttribute(__TRACKER, tracker); AsyncContext asyncContext = request.startAsync(); if (delayMs > 0) asyncContext.setTimeout(delayMs); asyncContext.addListener(new DoSTimeoutAsyncListener()); return; } } } if (LOG.isDebugEnabled()) LOG.debug("Throttling {}", request); // Throttle the request. boolean accepted = false; try { // Check if we can afford to accept another request at this time. accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS); if (!accepted) { // We were not accepted, so either we suspend to wait, // or if we were woken up we insist or we fail. Boolean throttled = (Boolean) request.getAttribute(__THROTTLED); long throttleMs = getThrottleMs(); if (throttled != Boolean.TRUE && throttleMs > 0) { int priority = getPriority(request, tracker); request.setAttribute(__THROTTLED, Boolean.TRUE); if (isInsertHeaders()) response.addHeader("DoSFilter", "throttled"); AsyncContext asyncContext = request.startAsync(); request.setAttribute(_suspended, Boolean.TRUE); if (throttleMs > 0) asyncContext.setTimeout(throttleMs); asyncContext.addListener(_listeners[priority]); _queues[priority].add(asyncContext); if (LOG.isDebugEnabled()) LOG.debug("Throttled {}, {}ms", request, throttleMs); return; } Boolean resumed = (Boolean) request.getAttribute(_resumed); if (resumed == Boolean.TRUE) { // We were resumed, we wait for the next pass. _passes.acquire(); accepted = true; } } // If we were accepted (either immediately or after throttle)... if (accepted) { // ...call the chain. if (LOG.isDebugEnabled()) LOG.debug("Allowing {}", request); doFilterChain(filterChain, request, response); } else { // ...otherwise fail the request. if (LOG.isDebugEnabled()) LOG.debug("Rejecting {}", request); if (isInsertHeaders()) response.addHeader("DoSFilter", "unavailable"); response.sendError(getTooManyCode()); } } catch (InterruptedException e) { LOG.ignore(e); response.sendError(getTooManyCode()); } finally { if (accepted) { try { // Wake up the next highest priority request. for (int p = _queues.length - 1; p >= 0; --p) { AsyncContext asyncContext = _queues[p].poll(); if (asyncContext != null) { ServletRequest candidate = asyncContext.getRequest(); Boolean suspended = (Boolean) candidate.getAttribute(_suspended); if (suspended == Boolean.TRUE) { if (LOG.isDebugEnabled()) LOG.debug("Resuming {}", request); candidate.setAttribute(_resumed, Boolean.TRUE); asyncContext.dispatch(); break; } } } } finally { _passes.release(); } } } }
/** * Get priority for this request, based on user type * * @param request the current request * @param tracker the rate tracker for this request * @return the priority for this request */ protected int getPriority(HttpServletRequest request, RateTracker tracker) { if (extractUserId(request) != null) return USER_AUTH; if (tracker != null) return tracker.getType(); return USER_UNKNOWN; }
public void sessionDidActivate(HttpSessionEvent se) { RateTracker tracker = (RateTracker) se.getSession().getAttribute(__TRACKER); if (tracker != null) _rateTrackers.put(tracker.getId(), tracker); }