/* * Copyright (C) 2000 - 2010 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ /* * BlueDragon only tag. (Alan Williamson) * * This tag maintains a list of TOKENs and the time between * successive access of those TOKENs to allow for a throttle * mechanism. * * ACTION = THROTTLE (default) * - TOKEN = string of the token (defaults to the client IP) * - HITTHRESHOLD = number of times the token should be hit in a time period * - HITTIMEPERIOD = the time period in which the max hits can appear in * - MINHITTIME = the time where if a request comes in quicker its throttled * * returns CFTHROTTLE structure with a flag to say whether or not it should throttle * * * ACTION = STATUS * - returns a structure CFTHROTTLE of all the tokens * * * ACTION = SET * - HISTORY the size of the list (defaults to 100) * */ package com.naryx.tagfusion.cfm.tag.ext; import java.io.Serializable; import org.apache.commons.collections.map.LRUMap; import com.naryx.tagfusion.cfm.engine.cfArrayData; import com.naryx.tagfusion.cfm.engine.cfBooleanData; import com.naryx.tagfusion.cfm.engine.cfDateData; import com.naryx.tagfusion.cfm.engine.cfNumberData; import com.naryx.tagfusion.cfm.engine.cfSession; import com.naryx.tagfusion.cfm.engine.cfStringData; import com.naryx.tagfusion.cfm.engine.cfStructData; import com.naryx.tagfusion.cfm.engine.cfmBadFileException; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; import com.naryx.tagfusion.cfm.tag.cfTag; import com.naryx.tagfusion.cfm.tag.cfTagReturnType; public class cfTHROTTLE extends cfTag implements Serializable { static final long serialVersionUID = 1; transient static LRUMap throttleHistory = null; static int totalHit = 0, totalThrottled = 0, totalThrottledQuick = 0; static long startTime; private int actionType = 0; static int ACTION_THROTTLE = 0; static int ACTION_FLUSH = 1; static int ACTION_STATUS = 2; static int ACTION_SET = 3; protected void defaultParameters( String _tag ) throws cfmBadFileException { //-- If not initialised, initialise it if (throttleHistory == null){ throttleHistory = new LRUMap( 100 ); startTime = System.currentTimeMillis(); } defaultAttribute( "ACTION", "THROTTLE" ); defaultAttribute( "HITTHRESHOLD", "5" ); defaultAttribute( "HITTIMEPERIOD", "10000" ); defaultAttribute( "MINHITTIME", "500" ); parseTagHeader( _tag ); if ( getConstant("ACTION").equalsIgnoreCase("FLUSH") ){ actionType = ACTION_FLUSH; }else if ( getConstant("ACTION").equalsIgnoreCase("STATUS") ){ actionType = ACTION_STATUS; }else if ( getConstant("ACTION").equalsIgnoreCase("SET") ){ actionType = ACTION_SET; }else{ actionType = ACTION_THROTTLE; } } public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException { if ( actionType == ACTION_THROTTLE ){ //-- Determine the token; if not present then use the clients remote IP address String token; if ( containsAttribute("TOKEN") ) token = getDynamic(_Session,"TOKEN").getString(); else token = _Session.REQ.getRemoteAddr(); int hitThresHold = getDynamic(_Session,"HITTHRESHOLD").getInt(); long hitTimePeriodMs = getDynamic(_Session,"HITTIMEPERIOD").getLong(); long hitMinTimeMs = getDynamic(_Session,"MINHITTIME").getLong(); throttleClient client; cfStructData result = new cfStructData(); synchronized(throttleHistory){ client = (throttleClient)throttleHistory.get(token); if ( client == null ){ client = new throttleClient( token ); throttleHistory.put( token, client ); } } //-- Synchronize on this client synchronized( client ){ long lasthit = client.lastHit(); long age = client.age(); totalHit++; client.hit(); //-- Register this hit if ( age <= hitTimePeriodMs && client.hitCount >= hitThresHold ){ //- checks to make sure they are within their allocated amount result.put( "THROTTLE", cfBooleanData.TRUE ); client.throttled(); totalThrottled++; } else if ( lasthit > 10 && lasthit < hitMinTimeMs ){ //-- checks for too fast accesses between requests result.put( "THROTTLE", cfBooleanData.TRUE ); client.throttled(); totalThrottled++; totalThrottledQuick++; }else result.put( "THROTTLE", cfBooleanData.FALSE ); //-- If the age of this entry has expired then reset it. if ( age > hitTimePeriodMs ) client.reset(); //-- Set the data into the session result.setData("HITCOUNT", new cfNumberData(client.hitCount) ); result.setData("TOTALHITS", new cfNumberData(client.totalHits)); result.setData("LASTHIT", new cfNumberData(lasthit) ); result.setData("AGE", new cfNumberData(age) ); } _Session.setData( "CFTHROTTLE", result ); }else if ( actionType == ACTION_SET ){ //-- Check to see if the history length has changed or been passed in if ( containsAttribute("HISTORY") ){ throttleHistory = new LRUMap( getDynamic(_Session,"HISTORY").getInt() ); } }else if ( actionType == ACTION_FLUSH ){ //-- Clears out the history synchronized(throttleHistory){ throttleHistory.clear(); } }else if ( actionType == ACTION_STATUS ){ cfArrayData array = cfArrayData.createArray(1); synchronized(throttleHistory){ org.apache.commons.collections.OrderedMapIterator it = throttleHistory.orderedMapIterator(); throttleClient client; cfStructData clientData; while ( it.hasNext() ){ it.next(); client = (throttleClient)it.getValue(); clientData = new cfStructData(); clientData.setData("TOKEN", new cfStringData(client.token) ); clientData.setData("HIT", new cfNumberData(client.hitCount)); clientData.setData("TOTALHITS", new cfNumberData(client.totalHits)); clientData.setData("TOTALTHROTTLE", new cfNumberData(client.totalThrottled)); clientData.setData("THROTTLE", new cfNumberData(client.throttled)); clientData.setData("LASTHIT", new cfDateData(client.lastUsed) ); clientData.setData("AGE", new cfNumberData(client.age()) ); array.addElement(clientData); } } cfStructData throttle = new cfStructData(); throttle.setData("CLIENTS", array ); throttle.setData("HITS", new cfNumberData(totalHit) ); throttle.setData("THROTTLE", new cfNumberData(totalThrottled) ); throttle.setData("QUICKTHROTTLE", new cfNumberData(totalThrottledQuick) ); throttle.setData("STARTTIME", new cfDateData(startTime) ); _Session.setData("CFTHROTTLE", throttle ); } return cfTagReturnType.NORMAL; } //----------------------- class throttleClient extends Object { public String token; public int hitCount; public long lastUsed; public long created; public int throttled; public int totalHits, totalThrottled; public throttleClient(String _token){ token = _token; hitCount = 0; throttled = 0; totalHits = 0; totalThrottled = 0; lastUsed = System.currentTimeMillis(); created = lastUsed; } public final void throttled(){ throttled++; totalThrottled++; } public final void hit(){ hitCount++; totalHits++; lastUsed = System.currentTimeMillis(); } public final long age(){ return System.currentTimeMillis() - created; } public final long lastHit(){ return System.currentTimeMillis() - lastUsed; } public void reset(){ hitCount = 0; lastUsed = System.currentTimeMillis(); created = lastUsed; throttled = 0; } } }