From 8cfab05bce6e1c2c917c31ad5bec227697e472c1 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 26 Jul 2018 21:48:14 -0700 Subject: esnext: add webconn --- lib/webconn.js | 317 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 157 insertions(+), 160 deletions(-) (limited to 'lib') diff --git a/lib/webconn.js b/lib/webconn.js index 133f738..9c2b461 100644 --- a/lib/webconn.js +++ b/lib/webconn.js @@ -1,197 +1,194 @@ -module.exports = WebConn - -var BitField = require('bitfield') -var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('webtorrent:webconn') -var get = require('simple-get') -var inherits = require('inherits') -var sha1 = require('simple-sha1') -var Wire = require('bittorrent-protocol') - -var VERSION = require('../package.json').version +const BitField = require('bitfield') +const Buffer = require('safe-buffer').Buffer +const debug = require('debug')('webtorrent:webconn') +const get = require('simple-get') +const sha1 = require('simple-sha1') +const Wire = require('bittorrent-protocol') -inherits(WebConn, Wire) +const VERSION = require('../package.json').version /** * Converts requests for torrent blocks into http range requests. * @param {string} url web seed url * @param {Object} torrent */ -function WebConn (url, torrent) { - Wire.call(this) +class WebConn extends Wire { + constructor (url, torrent) { + super() - this.url = url - this.webPeerId = sha1.sync(url) - this._torrent = torrent + this.url = url + this.webPeerId = sha1.sync(url) + this._torrent = torrent - this._init() -} + this._init() + } -WebConn.prototype._init = function () { - var self = this - self.setKeepAlive(true) - - self.once('handshake', function (infoHash, peerId) { - if (self.destroyed) return - self.handshake(infoHash, self.webPeerId) - var numPieces = self._torrent.pieces.length - var bitfield = new BitField(numPieces) - for (var i = 0; i <= numPieces; i++) { - bitfield.set(i, true) - } - self.bitfield(bitfield) - }) - - self.once('interested', function () { - debug('interested') - self.unchoke() - }) - - self.on('uninterested', function () { debug('uninterested') }) - self.on('choke', function () { debug('choke') }) - self.on('unchoke', function () { debug('unchoke') }) - self.on('bitfield', function () { debug('bitfield') }) - - self.on('request', function (pieceIndex, offset, length, callback) { - debug('request pieceIndex=%d offset=%d length=%d', pieceIndex, offset, length) - self.httpRequest(pieceIndex, offset, length, callback) - }) -} + _init () { + this.setKeepAlive(true) -WebConn.prototype.httpRequest = function (pieceIndex, offset, length, cb) { - var self = this - var pieceOffset = pieceIndex * self._torrent.pieceLength - var rangeStart = pieceOffset + offset /* offset within whole torrent */ - var rangeEnd = rangeStart + length - 1 - - // Web seed URL format: - // For single-file torrents, make HTTP range requests directly to the web seed URL - // For multi-file torrents, add the torrent folder and file name to the URL - var files = self._torrent.files - var requests - if (files.length <= 1) { - requests = [{ - url: self.url, - start: rangeStart, - end: rangeEnd - }] - } else { - var requestedFiles = files.filter(function (file) { - return file.offset <= rangeEnd && (file.offset + file.length) > rangeStart + this.once('handshake', (infoHash, peerId) => { + if (this.destroyed) return + this.handshake(infoHash, this.webPeerId) + const numPieces = this._torrent.pieces.length + const bitfield = new BitField(numPieces) + for (let i = 0; i <= numPieces; i++) { + bitfield.set(i, true) + } + this.bitfield(bitfield) }) - if (requestedFiles.length < 1) { - return cb(new Error('Could not find file corresponnding to web seed range request')) - } - requests = requestedFiles.map(function (requestedFile) { - var fileEnd = requestedFile.offset + requestedFile.length - 1 - var url = self.url + - (self.url[self.url.length - 1] === '/' ? '' : '/') + - requestedFile.path - return { - url: url, - fileOffsetInRange: Math.max(requestedFile.offset - rangeStart, 0), - start: Math.max(rangeStart - requestedFile.offset, 0), - end: Math.min(fileEnd, rangeEnd - requestedFile.offset) - } + this.once('interested', () => { + debug('interested') + this.unchoke() }) - } - // Now make all the HTTP requests we need in order to load this piece - // Usually that's one requests, but sometimes it will be multiple - // Send requests in parallel and wait for them all to come back - var numRequestsSucceeded = 0 - var hasError = false + this.on('uninterested', () => { debug('uninterested') }) + this.on('choke', () => { debug('choke') }) + this.on('unchoke', () => { debug('unchoke') }) + this.on('bitfield', () => { debug('bitfield') }) - var ret - if (requests.length > 1) { - ret = Buffer.alloc(length) + this.on('request', (pieceIndex, offset, length, callback) => { + debug('request pieceIndex=%d offset=%d length=%d', pieceIndex, offset, length) + this.httpRequest(pieceIndex, offset, length, callback) + }) } - requests.forEach(function (request) { - var url = request.url - var start = request.start - var end = request.end - debug( - 'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d', - url, pieceIndex, offset, length, start, end - ) - var opts = { - url: url, - method: 'GET', - headers: { - 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)', - range: 'bytes=' + start + '-' + end + httpRequest (pieceIndex, offset, length, cb) { + const pieceOffset = pieceIndex * this._torrent.pieceLength + const rangeStart = pieceOffset + offset /* offset within whole torrent */ + const rangeEnd = rangeStart + length - 1 + + // Web seed URL format: + // For single-file torrents, make HTTP range requests directly to the web seed URL + // For multi-file torrents, add the torrent folder and file name to the URL + const files = this._torrent.files + let requests + if (files.length <= 1) { + requests = [{ + url: this.url, + start: rangeStart, + end: rangeEnd + }] + } else { + const requestedFiles = files.filter(file => { + return file.offset <= rangeEnd && (file.offset + file.length) > rangeStart + }) + if (requestedFiles.length < 1) { + return cb(new Error('Could not find file corresponnding to web seed range request')) } + + requests = requestedFiles.map(requestedFile => { + const fileEnd = requestedFile.offset + requestedFile.length - 1 + const url = this.url + + (this.url[this.url.length - 1] === '/' ? '' : '/') + + requestedFile.path + return { + url, + fileOffsetInRange: Math.max(requestedFile.offset - rangeStart, 0), + start: Math.max(rangeStart - requestedFile.offset, 0), + end: Math.min(fileEnd, rangeEnd - requestedFile.offset) + } + }) } - function onResponse (res, data) { - if (res.statusCode < 200 || res.statusCode >= 300) { - hasError = true - return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) - } - debug('Got data of length %d', data.length) - - if (requests.length === 1) { - // Common case: fetch piece in a single HTTP request, return directly - cb(null, data) - } else { - // Rare case: reconstruct multiple HTTP requests across 2+ files into one - // piece buffer - data.copy(ret, request.fileOffsetInRange) - if (++numRequestsSucceeded === requests.length) { - cb(null, ret) + + // Now make all the HTTP requests we need in order to load this piece + // Usually that's one requests, but sometimes it will be multiple + // Send requests in parallel and wait for them all to come back + let numRequestsSucceeded = 0 + let hasError = false + + let ret + if (requests.length > 1) { + ret = Buffer.alloc(length) + } + + requests.forEach(request => { + const url = request.url + const start = request.start + const end = request.end + debug( + 'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d', + url, pieceIndex, offset, length, start, end + ) + const opts = { + url, + method: 'GET', + headers: { + 'user-agent': `WebTorrent/${VERSION} (https://webtorrent.io)`, + range: `bytes=${start}-${end}` } } - } - get.concat(opts, function (err, res, data) { - if (hasError) return - if (err) { - // Browsers allow HTTP redirects for simple cross-origin - // requests but not for requests that require preflight. - // Use a simple request to unravel any redirects and get the - // final URL. Retry the original request with the new URL if - // it's different. - // - // This test is imperfect but it's simple and good for common - // cases. It catches all cross-origin cases but matches a few - // same-origin cases too. - if (typeof window === 'undefined' || url.startsWith(window.location.origin + '/')) { + function onResponse (res, data) { + if (res.statusCode < 200 || res.statusCode >= 300) { hasError = true - return cb(err) + return cb(new Error(`Unexpected HTTP status code ${res.statusCode}`)) } - - return get.head(url, function (errHead, res) { - if (hasError) return - if (errHead) { - hasError = true - return cb(errHead) - } - if (res.statusCode < 200 || res.statusCode >= 300) { - hasError = true - return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) + debug('Got data of length %d', data.length) + + if (requests.length === 1) { + // Common case: fetch piece in a single HTTP request, return directly + cb(null, data) + } else { + // Rare case: reconstruct multiple HTTP requests across 2+ files into one + // piece buffer + data.copy(ret, request.fileOffsetInRange) + if (++numRequestsSucceeded === requests.length) { + cb(null, ret) } - if (res.url === url) { + } + } + get.concat(opts, (err, res, data) => { + if (hasError) return + if (err) { + // Browsers allow HTTP redirects for simple cross-origin + // requests but not for requests that require preflight. + // Use a simple request to unravel any redirects and get the + // final URL. Retry the original request with the new URL if + // it's different. + // + // This test is imperfect but it's simple and good for common + // cases. It catches all cross-origin cases but matches a few + // same-origin cases too. + if (typeof window === 'undefined' || url.startsWith(`${window.location.origin}/`)) { hasError = true return cb(err) } - opts.url = res.url - get.concat(opts, function (err, res, data) { + return get.head(url, (errHead, res) => { if (hasError) return - if (err) { + if (errHead) { + hasError = true + return cb(errHead) + } + if (res.statusCode < 200 || res.statusCode >= 300) { + hasError = true + return cb(new Error(`Unexpected HTTP status code ${res.statusCode}`)) + } + if (res.url === url) { hasError = true return cb(err) } - onResponse(res, data) + + opts.url = res.url + get.concat(opts, (err, res, data) => { + if (hasError) return + if (err) { + hasError = true + return cb(err) + } + onResponse(res, data) + }) }) - }) - } - onResponse(res, data) + } + onResponse(res, data) + }) }) - }) -} + } -WebConn.prototype.destroy = function () { - Wire.prototype.destroy.call(this) - this._torrent = null + destroy () { + super.destroy() + this._torrent = null + } } + +module.exports = WebConn -- cgit v1.2.3