Skip to content

Commit

Permalink
Added support for the sequence/multipart, start and end types of the …
Browse files Browse the repository at this point in the history
…'Range' header in the 'GET' method
  • Loading branch information
AdrienCastex committed Aug 3, 2017
1 parent 25a1cf3 commit dd23bfc
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 20 deletions.
121 changes: 111 additions & 10 deletions lib/server/v2/commands/Get.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,33 @@ var __extends = (this && this.__extends) || (function () {
Object.defineProperty(exports, "__esModule", { value: true });
var WebDAVRequest_1 = require("../WebDAVRequest");
var stream_1 = require("stream");
var MultipleRangedStream = (function (_super) {
__extends(MultipleRangedStream, _super);
function MultipleRangedStream(ranges) {
var _this = _super.call(this) || this;
_this.ranges = ranges;
_this.streams = ranges.map(function (r) {
return {
stream: new RangedStream(r.min, r.max),
range: r
};
});
return _this;
}
MultipleRangedStream.prototype._transform = function (chunk, encoding, callback) {
this.streams.forEach(function (streamRange) {
streamRange.stream.write(chunk, encoding);
});
callback(null, new Buffer(0));
};
MultipleRangedStream.prototype.end = function (chunk, encoding, cb) {
var _this = this;
if (this.onEnded)
process.nextTick(function () { return _this.onEnded(); });
_super.prototype.end.call(this, chunk, encoding, cb);
};
return MultipleRangedStream;
}(stream_1.Transform));
var RangedStream = (function (_super) {
__extends(RangedStream, _super);
function RangedStream(min, max) {
Expand Down Expand Up @@ -46,6 +73,31 @@ var RangedStream = (function (_super) {
};
return RangedStream;
}(stream_1.Transform));
function parseRangeBlock(size, block) {
size -= 1;
var rRange = /([0-9]+)-([0-9]+)/;
var match = rRange.exec(block);
if (match)
return {
min: Math.min(size, parseInt(match[1], 10)),
max: Math.min(size, parseInt(match[2], 10))
};
var rStart = /([0-9]+)-/;
match = rStart.exec(block);
if (match)
return {
min: Math.min(size + 1, parseInt(match[1], 10)),
max: size
};
var rEnd = /-([0-9]+)/;
match = rEnd.exec(block);
if (match)
return {
min: Math.max(0, size - parseInt(match[1], 10) + 1),
max: size
};
throw new Error('Cannot parse the range block');
}
var default_1 = (function () {
function default_1() {
}
Expand Down Expand Up @@ -86,16 +138,65 @@ var default_1 = (function () {
}
//ctx.invokeEvent('read', r);
if (range) {
var rex = /([0-9]+)/g;
var min = parseInt(rex.exec(range)[1], 10);
var max = parseInt(rex.exec(range)[1], 10);
ctx.setCode(WebDAVRequest_1.HTTPCodes.PartialContent);
ctx.response.setHeader('Accept-Ranges', 'bytes');
ctx.response.setHeader('Content-Type', mimeType);
ctx.response.setHeader('Content-Length', Math.min(size, max - min + 1).toString());
ctx.response.setHeader('Content-Range', 'bytes ' + min + '-' + max + '/*');
rstream.on('end', callback);
rstream.pipe(new RangedStream(min, max)).pipe(ctx.response);
try {
var createMultipart_1 = function (range) {
return '--' + separator_1 + '\r\nContent-Type: ' + mimeType + '\r\nContent-Range: bytes ' + range.min + '-' + range.max + '/*\r\n\r\n';
};
var endMultipart_1 = function () {
return '\r\n--' + separator_1 + '--';
};
var ranges_1 = range.split(',').map(function (block) { return parseRangeBlock(size, block); });
var separator_1 = Array.apply(null, { length: 20 }).map(function () { return String.fromCharCode('a'.charCodeAt(0) + Math.floor(Math.random() * 26)); }).join('');
var len = ranges_1.reduce(function (previous, mm) { return mm.max - mm.min + 1 + previous; }, 0) + (ranges_1.length > 1 ? ranges_1.reduce(function (previous, mm) { return createMultipart_1(mm).length + previous; }, endMultipart_1().length + '\r\n'.length * (ranges_1.length - 1)) : 0);
ctx.setCode(WebDAVRequest_1.HTTPCodes.PartialContent);
ctx.response.setHeader('Accept-Ranges', 'bytes');
ctx.response.setHeader('Content-Length', len.toString());
if (ranges_1.length <= 1) {
ctx.response.setHeader('Content-Type', mimeType);
ctx.response.setHeader('Content-Range', 'bytes ' + ranges_1[0].min + '-' + ranges_1[0].max + '/*');
rstream.on('end', callback);
return rstream.pipe(new RangedStream(ranges_1[0].min, ranges_1[0].max)).pipe(ctx.response);
}
ctx.response.setHeader('Content-Type', 'multipart/byteranges; boundary=' + separator_1);
var multi_1 = new MultipleRangedStream(ranges_1);
rstream.pipe(multi_1);
var current_1 = 0;
var dones_1 = {};
var evalNext_1 = function () {
if (current_1 === ranges_1.length) {
return ctx.response.end(endMultipart_1(), function () {
callback();
});
}
var sr = dones_1[current_1];
if (sr) {
if (current_1 > 0)
ctx.response.write('\r\n');
ctx.response.write(createMultipart_1(sr.range));
sr.stream.on('end', function () {
++current_1;
evalNext_1();
});
sr.stream.on('data', function (chunk, encoding) {
ctx.response.write(chunk, encoding);
});
//sr.stream.pipe(ctx.response);
}
};
multi_1.streams.forEach(function (sr, index) {
dones_1[index] = sr;
});
multi_1.onEnded = function () {
multi_1.streams.forEach(function (sr, index) {
sr.stream.end();
});
evalNext_1();
};
}
catch (ex) {
ctx.setCode(WebDAVRequest_1.HTTPCodes.BadRequest);
callback();
}
}
else {
ctx.setCode(WebDAVRequest_1.HTTPCodes.OK);
Expand Down
150 changes: 140 additions & 10 deletions src/server/v2/commands/Get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,39 @@ import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'
import { Errors } from '../../../Errors'
import { Transform } from 'stream'

class MultipleRangedStream extends Transform
{
streams : { stream : RangedStream, range : IRange }[]
onEnded : () => void

constructor(public ranges : IRange[])
{
super();

this.streams = ranges.map((r) => {
return {
stream: new RangedStream(r.min, r.max),
range: r
}
});
}

_transform(chunk : string | Buffer, encoding : string, callback : Function)
{
this.streams.forEach((streamRange) => {
streamRange.stream.write(chunk, encoding);
});

callback(null, new Buffer(0));
}

end(chunk ?: any, encoding?: any, cb?: Function): void
{
if(this.onEnded)
process.nextTick(() => this.onEnded());
super.end(chunk, encoding, cb);
}
}
class RangedStream extends Transform
{
nb : number;
Expand Down Expand Up @@ -44,6 +77,43 @@ class RangedStream extends Transform
}
}

interface IRange
{
min : number
max : number
}

function parseRangeBlock(size : number, block : string) : IRange
{
size -= 1;

const rRange = /([0-9]+)-([0-9]+)/;
let match = rRange.exec(block);
if(match)
return {
min: Math.min(size, parseInt(match[1], 10)),
max: Math.min(size, parseInt(match[2], 10))
};

const rStart = /([0-9]+)-/;
match = rStart.exec(block);
if(match)
return {
min: Math.min(size + 1, parseInt(match[1], 10)),
max: size
};

const rEnd = /-([0-9]+)/;
match = rEnd.exec(block);
if(match)
return {
min: Math.max(0, size - parseInt(match[1], 10) + 1),
max: size
};

throw new Error('Cannot parse the range block');
}

export default class implements HTTPMethod
{
unchunked(ctx : HTTPRequestContext, data : Buffer, callback : () => void) : void
Expand Down Expand Up @@ -95,18 +165,78 @@ export default class implements HTTPMethod

if(range)
{
const rex = /([0-9]+)/g;
const min = parseInt(rex.exec(range)[1], 10);
const max = parseInt(rex.exec(range)[1], 10);
try
{
const createMultipart = (range : IRange) => {
return '--' + separator + '\r\nContent-Type: ' + mimeType + '\r\nContent-Range: bytes ' + range.min + '-' + range.max + '/*\r\n\r\n';
};
const endMultipart = () => {
return '\r\n--' + separator + '--';
};

ctx.setCode(HTTPCodes.PartialContent);
ctx.response.setHeader('Accept-Ranges', 'bytes')
ctx.response.setHeader('Content-Type', mimeType)
ctx.response.setHeader('Content-Length', Math.min(size, max - min + 1).toString())
ctx.response.setHeader('Content-Range', 'bytes ' + min + '-' + max + '/*')
const ranges = range.split(',').map((block) => parseRangeBlock(size, block));
const separator = Array.apply(null, {length: 20}).map(() => String.fromCharCode('a'.charCodeAt(0) + Math.floor(Math.random() * 26))).join('');
const len = ranges.reduce((previous, mm) => mm.max - mm.min + 1 + previous, 0) + (ranges.length > 1 ? ranges.reduce((previous, mm) => createMultipart(mm).length + previous, endMultipart().length + '\r\n'.length * (ranges.length - 1)) : 0);

rstream.on('end', callback);
rstream.pipe(new RangedStream(min, max)).pipe(ctx.response);
ctx.setCode(HTTPCodes.PartialContent);
ctx.response.setHeader('Accept-Ranges', 'bytes')
ctx.response.setHeader('Content-Length', len.toString())
if(ranges.length <= 1)
{
ctx.response.setHeader('Content-Type', mimeType)
ctx.response.setHeader('Content-Range', 'bytes ' + ranges[0].min + '-' + ranges[0].max + '/*')
rstream.on('end', callback);
return rstream.pipe(new RangedStream(ranges[0].min, ranges[0].max)).pipe(ctx.response);
}

ctx.response.setHeader('Content-Type', 'multipart/byteranges; boundary=' + separator)

const multi = new MultipleRangedStream(ranges);
rstream.pipe(multi);

let current = 0;
const dones = {};
const evalNext = () => {
if(current === ranges.length)
{
return ctx.response.end(endMultipart(), () => {
callback();
});
}

const sr = dones[current];
if(sr)
{
if(current > 0)
ctx.response.write('\r\n');
ctx.response.write(createMultipart(sr.range));

sr.stream.on('end', () => {
++current;
evalNext();
});
sr.stream.on('data', (chunk, encoding) => {
ctx.response.write(chunk, encoding);
})
//sr.stream.pipe(ctx.response);
}
}
multi.streams.forEach((sr, index) => {
dones[index] = sr;
})

multi.onEnded = () => {
multi.streams.forEach((sr, index) => {
sr.stream.end();
});
evalNext();
}
}
catch(ex)
{
ctx.setCode(HTTPCodes.BadRequest);
callback();
}
}
else
{
Expand Down

0 comments on commit dd23bfc

Please sign in to comment.