Rate Limiting Calls To an Api Using Cache in ColdFusion - api

Hi I am using ColdFusion to call the last.fm api, using a cfc bundle sourced from here.
I am concerned about going over the request limit, which is 5 requests per originating IP address per second, averaged over a 5 minute period.
The cfc bundle has a central component which calls all the other components, which are split up into sections like "artist", "track" etc...This central component "lastFmApi.cfc." is initiated in my application, and persisted for the lifespan of the application
// Application.cfc example
<cffunction name="onApplicationStart">
<cfset var apiKey = '[your api key here]' />
<cfset var apiSecret = '[your api secret here]' />
<cfset application.lastFm = CreateObject('component', 'org.FrankFusion.lastFm.lastFmApi').init(apiKey, apiSecret) />
</cffunction>
Now if I want to call the api through a handler/controller, for example my artist handler...I can do this
<cffunction name="artistPage" cache="5 mins">
<cfset qAlbums = application.lastFm.user.getArtist(url.artistName) />
</cffunction>
I am a bit confused towards caching, but am caching each call to the api in this handler for 5 mins, but does this make any difference, because each time someone hits a new artist page wont this still count as a fresh hit against the api?
Wondering how best to tackle this
Thanks

Since it's simple data model I'd not complicate things with custom cache engines. I'd put simple struct/query : searchTerm/result,timestamp somewhere. There you could do this:
<cffunction name="artistPage" cache="5 mins">
<cfargument name="artistName" type="string" required="true"/>
<cfif structKeyExists(application.artistCache, arguments.artistName) and structfindkey(application.artistCache, arguments.artistName)>
<cfif (gettickcount() - application.artistCache[arguments.artistName].timestamp ) lte 5000 >
<cfset result = application.artistCache[arguments.artistName].result >
<cfelse>
<cfset qAlbums = application.lastFm.user.getArtist(arguments.artistName) />
<cfset tempStruct = structnew()>
<cfset structNew.result = qAlbums >
<cfset structNew.timestamp = getTickCount() >
<cfset structInsert(application.artistCache, arguments.artistName, tempstruct, true) >
<cfset result = qAlbums >
</cfif>
<cfreturn result >
</cffunction>
EDIT: yes, you should put somewhere also method which would remove struct keys where timestamp difference is gt then your cache validity period.
Facade pattern is recommended in order to make this changeable in future.
Sorry for typos :)

I would try the custom cache.
It can be a structure where keys are artist names or other unique identifiers of your entries.
If you are using CF9 or Railo 3.1.2+ you can use built-in caching (functions CachePut, CacheGet etc), it can handle for you the timeouts and stuff.
Otherwise you can store the cache into Application scope, but will need to include timestamp with each entry and check it on cache events (get/put/remove) or even on each request.

Related

How to re-trigger the default error screen from the onError function in application.cfc using ColdFusion 11?

I use the onError function inside Application.cfc to integrate with RayGun when the code is on live but when we are on dev I'd like to be able to revert back to the normal ColdFusion error event. At the moment I have some basic error handling in place but it isn't as good as the default ColdFusion behavior. Does anybody know if this is possible and how?
I intend to add this as a feature request for ColdFusion 12 if there is no way of doing it.
This works when I run it.
in Application.cfc
<cffunction name="onError" access="public" returntype="void">
<cfargument name="Exception" required=true type="any">
<cfif true>
<cfthrow object="#arguments.exception#">
<cfelse>
error
</cfif>
</cffunction>
in cfm page.
<cfscript>
X=Y; // Y is undefined
</cfscript>
All you have to do is replace <cfif true> with something that identifies your development environment.

Enable Coldfusion ORM per component

I've started using ORM in Coldfusion 9, but I'm running into an issue in which I've got a CFC that is set to persistant=true so that when I run myCFC.init() the default values of the properties are assigned - but I don't want to use this CFC with ORM.
The problem is that Coldfusion throws the error "Table myCFC defined for cfc myCFC does not exist."
Is there a way I can get my application to ignore certain CFCs? Or only pay attention to specific CFCs, other than persistant=true
Alternatively, can I get my default property values to take effect without making the component persistent
Alternatively, can I get my default property values to take effect without making the component persistent?
Yes, just set them in your init() method.
<cfcomponent name="person" persistent="false" output="false">
<cfproperty name="gender"><!--- Non-persistent CFC: you can't set a default here --->
<cffunction name="init" output="false>
<cfset variables.gender = "m"><!--- Set the default here --->
</cffunction>
</cfcomponent>
You would also need to do this in your persistent CFCs for any complex or dynamic value defaults (e.g. an array or the current date), since you can only set simple default values (e.g. a literal string or integer) in property declarations.
<cfcomponent name="person" persistent="true" table="persons" output="false">
<cfproperty name="gender" default="m"><!---Persistent CFC, so this simple default will be set --->
<cfproperty name="dateCreated"><!---You can't set a default dynamic date value --->
<cffunction name="init" output="false>
<cfset variables.dateCreated= Now()><!--- Set the current datetime here --->
</cffunction>
</cfcomponent>
Any code you place between your opening and the first will be executed. I'm assuming your using CFproperty tags to set you defaults. Instead, use this structure:
<cfcomponent name="aCFC">
<!---
|| Psuedo Constructor code: this code runs when the object is created.
||--->
<cfset defaultVar_1 = "default value">
...etc
<cffunction name="firstFunction">
...
</cffunction>
</cfcomponent>

CFWheels - How do I increment a number in a database column?

Just wondering, with CFWheels ORM, how I go about incrementing a number in a column?
In straight up SQL I would do something like:
UPDATE table
SET field = field + 1
WHERE ...
I'd like to avoid "fetching" the value from the column and incrementing it manually. I'll be doing a lot of this in my app, so it's an overhead I could do without (plus I'd also like to keep my code simple and elegant).
Could something like this work?
<cfset post = model("post").findByKey(99) />
<cfset post.update( myColumn = myColumn + 1 ) />
Appreciate some input.
You can abstract this in the base Model.cfc if you need to repeat it a lot:
<cfcomponent extends="Wheels">
<cffunction name="incrementColumn" returntype="boolean">
<cfargument name="key" type="string" required="true">
<cfargument name="property" type="string" required="true">
<cfset local.record = this.findByKey(arguments.key)>
<cfset local.record[arguments.property]++>
<cfreturn local.record.update()>
</cffunction>
</cfcomponent>
Then you can call it from your controller like so:
<cfif post = model("post").incrementColumn(params.key, "myColumn")>
Success
<cfelse>
Error
</cfif>
the orm in cfwheels doesn't support this. for stuff like that straight sql is the way to go. the best thing to do is to create a custom method on you model to encapsulate the logic.

Where do I define application and session variables in Coldfusion8 application.cfc?

I'm trying to setup my first application.cfc file.
I tried to invoke variables on an "event-basis" like so:
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfparam name="Application.RootDir" default="/">
<cfreturn true>
</cffunction>
<cffunction name="onSessionStart" returnType="boolean" output="false">
<cfparam name="Session.activeSession" default="Yes">
<cfparam name="Session.activeLogin" default="No">
<cfreturn true>
</cffunction>
I thought this would work nicely, but turns out, it does not, because I need to declare everything before, like so:
<cfparam name="Application.RootDir" default="">
<cfparam name="Session.activeSession" default="">
<cfparam name="Session.activeLogin" default="">
My Question:
Why should I use CF-events for declaring variables. If I need to declare outside of an event anyway, I could save myself a line of code and declare&assign the inital value? Am I doing it wrong or are events only for assinging not for declaring?
Thanks for input!
What do you mean by "before" ("declare everything before")? Before what? The onApplicationStart() handler is the first thing run when an application starts, and the onSessionStart() handler is the first thing run when a session starts. So in the context of applications and sessions being established, there is no "before". I think you need to show us more code: eg some code that demonstrates them "not working".
That said, you don't show us the pseudo-constructor section of your Application.cfc, so it's impossible to tell what problems you have there, if any. But you need to enable session management before sessions will work, and you possible need to name your app before either application or session variables will stick, too (although they might just work with the nameless application too? Even if they do, it's best to name your app).
Lastly - and this will have no bearing on whether your variables are set or not, but you should be using <cfset> when setting variables, not <cfparam>. Both have the same effect here, but the former is the correct tool for the job.

cfqueryparam questions/help

Via this question I've been told to start using cfqueryparam for my data, to prevent SQL injection attacks.
How do I use it for my forms? Right now I've been going over Ben Forta's book, Vol 1 and been passing data to my form, then to a form processor that calls a CFC. The CFC takes them in as a cfargument then injects that into the database with any type="x" validation.
Io use the cfqueryparam, I use that on the query itself and not even declare cfargument?
You can still use a CFC, but remember that string data passed as a function argument will still need <cfqueryparam>. Here is an example:
<cffunction name="saveData" access="public" returntype="void" output="false">
<cfargument name="formVar" type="string" required="true" />
<cfquery name="LOCAL.qSave" datasource="myDSN">
insert into myTable (col1)
values (<cfqueryparam cfsqltype="cf_sql_varchar" value="#ARGUMENTS.formVar#" />)
</cfquery>
</cffunction>
The important habit to get into is to always use <cfqueryparam>, even in CFCs.
Here is some more info on those edge-cases where you might find it hard to use <cfqueryparam>.
Hope that helps!