UNPKG

@masx200/koa-range

Version:

range request implementation for koa

139 lines (118 loc) 3.57 kB
var util = require("util"); var slice = require("stream-slice").slice; var Stream = require("stream"); const fs = require("fs"); const stat = util.promisify(fs.stat); module.exports = async function (ctx, next) { var range = ctx.header.range; ctx.set("Accept-Ranges", "bytes"); if (!range) { return next(); } var ranges = rangeParse(range); if (!ranges || ranges.length == 0) { ctx.status = 416; return; } if (ctx.method == "PUT") { ctx.status = 400; return; } await next(); if (ctx.response.has("Content-Range")) { return; } if (ctx.method != "GET" || ctx.body == null) { return; } var first = ranges[0]; var rawBody = ctx.body; var len = rawBody.length; // avoid multi ranges var firstRange = ranges[0]; var start = firstRange[0]; var end = firstRange[1]; if (!Buffer.isBuffer(rawBody)) { if (rawBody instanceof Stream.Readable) { len = ctx.length || "*"; if (rawBody.path) { let path = rawBody.path; if (!Number.isInteger(len)) { let stats = await stat(path); len = stats.size; } rawBody = fs.createReadStream(rawBody.path, { start, end: end, }); } else { if (end === Infinity && !Number.isInteger(len)) { return; } rawBody = rawBody.pipe(slice(start, end + 1)); } } else if (typeof rawBody !== "string") { rawBody = new Buffer(JSON.stringify(rawBody)); len = rawBody.length; } else { rawBody = new Buffer(rawBody); len = rawBody.length; } } // Adjust infinite end if (end === Infinity) { if (Number.isInteger(len)) { end = len - 1; } else { // FIXME(Calle Svensson): If we don't know how much we can return, we do a normal HTTP 200 repsonse ctx.status = 200; return; } } ctx.status = 206; // Adjust end while larger than len if (Number.isInteger(len) && end >= len) { end = len - 1; if (start === 0) { ctx.status = 200; } } var args = [start, end + 1].filter(function (item) { return typeof item == "number"; }); ctx.set("Content-Range", rangeContentGenerator(start, end, len)); if (rawBody instanceof Stream) { ctx.body = rawBody; } else { ctx.body = rawBody.slice.apply(rawBody, args); } if (len !== "*") { let ctxlength = end - start + 1; if (ctxlength <= 0) { ctx.status = 416; } ctx.length = Math.max(0, ctxlength); } }; function rangeParse(str) { var token = str.split("="); if (!token || token.length != 2 || token[0] != "bytes") { return null; } return token[1] .split(",") .map(function (range) { return range.split("-").map(function (value) { if (value === "") { return Infinity; } return Number(value); }); }) .filter(function (range) { return !isNaN(range[0]) && !isNaN(range[1]) && range[0] <= range[1]; }); } function rangeContentGenerator(start, end, length) { return util.format("bytes %d-%d/%s", start, end, length); }