-
-
Notifications
You must be signed in to change notification settings - Fork 169
GridFS 206 Streaming
dr.dimitru edited this page Oct 19, 2017
·
3 revisions
By default files served from GridFS returned with 200
response code. Which means if you serve video/audio/large downloads, file will be entirely read to memory and serve to client. This behavior is not best solution in terms of performance and resource usage.
206
partial content response is much better from all sides, for video and audio it allows to support time-seeking, for large files - resumable downloads. For server-side it reduces memory and CPU consumption.
Below is code sample suggested by @j1016h, where interceptDownload
is used to alter default file-serving behavior - use it as it at your own risk, or take it and modify to meet your own needs.
interceptDownload(http, image, versionName) {
function interceptDownloadServe(http, fileRef, vRef, version, readableStream, responseType, force200) {
var array, dispositionEncoding, dispositionName, dispositionType, end, headers, key, partiral, reqRange, self, start, stream, streamErrorHandler, take, text, value;
if (version == null) {
version = 'original';
}
if (readableStream == null) {
readableStream = null;
}
if (responseType == null) {
responseType = '200';
}
if (force200 == null) {
force200 = false;
}
self = this;
partiral = false;
reqRange = false;
if (http.params.query.download && http.params.query.download === 'true') {
dispositionType = 'attachment; ';
} else {
dispositionType = 'inline; ';
}
dispositionName = "filename=\"" + (encodeURIComponent(fileRef.name)) + "\"; filename=*UTF-8\"" + (encodeURIComponent(fileRef.name)) + "\"; ";
dispositionEncoding = 'charset=utf-8';
http.response.setHeader('Content-Disposition', dispositionType + dispositionName + dispositionEncoding);
if (http.request.headers.range && !force200) {
partiral = true;
array = http.request.headers.range.split(/bytes=([0-9]*)-([0-9]*)/);
start = parseInt(array[1]);
end = parseInt(array[2]);
if (isNaN(end)) {
end = vRef.size - 1;
}
take = end - start;
} else {
start = 0;
end = vRef.size - 1;
take = vRef.size;
}
if (partiral || (http.params.query.play && http.params.query.play === 'true')) {
reqRange = {
start: start,
end: end
};
if (isNaN(start) && !isNaN(end)) {
reqRange.start = end - take;
reqRange.end = end;
}
if (!isNaN(start) && isNaN(end)) {
reqRange.start = start;
reqRange.end = start + take;
}
if ((start + take) >= vRef.size) {
reqRange.end = vRef.size - 1;
}
if (self.strict && (reqRange.start >= (vRef.size - 1) || reqRange.end > (vRef.size - 1))) {
responseType = '416';
} else {
responseType = '206';
}
} else {
responseType = '200';
}
streamErrorHandler = function(error) {
http.response.writeHead(500);
http.response.end(error.toString());
if (self.debug) {
console.error("[FilesCollection] [serve(" + vRef.path + ", " + version + ")] [500]", error);
}
};
headers = http.request.headers;
switch (responseType) {
case '400':
if (self.debug) {
console.warn("[FilesCollection] [serve(" + vRef.path + ", " + version + ")] [400] Content-Length mismatch!");
}
text = 'Content-Length mismatch!';
http.response.writeHead(400, {
'Content-Type': 'text/plain',
'Content-Length': text.length
});
http.response.end(text);
break;
case '404':
return self._404(http);
break;
case '416':
if (self.debug) {
console.warn("[FilesCollection] [serve(" + vRef.path + ", " + version + ")] [416] Content-Range is not specified!");
}
http.response.writeHead(416);
http.response.end();
break;
case '200':
if (self.debug) {
console.info("[FilesCollection] [serve(" + vRef.path + ", " + version + ")] [200]");
}
stream = readableStream || fs.createReadStream(vRef.path);
if (readableStream) {
http.response.writeHead(200);
}
stream.on('open', function() {
http.response.writeHead(200);
}).on('error', streamErrorHandler).on('end', function() {
http.response.end();
}).pipe(http.response);
break;
case '206':
if (self.debug) {
console.info("[FilesCollection] [serve(" + vRef.path + ", " + version + ")] [206]");
}
http.response.setHeader('Content-Range', "bytes " + reqRange.start + "-" + reqRange.end + "/" + vRef.size);
var myid = (fileRef.versions[version].meta || {}).gridFsFileId;
stream = readableStream || gfs.createReadStream({
_id: myid,
range: {
startPos: reqRange.start,
endPos: reqRange.end
}
});
if (readableStream) {
http.response.writeHead(206);
}
stream.on('open', function() {
http.response.writeHead(206);
}).on('error', streamErrorHandler).on('end', function() {
http.response.end();
}).pipe(http.response);
break;
}
}
const _id = (image.versions[versionName].meta || {}).gridFsFileId;
if (_id) {
interceptDownloadServe(http, image, image.versions[versionName], versionName, null, '206', false);
}
return Boolean(_id);
}
Meteor-Files | Support | Try ostr.io |
---|---|---|
If you found this package useful, — star it at GitHub and support our open source contributions with a monthly pledge, or submit a one-time donation via PayPal | Monitoring, Analytics, WebSec, Web-CRON and Pre-rendering for a website |