From 299024229f1efefaa2ece7aa21802d5296a56577 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 11 Sep 2019 09:36:37 -0500 Subject: [PATCH] Support byte range requests (#71) * Support Byte Range Requests See https://github.com/parse-community/parse-server/pull/6028 * use promises * Add Tests * rename getFileStream to handleFileStream --- index.js | 28 ++++++++++++++++ spec/test.spec.js | 82 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/index.js b/index.js index d973492..7b76ae2 100644 --- a/index.js +++ b/index.js @@ -144,5 +144,33 @@ S3Adapter.prototype.getFileLocation = function(config, filename) { return (config.mount + '/files/' + config.applicationId + '/' + filename); } +S3Adapter.prototype.handleFileStream = function (filename, req, res) { + const params = { + Key: this._bucketPrefix + filename, + Range: req.get('Range'), + }; + return this.createBucket().then(() => { + return new Promise((resolve, reject) => { + this._s3Client.getObject(params, (error, data) => { + if (error !== null) { + return reject(error); + } + if (data && !data.Body) { + return reject(data); + } + res.writeHead(206, { + 'Accept-Ranges': data.AcceptRanges, + 'Content-Length': data.ContentLength, + 'Content-Range': data.ContentRange, + 'Content-Type': data.ContentType, + }); + res.write(data.Body); + res.end(); + resolve(data.Body); + }); + }); + }); +} + module.exports = S3Adapter; module.exports.default = S3Adapter; diff --git a/spec/test.spec.js b/spec/test.spec.js index f4a4334..1fa79b8 100644 --- a/spec/test.spec.js +++ b/spec/test.spec.js @@ -153,6 +153,88 @@ describe('S3Adapter tests', () => { }); }); + describe('getFileStream', () => { + it('should handle range bytes', () => { + const s3 = new S3Adapter('accessKey', 'secretKey', 'myBucket'); + s3._s3Client = { + createBucket: callback => callback(), + getObject: (params, callback) => { + const { Range } = params; + + expect(Range).toBe('bytes=0-1'); + + const data = { + Body: Buffer.from('hello world', 'utf8'), + }; + callback(null, data); + }, + }; + const req = { + get: () => 'bytes=0-1', + }; + const resp = { + writeHead: jasmine.createSpy('writeHead'), + write: jasmine.createSpy('write'), + end: jasmine.createSpy('end'), + }; + s3.handleFileStream('test.mov', req, resp).then((data) => { + expect(data.toString('utf8')).toBe('hello world'); + expect(resp.writeHead).toHaveBeenCalled(); + expect(resp.write).toHaveBeenCalled(); + expect(resp.end).toHaveBeenCalled(); + }); + }); + + it('should handle range bytes error', () => { + const s3 = new S3Adapter('accessKey', 'secretKey', 'myBucket'); + s3._s3Client = { + createBucket: callback => callback(), + getObject: (params, callback) => { + callback('FileNotFound', null); + }, + }; + const req = { + get: () => 'bytes=0-1', + }; + const resp = { + writeHead: jasmine.createSpy('writeHead'), + write: jasmine.createSpy('write'), + end: jasmine.createSpy('end'), + }; + s3.handleFileStream('test.mov', req, resp).catch((error) => { + expect(error).toBe('FileNotFound'); + expect(resp.writeHead).not.toHaveBeenCalled(); + expect(resp.write).not.toHaveBeenCalled(); + expect(resp.end).not.toHaveBeenCalled(); + }); + }); + + it('should handle range bytes no data', () => { + const s3 = new S3Adapter('accessKey', 'secretKey', 'myBucket'); + const data = { Error: 'NoBody' }; + s3._s3Client = { + createBucket: callback => callback(), + getObject: (params, callback) => { + callback(null, data); + }, + }; + const req = { + get: () => 'bytes=0-1', + }; + const resp = { + writeHead: jasmine.createSpy('writeHead'), + write: jasmine.createSpy('write'), + end: jasmine.createSpy('end'), + }; + s3.handleFileStream('test.mov', req, resp).catch((error) => { + expect(error).toBe(data); + expect(resp.writeHead).not.toHaveBeenCalled(); + expect(resp.write).not.toHaveBeenCalled(); + expect(resp.end).not.toHaveBeenCalled(); + }); + }); + }); + describe('getFileLocation', () => { var config = { mount: 'http://my.server.com/parse',