| var url = require('url') |
| var util = require('util') |
| |
| var Promise = require('bluebird') |
| var dns = Promise.promisifyAll(require('dns')) |
| |
| var srv = module.exports = Object.create(null) |
| |
| function groupByPriority(records) { |
| function sortByPriority(a, b) { |
| return a.priority - b.priority |
| } |
| |
| return records.sort(sortByPriority).reduce(function(acc, record) { |
| if (acc.length) { |
| var last = acc[acc.length - 1] |
| if (last[0].priority !== record.priority) { |
| acc.push([record]) |
| } |
| else { |
| last.push(record) |
| } |
| } |
| else { |
| acc.push([record]) |
| } |
| return acc |
| }, []) |
| } |
| |
| function shuffleWeighted(records) { |
| function sortByWeight(a, b) { |
| return b.weight - a.weight |
| } |
| |
| function totalWeight(records) { |
| return records.reduce(function(sum, record) { |
| return sum + record.weight |
| }, 0) |
| } |
| |
| function pick(records, sum) { |
| var rand = Math.random() * sum |
| var counter = 0 |
| |
| for (var i = 0, l = records.length; i < l; ++i) { |
| counter += records[i].weight |
| if (rand < counter) { |
| var picked = records.splice(i, 1) |
| return picked.concat(pick(records, sum - picked[0].weight)) |
| } |
| } |
| |
| return [] |
| } |
| |
| return pick(records.sort(sortByWeight), totalWeight(records)) |
| } |
| |
| function flatten(groupedRecords) { |
| return groupedRecords.reduce(function(acc, group) { |
| return acc.concat(group) |
| }, []) |
| } |
| |
| function NEXT() { |
| Error.call(this) |
| this.name = 'NEXT' |
| Error.captureStackTrace(this, NEXT) |
| } |
| |
| util.inherits(NEXT, Error) |
| |
| srv.NEXT = NEXT |
| |
| srv.sort = function(records) { |
| return flatten(groupByPriority(records).map(shuffleWeighted)) |
| } |
| |
| srv.resolve = function(domain) { |
| var parsedUrl = url.parse(domain) |
| |
| if (!parsedUrl.protocol) { |
| return Promise.reject(new Error( |
| 'Must include protocol in "%s"' |
| , domain |
| )) |
| } |
| |
| if (/^srv\+/.test(parsedUrl.protocol)) { |
| parsedUrl.protocol = parsedUrl.protocol.substr(4) |
| return dns.resolveSrvAsync(parsedUrl.hostname) |
| .then(module.exports.sort) |
| .then(function(records) { |
| return records.map(function(record) { |
| parsedUrl.host = util.format('%s:%d', record.name, record.port) |
| parsedUrl.hostname = record.name |
| parsedUrl.port = record.port |
| record.url = url.format(parsedUrl) |
| return record |
| }) |
| }) |
| } |
| else { |
| return Promise.resolve([{ |
| url: domain |
| , name: parsedUrl.hostname |
| , port: parsedUrl.port |
| }]) |
| } |
| } |
| |
| srv.attempt = function(records, fn) { |
| function next(i) { |
| if (i >= records.length) { |
| throw new Error('No more records left to try') |
| } |
| |
| return fn(records[i]).catch(srv.NEXT, function() { |
| return next(i + 1) |
| }) |
| } |
| |
| return next(0) |
| } |