Skip to content

Commit 82e6792

Browse files
committed
BREAKING CHANGES
Breaking changes: - 'listening' event no longer emits with `port` param - `server.port` property removed (instead, use `server.http.address().port`) Added features: - expose http server as `server.http` - expose udp server as `server.udp` - client.destroy() - ungracefully leave the swarm - server: added `filter` option to black/whitelist torrents Bugfixes: - client considers udp tracker errors to be warnings - emit 'start', 'stop', 'update', etc. AFTER response sent - fix udp error response action and message being `undefined` Internal: - remove `portfinder` dep - add complete test for `filter` functionality
1 parent 098ec70 commit 82e6792

File tree

12 files changed

+233
-201
lines changed

12 files changed

+233
-201
lines changed

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ client.update()
8787
// stop getting peers from the tracker, gracefully leave the swarm
8888
client.stop()
8989

90+
// ungracefully leave the swarm (without sending final 'stop' message)
91+
client.destroy()
92+
9093
// scrape
9194
client.scrape()
9295

@@ -108,13 +111,17 @@ var Server = require('bittorrent-tracker').Server
108111
var server = new Server({
109112
udp: true, // enable udp server? [default=true]
110113
http: true, // enable http server? [default=true]
111-
filter: function (hash) {
112-
// specify white/blacklist for disallowing/allowing torrents
113-
return hash !== 'aaa67059ed6bd08362da625b3ae77f6f4a075aaa'
114+
filter: function (infoHash) {
115+
// black/whitelist for disallowing/allowing torrents [default=allow all]
116+
// this example only allows this one torrent
117+
return infoHash === 'aaa67059ed6bd08362da625b3ae77f6f4a075aaa'
114118
})
115-
116119
})
117120

121+
// Internal http and udp servers exposed as public properties.
122+
server.http
123+
server.udp
124+
118125
server.on('error', function (err) {
119126
// fatal server error!
120127
console.log(err.message)
@@ -125,11 +132,13 @@ server.on('warning', function (err) {
125132
console.log(err.message)
126133
})
127134

128-
server.on('listening', function (port) {
129-
console.log('tracker server is now listening on ' + port)
135+
server.on('listening', function () {
136+
// fired when all requested servers are listening
137+
console.log('listening on http port:' + server.http.address().port)
138+
console.log('listening on udp port:' + server.udp.address().port)
130139
})
131140

132-
// start tracker server listening!
141+
// start tracker server listening! Use 0 to listen on a random free port.
133142
server.listen(port)
134143

135144
// listen for individual tracker messages from peers:

client.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ Client.prototype.setInterval = function (intervalMs) {
128128
})
129129
}
130130

131+
Client.prototype.destroy = function () {
132+
var self = this
133+
self._trackers.forEach(function (tracker) {
134+
tracker.destroy()
135+
})
136+
}
137+
131138
inherits(Tracker, EventEmitter)
132139

133140
/**
@@ -175,7 +182,7 @@ Tracker.prototype.stop = function (opts) {
175182

176183
debug('sent `stop` %s', self._announceUrl)
177184
self._announce(opts)
178-
self.setInterval(0) // stop announcing on intervals
185+
self.destroy()
179186
}
180187

181188
Tracker.prototype.complete = function (opts) {
@@ -196,6 +203,12 @@ Tracker.prototype.update = function (opts) {
196203
self._announce(opts)
197204
}
198205

206+
Tracker.prototype.destroy = function () {
207+
var self = this
208+
debug('destroy', self._announceUrl)
209+
self.setInterval(0) // stop announcing on intervals
210+
}
211+
199212
/**
200213
* Send an announce request to the tracker.
201214
* @param {Object} opts
@@ -372,7 +385,7 @@ Tracker.prototype._requestUdp = function (requestUrl, opts) {
372385
if (msg.length < 8) {
373386
return error('invalid error message')
374387
}
375-
self.client.emit('error', new Error(msg.slice(8).toString()))
388+
self.client.emit('warning', new Error(msg.slice(8).toString()))
376389
break
377390

378391
default:

lib/common.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ exports.EVENT_IDS = {
1919
2: 'started',
2020
3: 'stopped'
2121
}
22+
exports.EVENT_NAMES = {
23+
update: 'update',
24+
completed: 'complete',
25+
started: 'start',
26+
stopped: 'stop'
27+
}
2228

2329
function toUInt32 (n) {
2430
var buf = new Buffer(4)

lib/swarm.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ function Swarm (infoHash, server) {
88
this.peers = {}
99
this.complete = 0
1010
this.incomplete = 0
11-
this.emit = server.emit.bind(server)
1211
}
1312

1413
Swarm.prototype.announce = function (params, cb) {
1514
var self = this
1615
var peer = self.peers[params.addr]
1716

1817
// Dispatch announce event
19-
if (!params.event || params.event === 'empty') params.event = 'update'
2018
var fn = '_onAnnounce_' + params.event
2119
if (self[fn]) {
2220
self[fn](params, peer) // process event
@@ -46,7 +44,6 @@ Swarm.prototype._onAnnounce_started = function (params, peer) {
4644
port: params.port,
4745
peerId: params.peer_id
4846
}
49-
this.emit('start', params.addr)
5047
}
5148

5249
Swarm.prototype._onAnnounce_stopped = function (params, peer) {
@@ -58,7 +55,6 @@ Swarm.prototype._onAnnounce_stopped = function (params, peer) {
5855
if (peer.complete) this.complete -= 1
5956
else this.incomplete -= 1
6057
this.peers[params.addr] = null
61-
this.emit('stop', params.addr)
6258
}
6359

6460
Swarm.prototype._onAnnounce_completed = function (params, peer) {
@@ -74,15 +70,13 @@ Swarm.prototype._onAnnounce_completed = function (params, peer) {
7470
this.complete += 1
7571
this.incomplete -= 1
7672
peer.complete = true
77-
this.emit('complete', params.addr)
7873
}
7974

8075
Swarm.prototype._onAnnounce_update = function (params, peer) {
8176
if (!peer) {
8277
debug('unexpected `update` event from peer that is not in swarm')
8378
return this._onAnnounce_started(params, peer) // treat as a start
8479
}
85-
this.emit('update', params.addr)
8680
}
8781

8882
Swarm.prototype._getPeers = function (numwant) {

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"inherits": "^2.0.1",
2525
"ip": "^0.3.0",
2626
"once": "^1.3.0",
27-
"portfinder": "^0.3.0",
2827
"run-series": "^1.0.2",
2928
"simple-get": "^1.3.0",
3029
"string2compact": "^1.1.1"

server.js

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ var dgram = require('dgram')
66
var EventEmitter = require('events').EventEmitter
77
var http = require('http')
88
var inherits = require('inherits')
9-
var portfinder = require('portfinder')
109
var series = require('run-series')
1110
var string2compact = require('string2compact')
1211

@@ -15,9 +14,6 @@ var Swarm = require('./lib/swarm')
1514
var parseHttpRequest = require('./lib/parse_http')
1615
var parseUdpRequest = require('./lib/parse_udp')
1716

18-
// Use random port above 1024
19-
portfinder.basePort = Math.floor(Math.random() * 60000) + 1025
20-
2117
inherits(Server, EventEmitter)
2218

2319
/**
@@ -40,40 +36,41 @@ function Server (opts) {
4036
EventEmitter.call(self)
4137
opts = opts || {}
4238

39+
if (opts.http === false && opts.udp === false)
40+
throw new Error('must start at least one type of server (http or udp)')
41+
4342
self._intervalMs = opts.interval
4443
? opts.interval
4544
: 10 * 60 * 1000 // 10 min
4645

4746
self._trustProxy = !!opts.trustProxy
47+
if (typeof opts.filter === 'function') self._filter = opts.filter
4848

4949
self.listening = false
50-
self.port = null
5150
self.torrents = {}
5251

5352
// default to starting an http server unless the user explictly says no
5453
if (opts.http !== false) {
55-
self._httpServer = http.createServer()
56-
self._httpServer.on('request', self.onHttpRequest.bind(self))
57-
self._httpServer.on('error', self._onError.bind(self))
58-
self._httpServer.on('listening', onListening)
54+
self.http = http.createServer()
55+
self.http.on('request', self.onHttpRequest.bind(self))
56+
self.http.on('error', self._onError.bind(self))
57+
self.http.on('listening', onListening)
5958
}
6059

6160
// default to starting a udp server unless the user explicitly says no
6261
if (opts.udp !== false) {
63-
self._udpSocket = dgram.createSocket('udp4')
64-
self._udpSocket.on('message', self.onUdpRequest.bind(self))
65-
self._udpSocket.on('error', self._onError.bind(self))
66-
self._udpSocket.on('listening', onListening)
62+
self.udp = dgram.createSocket('udp4')
63+
self.udp.on('message', self.onUdpRequest.bind(self))
64+
self.udp.on('error', self._onError.bind(self))
65+
self.udp.on('listening', onListening)
6766
}
6867

69-
if (typeof opts.filter === 'function') self._filter = opts.filter
70-
71-
var num = !!self._httpServer + !!self._udpSocket
68+
var num = !!self.http + !!self.udp
7269
function onListening () {
7370
num -= 1
7471
if (num === 0) {
7572
self.listening = true
76-
self.emit('listening', self.port)
73+
self.emit('listening')
7774
}
7875
}
7976
}
@@ -92,28 +89,24 @@ Server.prototype.listen = function (port, onlistening) {
9289
if (self.listening) throw new Error('server already listening')
9390
if (onlistening) self.once('listening', onlistening)
9491

95-
function onPort (err, port) {
96-
if (err) return self.emit('error', err)
97-
self.port = port
98-
// ATTENTION:
99-
// binding to :: only receives IPv4 connections if the bindv6only
100-
// sysctl is set 0, which is the default on many operating systems.
101-
self._httpServer && self._httpServer.listen(port.http || port, '::')
102-
self._udpSocket && self._udpSocket.bind(port.udp || port)
103-
}
92+
if (!port) port = 0
10493

105-
if (port) onPort(null, port)
106-
else portfinder.getPort(onPort)
94+
// ATTENTION:
95+
// binding to :: only receives IPv4 connections if the bindv6only
96+
// sysctl is set 0, which is the default on many operating systems.
97+
self.http && self.http.listen(port.http || port, '::')
98+
self.udp && self.udp.bind(port.udp || port)
10799
}
108100

109101
Server.prototype.close = function (cb) {
110102
var self = this
103+
self.listening = false
111104
cb = cb || function () {}
112-
if (self._udpSocket) {
113-
self._udpSocket.close()
105+
if (self.udp) {
106+
self.udp.close()
114107
}
115-
if (self._httpServer) {
116-
self._httpServer.close(cb)
108+
if (self.http) {
109+
self.http.close(cb)
117110
} else {
118111
cb(null)
119112
}
@@ -122,7 +115,7 @@ Server.prototype.close = function (cb) {
122115
Server.prototype.getSwarm = function (infoHash) {
123116
var self = this
124117
if (Buffer.isBuffer(infoHash)) infoHash = infoHash.toString('hex')
125-
if (self._filter && self._filter(infoHash)) return null
118+
if (self._filter && !self._filter(infoHash)) return null
126119
var swarm = self.torrents[infoHash]
127120
if (!swarm) swarm = self.torrents[infoHash] = new Swarm(infoHash, this)
128121
return swarm
@@ -157,6 +150,10 @@ Server.prototype.onHttpRequest = function (req, res) {
157150

158151
delete response.action // only needed for UDP encoding
159152
res.end(bencode.encode(response))
153+
154+
if (params.action === common.ACTIONS.ANNOUNCE) {
155+
self.emit(common.EVENT_NAMES[params.event], params.addr)
156+
}
160157
})
161158
}
162159

@@ -177,15 +174,21 @@ Server.prototype.onUdpRequest = function (msg, rinfo) {
177174
if (err) {
178175
self.emit('warning', err)
179176
response = {
180-
action: common.ACTIONS.ERRROR,
177+
action: common.ACTIONS.ERROR,
181178
'failure reason': err.message
182179
}
183180
}
181+
if (!self.listening) return
184182

185183
response.transactionId = params.transactionId
186184
response.connectionId = params.connectionId
185+
187186
var buf = makeUdpPacket(response)
188-
self._udpSocket.send(buf, 0, buf.length, rinfo.port, rinfo.address)
187+
self.udp.send(buf, 0, buf.length, rinfo.port, rinfo.address)
188+
189+
if (params.action === common.ACTIONS.ANNOUNCE) {
190+
self.emit(common.EVENT_NAMES[params.event], params.addr)
191+
}
189192
})
190193
}
191194

@@ -204,7 +207,8 @@ Server.prototype._onRequest = function (params, cb) {
204207
Server.prototype._onAnnounce = function (params, cb) {
205208
var self = this
206209
var swarm = self.getSwarm(params.info_hash)
207-
if (swarm === null) return cb(new Error('invalid hash'))
210+
if (swarm === null) return cb(new Error('disallowed info_hash'))
211+
if (!params.event || params.event === 'empty') params.event = 'update'
208212
swarm.announce(params, function (err, response) {
209213
if (response) {
210214
if (!response.action) response.action = common.ACTIONS.ANNOUNCE
@@ -311,7 +315,7 @@ function makeUdpPacket (params) {
311315
packet = Buffer.concat([
312316
common.toUInt32(common.ACTIONS.ERROR),
313317
common.toUInt32(params.transactionId || 0),
314-
new Buffer(params.message, 'utf8')
318+
new Buffer(params['failure reason'], 'utf8')
315319
])
316320
break
317321
default:

test/client-large-torrent.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
var Client = require('../')
22
var fs = require('fs')
33
var parseTorrent = require('parse-torrent')
4-
var portfinder = require('portfinder')
54
var Server = require('../').Server
65
var test = require('tape')
76

@@ -10,7 +9,7 @@ var parsedTorrent = parseTorrent(torrent)
109
var peerId = new Buffer('01234567890123456789')
1110

1211
test('large torrent: client.start()', function (t) {
13-
t.plan(6)
12+
t.plan(5)
1413

1514
var server = new Server({ http: false })
1615

@@ -22,9 +21,8 @@ test('large torrent: client.start()', function (t) {
2221
t.fail(err.message)
2322
})
2423

25-
portfinder.getPort(function (err, port) {
26-
t.error(err, 'found free port')
27-
server.listen(port)
24+
server.listen(0, function () {
25+
var port = server.udp.address().port
2826

2927
// remove all tracker servers except a single UDP one, for now
3028
parsedTorrent.announce = [ 'udp://127.0.0.1:' + port ]

test/client-magnet.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
var Client = require('../')
22
var magnet = require('magnet-uri')
3-
var portfinder = require('portfinder')
43
var Server = require('../').Server
54
var test = require('tape')
65

@@ -9,7 +8,7 @@ var parsedTorrent = magnet(uri)
98
var peerId = new Buffer('01234567890123456789')
109

1110
test('magnet + udp: client.start/update/stop()', function (t) {
12-
t.plan(12)
11+
t.plan(11)
1312

1413
var server = new Server({ http: false })
1514

@@ -21,9 +20,8 @@ test('magnet + udp: client.start/update/stop()', function (t) {
2120
t.fail(err.message)
2221
})
2322

24-
portfinder.getPort(function (err, port) {
25-
t.error(err, 'found free port')
26-
server.listen(port)
23+
server.listen(0, function () {
24+
var port = server.udp.address().port
2725
var announceUrl = 'udp://127.0.0.1:' + port
2826

2927
// remove all tracker servers except a single UDP one, for now

0 commit comments

Comments
 (0)