pixel-ping.coffee | |
|---|---|
| Require Node.js core modules. | fs = require 'fs'
url = require 'url'
http = require 'http'
querystring = require 'querystring' |
The Pixel Ping server | |
| Keep the version number in sync with | VERSION = '0.1.2' |
| The in-memory hit | store = {} |
| Record a single incoming hit from the remote pixel. | record = (params) ->
return unless key = params.query?.key
store[key] or= 0
store[key] += 1 |
| Serializes the current | serialize = ->
data = json: JSON.stringify(store)
store = {}
data.secret = config.secret if config.secret
querystring.stringify data |
| Flushes the | flush = ->
log store
return unless config.endpoint
data = serialize()
endHeaders['Content-Length'] = data.length
request = endpoint.request 'POST', endParams.pathname, endHeaders
request.write data
request.end()
request.on 'response', (response) ->
console.info '--- flushed ---' |
| Log the contents of the | log = (hash) ->
for key, hits of hash
console.info "#{hits}:\t#{key}" |
| Create a | server = http.createServer (req, res) ->
params = url.parse req.url, true
if params.pathname is '/pixel.gif'
res.writeHead 200, pixelHeaders
res.end pixel
record params
else
res.writeHead 404, emptyHeaders
res.end ''
null |
Configuration | |
| Load the configuration and the contents of the tracking pixel. Handle requests for the version number, and usage information. | configPath = process.argv[2]
if configPath in ['-v', '-version', '--version']
console.log "Pixel Ping version #{VERSION}"
process.exit 0
if not configPath or (configPath in ['-h', '-help', '--help'])
console.error "Usage: pixel-ping path/to/config.json"
process.exit 0
config = JSON.parse fs.readFileSync(configPath).toString()
pixel = fs.readFileSync __dirname + '/pixel.gif' |
| HTTP headers for the pixel image. | pixelHeaders =
'Cache-Control': 'private, no-cache, proxy-revalidate'
'Content-Type': 'image/gif'
'Content-Disposition': 'inline'
'Content-Length': pixel.length |
| HTTP headers for the 404 response. | emptyHeaders =
'Content-Type': 'text/html'
'Content-Length': '0' |
| If an | if config.endpoint
console.info "Flushing hits to #{config.endpoint}"
endParams = url.parse config.endpoint
endpoint = http.createClient endParams.port or 80, endParams.hostname
endHeaders =
'host': endParams.host
'Content-Type': 'application/x-www-form-urlencoded'
else
console.warn "No endpoint set. Hits won't be flushed, add \"endpoint\" to #{configPath}." |
| Sending | process.on 'SIGUSR1', ->
console.log 'Got SIGUSR1. Forcing a flush:'
flush() |
| Don't let exceptions kill the server. | process.on 'uncaughtException', (err) ->
console.error "Uncaught Exception: #{err}" |
Startup | |
| Start the server listening for pixel hits, and begin the periodic data flush. | server.listen config.port, config.host
setInterval flush, config.interval * 1000
|