Feral
Express + Zombie caching content server.
Store static HTML snapshots of JavaScript applications, conditionally serving those static snapshots to clients that are not capable of running the application.
Installation
npm install feral
feral.create
Create a new Feral + Express server. Create takes a single object with the following keys as it's only argument:
- port - Number
- public - String, path to serve static files from
- cacheDir - String, path to write
- targets - Object, name: callback pairs. callback will receive an http.ServerRequest object and must return boolean.
- cache - Object, name: Object option pairs. options can contain:
- url - String, route. May be relative or a fully qualified URL.
- targets - Object, name: true || callback pairs. If specifying a callback it will receive a zombie.Browser instance and a continuation callback. The continuation callback must be called.
- callback - Function. Will receive a zombie.Browser instance and a continuation callback. The continuation callback must be called.
- debug - Boolean
- runScripts - Boolean
- userAgent - String
The following is used to serve functionsource.com:
feral = require 'feral'
server = feral.create
port: 3000
public: __dirname + '/public/'
cacheDir: __dirname + '/cache/'
targets: [
{
mobile: (request) ->
request.agent().name in ['ios','android']
}
{
html5: (request) ->
request.agent().historyAvailable
}
{
legacy: (request) ->
not request.agent().historyAvailable
}
]
cache:
index:
url: '/:page?'
targets:
mobile: true
legacy: (browser,next) ->
# do something only when taking a snapshot for a legacy target
next()
callback: (browser,next) ->
# do something when taking a snapshot for any target
next()
tag:
url: '/tags/:tag/:page?'
targets:
legacy: true
post:
url: '/post/:slug'
targets:
legacy: true
To make an existing Express server Feral, pass it as the first argument (you must still pass the port number in the options object):
server = express.createServer()
# server setup logic here
feral.create server, options
Targets & Cache Middleware
Feral installs a middleware method which is similar to express.static, but will conditionally serve files based on the target. The following files would be served when a request for "/" is initiated:
mobile: /cache/mobile/index.html
legacy: /cache/legacy/index.html
html5: (no match, let other middleware respond)
Like middleware, order matters when defining targets and the first target callback that returns true will determine which file will be served. In our example above a browser may be both HTML5 capable and a mobile device, but mobile was specified first, so the request will be treated as a "mobile" target.
HTTP API
For each cache name specified Feral exposes a corresponding HTTP GET method. Visiting this URL will trigger Feral to send a Zombie to cache the page for each target. Given the example shown in feral.create visiting:
http://localhost:3000/feral/post?slug=slug_name
Will trigger Feral to send a Zombie to:
http://localhost:3000/post/slug_name
And will cache the result at:
/cache/legacy/post/slug_name
server.cache
A wrapper for the HTTP API
server.cache post: slug: 'slug_name', -> # on complete callback
Can also be used in cache definition callbacks to have one cache trigger another (i.e. when caching a blog post also cache the front page of the blog).
Cache Sweeping
Like Rails, sweeping the cache is an application specific task that is up to you to implement. Two convenience methods are provided to assist.
server.urlForCache (cache_name,params) - Returns the url that would be visited by a Zombie.
server.urlForCache("post",slug: "post_name") == "/post/post_name"
server.pathForCache (cache_name,target_name,params) - Returns the path in the filesystem where the cache would be stored.
server.pathForCache("post","mobile",slug: "post_name") == "/cache/mobile/post/post_name"
Request
Feral adds an agent method to every http.ServerRequest request object which is useful in creating targets. The agent method returns an object with the following attributes:
- name - String, name of the user agent, will be one of the following:
- chrome
- firefox
- safari
- ios
- android
- opera
- ie
- bing
- google
- ask
- yahoo
- unknown
- version - String, user agent version.
- historyAvailable - Boolean, does the agent support the HTML5 history API?
- robot - Boolean, is the agent a robot?
- targets - Object, containing target_name: boolean pairs for each of the specified targets.
Browser Object
Every browser object in Feral is a zombie.Browser object with the following additional methods:
- addScript (src) - Add a script tag to the browser.window's head.
- removeScript (src = false) - Remove a script tag from the browser.window's head. Not specifying a src will remove all.
Single File Targets
Serving a single file to a given target (i.e. an HTML page that will load a JS application) for all requests can be accomplished with an Express middleware method. The following code is used in conjunction with Feral on functionsource.com:
for path in [
"/:page?"
"/tags/:tag/:page?"
"/post/:slug"
]
server.get path, (request,response,next) ->
return next() if not request.agent().targets.html5
response.sendfile __dirname + '/cache/html5/index.html'
Protecting Feral
By default the Feral HTTP API is publicly accessible. It can be protected with an Express middleware method:
server = feral.create ...
server.get /^\/feral\/.*/, (request,response,next) ->
if not request.query.admin?
next new Error 'Not admin'
else
next()