diff --git a/README.md b/README.md
index f9ae868..d6e1a91 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ This module is used by [WebTorrent](http://webtorrent.io).
- Robust and well-tested
- Comprehensive test suite (runs entirely offline, so it's reliable)
- Used by popular clients: [WebTorrent](http://webtorrent.io), [peerflix](https://github.com/mafintosh/peerflix), and [playback](https://mafintosh.github.io/playback/)
-- Tracker statistics available via web interface at `/stats`
+- Tracker statistics available via web interface at `/stats` or JSON data at `/stats.json`
Also see [bittorrent-dht](https://github.com/feross/bittorrent-dht).
diff --git a/server.js b/server.js
index 34778da..62ca7be 100644
--- a/server.js
+++ b/server.js
@@ -150,7 +150,7 @@ function Server (opts) {
return count
}
- if (req.method === 'GET' && req.url === '/stats') {
+ if (req.method === 'GET' && (req.url === '/stats' || req.url === '/stats.json')) {
infoHashes.forEach(function (infoHash) {
var peers = self.torrents[infoHash].peers
var keys = Object.keys(peers)
@@ -185,13 +185,29 @@ function Server (opts) {
var isIPv4 = function (peer) { return peer.ipv4 }
var isIPv6 = function (peer) { return peer.ipv6 }
- res.end('
' + infoHashes.length + ' torrents (' + activeTorrents + ' active)
\n' +
- 'Connected Peers: ' + Object.keys(allPeers).length + '
\n' +
- 'Peers Seeding Only: ' + countPeers(isSeederOnly) + '
\n' +
- 'Peers Leeching Only: ' + countPeers(isLeecherOnly) + '
\n' +
- 'Peers Seeding & Leeching: ' + countPeers(isSeederAndLeecher) + '
\n' +
- 'IPv4 Peers: ' + countPeers(isIPv4) + '
\n' +
- 'IPv6 Peers: ' + countPeers(isIPv6) + '
\n')
+ var stats = {
+ torrents: infoHashes.length,
+ activeTorrents: activeTorrents,
+ peersAll: Object.keys(allPeers).length,
+ peersSeederOnly: countPeers(isSeederOnly),
+ peersLeecherOnly: countPeers(isLeecherOnly),
+ peersSeederAndLeecher: countPeers(isSeederAndLeecher),
+ peersIPv4: countPeers(isIPv4),
+ peersIPv6: countPeers(isIPv6)
+ }
+
+ if (req.url === '/stats.json' || req.headers['content-type'] === 'application/json') {
+ res.write(JSON.stringify(stats))
+ res.end()
+ } else if (req.url === '/stats') {
+ res.end('' + stats.torrents + ' torrents (' + stats.activeTorrents + ' active)
\n' +
+ 'Connected Peers: ' + stats.peersAll + '
\n' +
+ 'Peers Seeding Only: ' + stats.peersSeederOnly + '
\n' +
+ 'Peers Leeching Only: ' + stats.peersLeecherOnly + '
\n' +
+ 'Peers Seeding & Leeching: ' + stats.peersSeederAndLeecher + '
\n' +
+ 'IPv4 Peers: ' + stats.peersIPv4 + '
\n' +
+ 'IPv6 Peers: ' + stats.peersIPv6 + '
\n')
+ }
}
})
}
diff --git a/test/stats.js b/test/stats.js
new file mode 100644
index 0000000..8971cd6
--- /dev/null
+++ b/test/stats.js
@@ -0,0 +1,152 @@
+var Buffer = require('safe-buffer').Buffer
+var Client = require('../')
+var commonTest = require('./common')
+var fixtures = require('webtorrent-fixtures')
+var get = require('simple-get')
+var test = require('tape')
+
+var peerId = Buffer.from('01234567890123456789')
+
+function parseHtml (html) {
+ var extractValue = new RegExp('[^v^h](\\d+)')
+ var array = html.replace('torrents', '\n').split('\n').filter(function (line) {
+ return line && line.trim().length > 0
+ }).map(function (line) {
+ var a = extractValue.exec(line)
+ return parseInt(a[1])
+ })
+ var i = 0
+ return {
+ torrents: array[i++],
+ activeTorrents: array[i++],
+ peersAll: array[i++],
+ peersSeederOnly: array[i++],
+ peersLeecherOnly: array[i++],
+ peersSeederAndLeecher: array[i++],
+ peersIPv4: array[i++],
+ peersIPv6: array[i]
+ }
+}
+
+test('server: get empty stats', function (t) {
+ t.plan(11)
+
+ commonTest.createServer(t, 'http', function (server, announceUrl) {
+ var url = announceUrl.replace('/announce', '/stats')
+
+ get.concat(url, function (err, res, data) {
+ t.error(err)
+
+ var stats = parseHtml(data.toString())
+ t.equal(res.statusCode, 200)
+ t.equal(stats.torrents, 0)
+ t.equal(stats.activeTorrents, 0)
+ t.equal(stats.peersAll, 0)
+ t.equal(stats.peersSeederOnly, 0)
+ t.equal(stats.peersLeecherOnly, 0)
+ t.equal(stats.peersSeederAndLeecher, 0)
+ t.equal(stats.peersIPv4, 0)
+ t.equal(stats.peersIPv6, 0)
+
+ server.close(function () { t.pass('server closed') })
+ })
+ })
+})
+
+test('server: get empty stats with json header', function (t) {
+ t.plan(11)
+
+ commonTest.createServer(t, 'http', function (server, announceUrl) {
+ var opts = {
+ url: announceUrl.replace('/announce', '/stats'),
+ headers: {
+ 'content-type': 'json'
+ },
+ json: true
+ }
+
+ get.concat(opts, function (err, res, stats) {
+ t.error(err)
+
+ t.equal(res.statusCode, 200)
+ t.equal(stats.torrents, 0)
+ t.equal(stats.activeTorrents, 0)
+ t.equal(stats.peersAll, 0)
+ t.equal(stats.peersSeederOnly, 0)
+ t.equal(stats.peersLeecherOnly, 0)
+ t.equal(stats.peersSeederAndLeecher, 0)
+ t.equal(stats.peersIPv4, 0)
+ t.equal(stats.peersIPv6, 0)
+
+ server.close(function () { t.pass('server closed') })
+ })
+ })
+})
+
+test('server: get empty stats on stats.json', function (t) {
+ t.plan(11)
+
+ commonTest.createServer(t, 'http', function (server, announceUrl) {
+ var opts = {
+ url: announceUrl.replace('/announce', '/stats.json'),
+ json: true
+ }
+
+ get.concat(opts, function (err, res, stats) {
+ t.error(err)
+
+ t.equal(res.statusCode, 200)
+ t.equal(stats.torrents, 0)
+ t.equal(stats.activeTorrents, 0)
+ t.equal(stats.peersAll, 0)
+ t.equal(stats.peersSeederOnly, 0)
+ t.equal(stats.peersLeecherOnly, 0)
+ t.equal(stats.peersSeederAndLeecher, 0)
+ t.equal(stats.peersIPv4, 0)
+ t.equal(stats.peersIPv6, 0)
+
+ server.close(function () { t.pass('server closed') })
+ })
+ })
+})
+
+test('server: get leecher stats.json', function (t) {
+ t.plan(10)
+
+ commonTest.createServer(t, 'http', function (server, announceUrl) {
+ // announce a torrent to the tracker
+ var client = new Client({
+ infoHash: fixtures.leaves.parsedTorrent.infoHash,
+ announce: announceUrl,
+ peerId: peerId,
+ port: 6881
+ })
+ client.on('error', function (err) { t.error(err) })
+ client.on('warning', function (err) { t.error(err) })
+
+ client.start()
+
+ server.once('start', function () {
+ var opts = {
+ url: announceUrl.replace('/announce', '/stats.json'),
+ json: true
+ }
+
+ get.concat(opts, function (err, res, stats) {
+ t.error(err)
+ console.log(stats)
+
+ t.equal(res.statusCode, 200)
+ t.equal(stats.torrents, 1)
+ t.equal(stats.activeTorrents, 1)
+ t.equal(stats.peersAll, 1)
+ t.equal(stats.peersSeederOnly, 0)
+ t.equal(stats.peersLeecherOnly, 1)
+ t.equal(stats.peersSeederAndLeecher, 0)
+
+ client.destroy(function () { t.pass('client destroyed') })
+ server.close(function () { t.pass('server closed') })
+ })
+ })
+ })
+})