Tag Archives: caching

nginx

I recently need to set up caching for a very slow API service I was working with.  However the app I was working with forced a query param of “timestamp” on all requests.  This effectively killed the cache because all api requests were flowing through a single endpoint, and only the query params differentiated the requests.  So here’s how to set up caching and strip out query params with nginx.

http {
    # Create a storage location for 
    # caching and call this location "my-app"
    proxy_cache_path /data/nginx/cache levels=1:2 
         keys_zone=global:10m 
         max_size=100m inactive=60m;
    
    server {
    
        location /api/  {
            # Strip out query param "timestamp"
            if ($args ~ (.*)&timestamp=[^&]*(.*)) {
                set $args $1$2;
            }

            # Use the "my-app" cache defined above
            proxy_cache my-app;

            # Only cache 200 responses and cache for 20mins
            proxy_cache_valid 200 20m;

            # Create a unique cache key
            proxy_cache_key 
                "$scheme$request_method$host$uri$is_args$args";
            
            # Proxy the request
            proxy_pass http://myapi.com;
        }
}

So I’ve had quite a few questions about how I do AJAX caching in Titanium Appcelerator. So, here’s my solution.

First, I needed some persistent storage with a very general structure. I could have gone with flat files, but I decided to make use of the built-in SQLite engine, so that I could easily access entries and delete old data.

Database

var db_conn = Ti.Database.open('app_db1');
db_conn.execute('CREATE TABLE IF NOT EXISTS `remote_cache`
                 (key TEXT, type TEXT, valid_till TEXT, value TEXT,
                 PRIMARY KEY (key, type))');

Next, I need a wrapper class for my ajax calls. Note the “get” function which is the main function call. Here is the signature and explaination:

get: function (url, params, type, valid_for, callback, skip_cache)

  • url : string : the full url to call
  • params : object : a simple object with name/value parse for POST variables
  • type : string : the cache domain for this call. Usefull for clearing all cache data for a specific domain
  • valid_for : date string : the amount of time the cache should remain valid for
  • callback : function : the function to call on completion or fail of the request
  • skip_cache : boolean : an option to bypass the cache and perform write through

AJAX Class


var ajax =
{
	http_conn: Ti.Network.createHTTPClient(),
	url:'',
	type:'',
	params:{},
	key:'',
	valid_for: '+1 hour',
	callback: null,
	debug:true,
	get: function (url, params, type, valid_for, callback, skip_cache)
	{
		this.key = MD5(url + '|' +   JSON.stringify(params));
		this.url = url;
		this.params = params;
		this.valid_for = valid_for;
		this.type = type;

		this.callback = callback;

		if (ajax.debug) Titanium.API.info('Ajax Call');
		if (skip_cache)
		{
			this.remote_grab();
		}
		else if (!this.local_grab())
		{
			this.remote_grab();
		}

	},
	abort : function ()
	{
		this.http_conn.abort();
	},

	local_grab: function ()
	{
		if (ajax.debug) Titanium.API.info('Checking Local key:' + this.key);

		var rows = db_conn.execute('SELECT valid_till, value FROM remote_cache WHERE key = ? AND type = ? AND valid_till > datetime(\'now\')',  this.key, this.type);//, '', '+this.valid_for+')');//',  this.key, 'date(\'now\', '+this.valid_for+')');
		var result = false;
		if (rows.isValidRow())
		{
			if (ajax.debug) Titanium.API.info('Local cache found with expiration:' + rows.fieldByName('valid_till'));
			var result = JSON.parse(rows.fieldByName('value'));
			rows.close();

			this.callback(result);
			result = true;
		}
		rows.close();
		return result;
	},

	remote_grab: function ()
	{
		if (ajax.debug) Titanium.API.info('Calling Remote: ' + this.url + ' - PARAMS: '+ JSON.stringify(this.params));

		this.abort();
		this.http_conn.setTimeout(10000);

		// catch errors
		this.http_conn.onerror = function(e)
		{
			//alert('Call failed');
			ajax.callback({result:false});
			Ti.API.info(e);
		};

		// open connection
		this.http_conn.open('POST', this.url);

		// act on response
		var key = this.key;
		var valid_for = this.valid_for;
		var callback = this.callback;
		var type = this.type;

		this.http_conn.onload = function()
		{
			if (ajax.debug) Titanium.API.info('Response from server:' + this.responseText);
			if (this.responseText != 'null')
			{
				var response = JSON.parse(this.responseText);
				if (response.result == true || (response && response.length > 0))
				{
					ajax.update_local(key, type, valid_for, response);
					callback(response);
				}
				else if (response.result == false && response.error  && response.error.length > 0)
				{
					callback({result:false,error:response.error});
				}
				else
				{
					callback({result:false,error:'Invalid Result (1)'});
				}
			}
			else
			{
				callback({result:false,error:'Invalid Result (2)'});
			}
		};

		// Send the HTTP request
		this.http_conn.send(this.params);

	},

	update_local: function (key, type, valid_for, response)
	{
		if (ajax.debug) Ti.API.info('Updating Cache: KEY: ' + key + ' TYPE: ' + type +' - FOR: '+valid_for+', ' + JSON.stringify(response));
		db_conn.execute('DELETE FROM remote_cache WHERE (valid_till <= datetime(\'now\') OR key = ?) AND type = ?', key, type);
		db_conn.execute('INSERT INTO remote_cache ( key, type, valid_till, value ) VALUES(?,?,datetime(\'now\',?),?)',key, type, valid_for, JSON.stringify(response));
	}
};