Scrape implementation for websocket. Issue #116

This commit is contained in:
Yoann Ciabaud 2016-03-14 00:15:41 +01:00
parent eb3cefec35
commit 39507bf8be
3 changed files with 103 additions and 26 deletions

View File

@ -47,6 +47,7 @@ WebSocketTracker.prototype.announce = function (opts) {
self._generateOffers(numwant, function (offers) {
var params = extend(opts, {
action: 'announce',
numwant: numwant,
info_hash: self.client._infoHashBinary,
peer_id: self.client._peerIdBinary,
@ -61,7 +62,21 @@ WebSocketTracker.prototype.announce = function (opts) {
WebSocketTracker.prototype.scrape = function (opts) {
var self = this
if (self.destroyed || self.reconnecting) return
self._onSocketError(new Error('scrape not supported ' + self.announceUrl))
if (!self.socket.connected) {
return self.socket.once('connect', self.scrape.bind(self, opts))
}
var infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
? opts.infoHash.map(function (infoHash) {
return infoHash.toString('binary')
})
: (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary
var params = {
action: 'scrape',
info_hash: infoHashes
}
self._send(params)
}
WebSocketTracker.prototype.destroy = function (onclose) {
@ -133,6 +148,18 @@ WebSocketTracker.prototype._onSocketData = function (data) {
return
}
if (data.action === common.ACTIONS.ANNOUNCE || data.offer || data.answer) {
self._onAnnounceResponse(data)
} else if (data.action === common.ACTIONS.SCRAPE) {
self._onScrapeResponse(data)
} else {
throw new Error('invalid action in WS response: ' + data.action)
}
}
WebSocketTracker.prototype._onAnnounceResponse = function (data) {
var self = this
if (data.info_hash !== self.client._infoHashBinary) {
debug(
'ignoring websocket data from %s for %s (looking for %s: reused socket)',
@ -215,6 +242,32 @@ WebSocketTracker.prototype._onSocketData = function (data) {
}
}
WebSocketTracker.prototype._onScrapeResponse = function (data) {
var self = this
// NOTE: the unofficial spec says to use the 'files' key, 'host' has been
// seen in practice
data = data.files || data.host || {}
var keys = Object.keys(data)
if (keys.length === 0) {
self.client.emit('warning', new Error('invalid scrape response'))
return
}
keys.forEach(function (infoHash) {
var response = data[infoHash]
// TODO: optionally handle data.flags.min_request_interval
// (separate from announce interval)
self.client.emit('scrape', {
announce: self.announceUrl,
infoHash: common.binaryToHex(infoHash),
complete: response.complete,
incomplete: response.incomplete,
downloaded: response.downloaded
})
})
}
WebSocketTracker.prototype._onSocketClose = function () {
var self = this
if (self.destroyed) return

View File

@ -6,33 +6,51 @@ function parseWebSocketRequest (socket, opts, params) {
if (!opts) opts = {}
params = JSON.parse(params) // may throw
params.action = common.ACTIONS.ANNOUNCE
params.type = 'ws'
params.socket = socket
if (params.action === 'announce' || params.answer || params.offers) {
params.action = common.ACTIONS.ANNOUNCE
if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) {
throw new Error('invalid info_hash')
}
params.info_hash = common.binaryToHex(params.info_hash)
if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) {
throw new Error('invalid peer_id')
}
params.peer_id = common.binaryToHex(params.peer_id)
if (params.answer) {
if (typeof params.to_peer_id !== 'string' || params.to_peer_id.length !== 20) {
throw new Error('invalid `to_peer_id` (required with `answer`)')
if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) {
throw new Error('invalid info_hash')
}
params.to_peer_id = common.binaryToHex(params.to_peer_id)
}
params.info_hash = common.binaryToHex(params.info_hash)
params.left = Number(params.left) || Infinity
params.numwant = Math.min(
Number(params.offers && params.offers.length) || 0, // no default - explicit only
common.MAX_ANNOUNCE_PEERS
)
params.compact = -1 // return full peer objects (used for websocket responses)
if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) {
throw new Error('invalid peer_id')
}
params.peer_id = common.binaryToHex(params.peer_id)
if (params.answer) {
if (typeof params.to_peer_id !== 'string' || params.to_peer_id.length !== 20) {
throw new Error('invalid `to_peer_id` (required with `answer`)')
}
params.to_peer_id = common.binaryToHex(params.to_peer_id)
}
params.left = Number(params.left) || Infinity
params.numwant = Math.min(
Number(params.offers && params.offers.length) || 0, // no default - explicit only
common.MAX_ANNOUNCE_PEERS
)
params.compact = -1 // return full peer objects (used for websocket responses)
} else if (params.action === 'scrape') {
params.action = common.ACTIONS.SCRAPE
if (typeof params.info_hash === 'string') params.info_hash = [ params.info_hash ]
if (Array.isArray(params.info_hash)) {
params.info_hash = params.info_hash.map(function (binaryInfoHash) {
if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) {
throw new Error('invalid info_hash')
}
return common.binaryToHex(binaryInfoHash)
})
} else {
params.info_hash = common.binaryToHex(params.info_hash)
}
} else {
throw new Error('invalid action in WS request: ' + params.action)
}
params.ip = opts.trustProxy
? socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress

View File

@ -327,6 +327,7 @@ Server.prototype._onWebSocketRequest = function (socket, opts, params) {
self._onRequest(params, function (err, response) {
if (err) {
socket.send(JSON.stringify({
action: params.action,
'failure reason': err.message,
info_hash: common.hexToBinary(params.info_hash)
}), socket.onSend)
@ -336,9 +337,14 @@ Server.prototype._onWebSocketRequest = function (socket, opts, params) {
}
if (self.destroyed) return
if (socket.infoHashes.indexOf(params.info_hash) === -1) {
socket.infoHashes.push(params.info_hash)
}
var hashes
if (typeof params.info_hash === 'string') hashes = [ params.info_hash ]
else hashes = params.info_hash
hashes.forEach(function (info_hash) {
if (socket.infoHashes.indexOf(info_hash) === -1) {
socket.infoHashes.push(info_hash)
}
})
var peers = response.peers
delete response.peers