Last active
April 10, 2025 13:46
-
-
Save JamoCA/f1af7397d92b93eeea20 to your computer and use it in GitHub Desktop.
An update to a ColdFusion-based limiter UDF to throttle requests. Using HTTP status 429, CacheGet/Push (for automatic collection flushing) and cookie filters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cffunction name="limiter"> | |
<!--- | |
by Charlie Arehart, [email protected], in 2009, updated 2012 | |
http://www.carehart.org/blog/client/index.cfm/2010/5/21/throttling_by_ip_address | |
- Throttles requests made more than "count" times within "duration" seconds from single IP. | |
- sends 503 status code for bots to consider as well as text for humans to read | |
- also logs to a new "limiter.log" that is created automatically in cf logs directory, tracking when limits are hit, to help fine tune | |
- note that since it relies on the application scope, you need to place the call to it AFTER a cfapplication tag in application.cfm | |
- updated 10/16/12: now adds a test around the actual throttling code, so that it applies only to requests that present no cookie, so should only impact spiders, bots, and other automated requests. A "legit" user in a regular browser will be given a cookie by CF after their first visit and so would no longer be throttled. | |
- I also tweaked the cflog output to be more like a csv-format output | |
Rewrite by James Moberg, [email protected] 2/8/2016 | |
- Remove reliance of application scope. | |
- Generate request key using server name, IP address & CGI.User_Agent | |
- Use CacheGet/CachePut to auto-flush expired request data based on time (default: 5 minutes) | |
- Add Cookie Bypass filter. Set to "" to filter all requests. "*" will ignore any request w/a cookie. "cftoken" will ignore requests with a CFToken cookie. | |
- Use HTTP Status 429 (based on practices from other rate limiter libraries) | |
- Sample call: limiter(count, duration, "*"); | |
---> | |
<cfargument name="count" type="numeric" default="3"> | |
<cfargument name="duration" type="numeric" default="3"> | |
<cfargument name="cookieBypass" type="string" default="*"> | |
<cfset var thisCookieName = ""> | |
<cfset var CurrentLimiter = ""> | |
<cfset var Config = { | |
browserKey = "#CGI.Server_Name#_#CGI.Remote_Addr#_#CGI.Http_User_Agent#", | |
tempData = {}, | |
CacheTTL = CreateTimeSpan(0,0,5,0), | |
CookieBypass = 0, | |
Message = "Too Many Requests - your IP is being rate limited." | |
}> | |
<!--- Alt message: You are making too many requests too fast, please slow down and wait #arguments.duration# seconds ---> | |
<CFIF arguments.cookieBypass IS "*" AND LEN(TRIM(cgi.http_cookie))> | |
<CFSET Config.CookieBypass = 1> | |
<CFELSEIF LEN(arguments.cookieBypass) AND LEN(TRIM(cgi.http_cookie))> | |
<CFLOOP LIST="#arguments.cookieBypass#" INDEX="thisCookieName"> | |
<CFIF StructKeyExists(Cookie, thisCookieName)> | |
<CFSET Config.CookieBypass = 1> | |
<CFBREAK> | |
</CFIF> | |
</CFLOOP> | |
</CFIF> | |
<CFIF VAL(Config.CookieBypass)> | |
<CFRETURN> | |
</CFIF> | |
<cfset CurrentLimiter = cacheGet(config.browserKey)> | |
<CFIF isNull(CurrentLimiter)> | |
<cfset config.tempData = StructNew()> | |
<cfset config.tempData.attempts = 1> | |
<cfset config.tempData.last_attempt = Now()> | |
<CFSET cachePut(config.browserKey, config.tempData, config.CacheTTL)> | |
<CFELSEIF isDate(CurrentLimiter.last_attempt) AND DateDiff("s", CurrentLimiter.last_attempt, Now()) LT arguments.duration> | |
<cfset CurrentLimiter.attempts = CurrentLimiter.attempts + 1> | |
<cfset CurrentLimiter.last_attempt = Now()> | |
<cfset cachePut(config.browserKey, CurrentLimiter, config.CacheTTL)> | |
<cfif CurrentLimiter.attempts GT arguments.count> | |
<cflog file="limiter" text="'limiter invoked for:','#cgi.remote_addr#',#CurrentLimiter.attempts#,#cgi.request_method#,'#cgi.SCRIPT_NAME#', '#cgi.QUERY_STRING#','#cgi.http_user_agent#','#CurrentLimiter.last_attempt#',#StructCount(Cookie)#"> | |
<cfheader statuscode="429" statustext="Too Many Requests"> | |
<cfheader name="Retry-After" value="#arguments.duration#"> | |
<cfheader name="X-Rate-Limit-Reset" value="#arguments.duration#"> | |
<cfcontent type="text/html; charset=UTF-8"> | |
<cfoutput><p>#Config.Message#</p></cfoutput> | |
<cfabort> | |
</cfif> | |
<CFELSE> | |
<cfset config.tempData = StructNew()> | |
<cfset config.tempData.attempts = 1> | |
<cfset config.tempData.last_attempt = Now()> | |
<CFSET cachePut(config.browserKey, config.tempData, config.CacheTTL)> | |
</CFIF> | |
</cffunction> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment