prototypes/server/extend/response-pipe-file.js

/**
 * @private
 * @param {SGAppsServerRequest} request 
 * @param {SGAppsServerResponse} response 
 * @param {SGAppsServer} server 
 * @param {function} callback
 */
function ResponsePipeFileDecorator(request, response, server, callback) {
	if (request === null || response === null) {
		callback();
		return;
	}

	/**
	 * @memberof SGAppsServerResponse#
	 * @method pipeFile
	 * @param {string} filePath 
	 * @param {SGAppsServerErrorOnlyCallback} callback represents a `Function(Error)`
	 */
	response.pipeFile = function ResponsePipe(filePath, callback) {
		var returned = false;
		var _callback = function (err) { if (!returned && callback) callback(err); };

		if (response._flags.finished) {
			_callback(Error('Response is already ended'));
			return;
		}
		
		server._fs.stat(filePath, function (err, stat) {
			if (err) {
				_callback(err);
				return;
			}

			let readStream, start, end, total = stat.size, chunkSize;

			if (request.request) {
				var range = request.request.headers.range || "";
				if (range && total) {
					var parts = range.replace(/bytes=/, "").split("-");
					var partialStart = parts[0];
					var partialEnd = parts[1];

					start = parseInt(partialStart, 10);
					end = partialEnd ? parseInt(partialEnd, 10) : total-1;
					chunkSize = (end-start)+1;

					if (
						!response.response.headersSent
						&& !response._flags.finished
					) {
						response.response.statusCode	= 206;
						response.response.setHeader(
							"Content-Range",
							`bytes ${start}-${end}/${total}`
						);
						response.response.setHeader("Accept-Ranges", "bytes");
						response.response.setHeader("Content-Length", chunkSize);
					}
				}
			}

			if (typeof(start) === "number") {
				if (typeof(end) === "number") {
					readStream = server._fs.createReadStream(filePath, { start: start, end: end });
				} else {
					readStream = server._fs.createReadStream(filePath, { start: start });
				}
			} else {
				readStream = server._fs.createReadStream(filePath);
			}



			// This catches any errors that happen while creating the readable stream (usually invalid names)
			readStream.on('error', function(err) {
				_callback(err);
			});

			// // This will wait until we know the readable stream is actually valid before piping
			readStream.on('open', function () {
				// This just pipes the read stream to the response object (which goes to the client)
				readStream.pipe(response.response);
			});

			response.response.on('close', function() {
				// Resume the read stream when the write stream gets hungry
				readStream.destroy();
			});

			response.response.on('end', function() {
				// Resume the read stream when the write stream gets hungry
				readStream.destroy();
			});

			readStream.on('end', function() {
				_callback(null);
			});
		});
	};

	// response._destroy.push(function ResponsePipeEnd() {
	// 	delete response.pipeFile;
	// });

	callback();
};

module.exports = ResponsePipeFileDecorator;