Hacking AMFPHP into submission: Caching

Posted on Tuesday 6 September 2005

AMFPHP is made up of modules which can be happily modified by any user armed with patience and a good knowledge of PHP. Technical docs are currently sorely lacking (I’m working on it) but hopefully this post should help some people get a better understanding of how this is done. So here’s a concrete example of hacking AMFPHP: service-less caching.

This was discussed recently on the AMFPHP list and I actually had to do a very similar thing in a recent project. Basically, what is required is that instead of calling a service every time, depending on the method and arguments, a modified version of AMFPHP would fetch the results pre-cached from a MySQL database. In the case of our project, the reason was that the remote server was using openAMF and we were in a kiosk situation, so that a local database would store canned responses instead of calling the dog-slow remote server over a shaky internet connection. This eliminates lag completely.

One way to do so (and AMFPHP amateurs usually use this route) is to create a dummy service where every method points to as centralized function that does the MySQL caching thing. However, if remote methods are added, then we need to add to the service some new methods which can easily become a maintenance nightmare. Also in our case we needed to emulate a paging mechanism from a standard array and well, that just wasn’t going to happen with traditional methods.

What you need to know is that AMFPHP sets up two work chains which AMF messages run through. AMF messages can contain submessages (batch calls). Hence we need a chain that processes the envelope and one that works with submessages. This is in AMFPHP jargon “filters” and “actions”. The standard filter chain is:

Deserialization ->Headers -> DescribeService -> Authentication -> BatchProcess -> Debug ->Serialization

Deserialization deserializes a binary AMF message. Headers looks at the headers and makes these into a static variable available from Headers::getHeader($key). DescribeService intercepts a service description header that triggers it to give back meta information about a certain service. Authenticate looks for an authentication header and creates a new submessage triggering our friend _authenticate. BatchProcess starts the action chain for each submessage. Debug adds debug information to the outgoing message such as NetDebug::trace calls, and various headers that are NetConnection debugger specific. Finally Serialization serializes the final message back to Flash.

As for (per-submessage) actions, they are:

Adapter -> WebService -> ClassLoader -> MetaData -> Security -> Mapper -> Execution

Adapter looks at the message and transforms the service names into PHP paths, looks for http:// service names to initiate a web service call, looks for pageable recordset page calls to transform the dummy arguments into stored arguments. WebService does SOAP calls. ClassLoader attempts to load a service in the service folder and instantiate the class. Metadata checks that a method exists in the method table and does aliases. Security checks that a service is remote, it has the right instance name, has right credentials, etc. Mapper looks at methodTable argument types and attempts to load an appropriate class from service folder if it finds a match. Execution finally calls the method that needs to be called and returns the results.

These two chains are defined in app/Gateway.php in the registerActionChain and registerFilterChain methods. Can you guess what needs to be done then? That’s right, extend app/Gateway.php and override the registerActionChain or registerFilterChain action depending on what needs to be done. Then a new gateway.php file needs to be created to instantiate the custom gateway and that’s pretty much all there is to it.

In our project’s case we modified the action chain so that it would be Adapter -> CachedExecution. CachedExecution takes the service name, method name, and arguments, places them in an associative array, then serializes, then md5 that to get a unique key which we can query against a database. If the entry exists, the results are deserialized and the array is chopped to fit with paging requirements, and if not an error is thrown. (I’d show the code but it’s under NDA, sorry).

That’s one way to enable caching in AMFPHP. Another would be to catch outgoing messages and then store these as binary AMF in MySQL. This would require adding another filter after Serialization to store the results, and a modified Execution action to accomodate the possibility of canned binary messages. Tons of fun for the whole family!

Another way to extend AMFPHP is to create custom adapters for recordsets, but that’s another story and another blog entry… Get the latest AMFPHP build here.


WordPress database error: [Can't open file: 'wp_comments.MYD'. (errno: 145)]
SELECT * FROM wp_comments WHERE comment_post_ID = '143' AND comment_approved = '1' ORDER BY comment_date

No comments have been added to this post yet.

Leave a comment




Your e-mail address is never displayed. If you run into issues with SpamKarma blocking you, email me at $patrick->5etdemi(com)


RSS feed for comments on this post | TrackBack URI