Skip to content

Instantly share code, notes, and snippets.

@johnthethird
Created September 1, 2010 03:38
Show Gist options
  • Save johnthethird/560183 to your computer and use it in GitHub Desktop.
Save johnthethird/560183 to your computer and use it in GitHub Desktop.
HTTPClientWithCache for Titanium in CoffeeScript
###
------> HTTPClientWithCache <------
This class is a wrapper around the standard Titanium.Network.HTTPClient(), but it adds a
few nice features:
* A cache backed by a SQLite database. All HTTPClientWithCache instances use the same database table, with
the primary cache key being a hash of the full URL (and any data parameters in a POST)
* The cache is automatically pruned before each query
* A retry mechanism, so that you can retry a particular query a number of times before failing.
* Automatically fires a Titanium event ("show_activity_indicator") when a query is started, and another one
when the query finishes ("hide_activity_indicator"), you can capture these events to show/hide an activity indicator
(These code examples are written in CoffeeScript, which compiles down into plain Javascript.)
First, we create a new instance of the class to connect to the CNN RSS feeds:
cnn = new HTTPClientWithCache({
baseURL: "http://rss.cnn.com/rss",
retryCount: 2,
cacheSeconds: 60,
onload: (response) ->
Ti.API.debug("Response Data: #{response.responseText}")
Ti.API.debug("Is this cached data?: #{response.cached}")
})
cnn.get({url: "/cnn_topstories.rss"})
Subsequent calls will return (via the onload callback) the cached data until the cacheSeconds time expires.
You can use the same object to make calls to other URLs as well, like so:
cnn.get({url: "/cnn_us.rss"})
If you need to pass parameters (like for pagination) you can say:
cnn.get({url: "/cnn_us.rss?page=1"})
To POST data to a URL, do this:
cnn.post({url: "/story/19912/edit", data: {param1: "value1", param2: "value2"}})
To manually prune the cache, you can call the prune_cache method, and anything older than seconds will be
deleted from the cache. For example, to remove anything older than 1 day (86,400 seconds) you would say this:
cnn.prune_cache(86400)
To completely clear the cache of every single entry, you can do this:
cnn.prune_cache(0)
###
# This class requires a sqlite database table, so make sure it exists
db = Titanium.Database.open('http_client_cache')
db.execute('CREATE TABLE IF NOT EXISTS REQUESTS (URL_HASH STRING, RESPONSE TEXT, UPDATED_AT INTEGER)')
db.close()
class HTTPClientWithCache
constructor: (opts) ->
@url_hash = ""
@currentRetryCount = 0
@options = {
method: "GET",
baseUrl: "",
timeout: 10000, #miliseconds
retryCount: 0,
cacheSeconds: 30,
pruneSeconds: 2520000,
showActivityEvent: "show_activity_indicator",
hideActivityEvent: "hide_activity_indicator"
}
@xhr = Titanium.Network.createHTTPClient()
self = this
@xhr.onload = () -> self.onload_hook(self, {responseText: this.responseText, cached:false, status: this.status})
@xhr.onerror = () -> self.onerror_hook(self)
@set_options(opts)
@prune_cache()
set_options: (opts) ->
for attrname of opts
@options[attrname] = opts[attrname]
@_compute_url_hash()
get: (opts) ->
@set_options(opts)
@options.method = "GET"
@send()
post: (opts) ->
@set_options(opts)
@options.method = "POST"
@send()
send: (opts) ->
self = this
@set_options(opts)
return false if !@_validate()
@prune_cache()
if response = @_get_cached_response()
Ti.API.info("HTTPClientWithCache: Using cached response")
@onload_hook(this, response)
else
@_reset_xhr()
@xhr.setTimeout(@options.timeout)
Ti.App.fireEvent(@options.showActivityEvent)
@xhr.open(@options.method,@options.baseUrl + @options.url)
@xhr.send(@options.data if @options.data?)
onload_hook: (self, response) ->
if response.status >= 400
@onerror_hook(self)
return
Ti.App.fireEvent(@options.hideActivityEvent);
@_save_to_cache(self,response)
if @options.onload?
@options.onload(response)
else
Ti.API.error("HTTPClientWithCache: Please specify an onload callback!")
onerror_hook: (self) ->
if (@currentRetryCount++) <= @options.retryCount
Ti.API.info("HTTPClientWithCache: Retry Count #{@currentRetryCount} of #{@options.retryCount}")
@xhr.abort()
@xhr.open(@options.method,@options.baseUrl + @options.url)
@xhr.send(@options.data if @options.data?)
return
Ti.App.fireEvent(@options.hideActivityEvent);
if @options.onerror?
response = @_get_cached_response(9999999) #return any data we have on an error
@options.onerror(response)
else
Ti.API.info("You might want to specify an onerror callback.")
prune_cache: (seconds) ->
seconds ?= @options.pruneSeconds
db = Titanium.Database.open('http_client_cache')
db.execute("DELETE FROM REQUESTS WHERE UPDATED_AT < DATETIME('now','-#{seconds} seconds')")
db.close()
# PRIVATE
_validate: () ->
if @options.url?
true
else
Ti.API.error("HTTPClientWithCache: Invalid options #{JSON.stringify(@options)}")
false
_compute_url_hash: () ->
@url_hash = Ti.Utils.md5HexDigest(@options.method + @options.baseUrl + @options.url + @options.data)
_save_to_cache: (self, response) ->
return if response.status >= 400 || response.cached
db = Titanium.Database.open('http_client_cache')
urlHash = self._compute_url_hash()
if self._exists_in_cache()
db.execute("UPDATE REQUESTS SET RESPONSE=?, UPDATED_AT=CURRENT_TIMESTAMP WHERE URL_HASH=?", response.responseText, urlHash)
else
db.execute("INSERT INTO REQUESTS (RESPONSE, URL_HASH, UPDATED_AT) VALUES (?,?,CURRENT_TIMESTAMP)", response.responseText, urlHash)
db.close()
_get_cached_response: (seconds) ->
db = Titanium.Database.open('http_client_cache')
seconds ?= @options.cacheSeconds
row = db.execute("SELECT RESPONSE, UPDATED_AT FROM REQUESTS WHERE URL_HASH=? AND UPDATED_AT > DATETIME('now','-#{seconds} seconds')", @url_hash)
responseText = row.field(0)
cachedAt = row.field(1)
row.close()
db.close()
{responseText: responseText, cached: true, cached_at: cachedAt, status: 200} if responseText?
_exists_in_cache: () ->
row = db.execute("SELECT COUNT(*) FROM REQUESTS WHERE URL_HASH=?", @url_hash)
count = row.field(0)
row.close()
count > 0 ? true : false
_reset_xhr: () ->
@currentRetryCount = 0
@xhr.abort()
root.HTTPClientWithCache = HTTPClientWithCache
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment