Skip to content

Don't read full file if range header is present #876

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 56 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a5ce745
feat: use streaming
JonnyBurger Apr 15, 2021
544f52a
should not throw error while reading stream
JonnyBurger Aug 11, 2021
3dc0120
chore(deps-dev): bump prettier from 2.3.0 to 2.3.1 (#935)
dependabot[bot] Jun 7, 2021
a3348e9
chore(deps-dev): bump eslint from 7.27.0 to 7.28.0 (#934)
dependabot[bot] Jun 7, 2021
ec1489b
chore(deps-dev): bump execa from 5.1.0 to 5.1.1 (#933)
dependabot[bot] Jun 7, 2021
2f65898
chore(deps-dev): bump jest from 27.0.3 to 27.0.4 (#932)
dependabot[bot] Jun 7, 2021
7cbdf2d
chore(deps-dev): bump @babel/core from 7.14.3 to 7.14.5 (#937)
dependabot[bot] Jun 10, 2021
a2e8bfb
chore(deps-dev): bump @babel/preset-env from 7.14.4 to 7.14.5 (#939)
dependabot[bot] Jun 10, 2021
7a9dda4
chore: update deps (#942)
snitin315 Jun 15, 2021
92f2639
chore(deps-dev): bump chokidar from 3.5.1 to 3.5.2 (#943)
dependabot[bot] Jun 16, 2021
c7074b7
chore(deps-dev): bump webpack from 5.39.0 to 5.39.1 (#944)
dependabot[bot] Jun 18, 2021
4928606
chore(deps-dev): bump @babel/cli from 7.14.3 to 7.14.5 (#938)
dependabot[bot] Jun 21, 2021
700b4e2
chore(deps-dev): bump webpack from 5.39.1 to 5.40.0 (#947)
dependabot[bot] Jun 22, 2021
d466d2c
chore: update deps (#949)
snitin315 Jun 22, 2021
1968a40
chore(deps-dev): bump babel-jest from 27.0.2 to 27.0.5 (#951)
dependabot[bot] Jun 23, 2021
e065199
chore(deps-dev): bump jest from 27.0.4 to 27.0.5 (#950)
dependabot[bot] Jun 25, 2021
a83aef5
chore(deps-dev): bump prettier from 2.3.1 to 2.3.2 (#954)
dependabot[bot] Jun 28, 2021
96d14cf
chore(deps-dev): bump jest from 27.0.5 to 27.0.6 (#957)
dependabot[bot] Jun 29, 2021
27601b4
chore(deps-dev): bump babel-jest from 27.0.5 to 27.0.6 (#955)
dependabot[bot] Jun 29, 2021
6ae5d9a
chore(deps-dev): bump webpack from 5.40.0 to 5.41.0 (#956)
dependabot[bot] Jun 29, 2021
86ac2a3
chore(deps-dev): bump webpack from 5.41.0 to 5.41.1 (#958)
dependabot[bot] Jun 30, 2021
ae5b3d6
chore: update husky and other deps (#959)
snitin315 Jul 2, 2021
92d5525
chore(deps-dev): bump webpack from 5.41.1 to 5.42.0 (#961)
dependabot[bot] Jul 5, 2021
84fa91f
chore(deps-dev): bump eslint from 7.29.0 to 7.30.0 (#960)
dependabot[bot] Jul 5, 2021
ab00b8a
chore(deps): bump schema-utils from 3.0.0 to 3.1.0 (#962)
dependabot[bot] Jul 7, 2021
1440f93
chore(deps-dev): bump webpack from 5.42.0 to 5.44.0 (#967)
dependabot[bot] Jul 12, 2021
c7c8d93
chore(deps-dev): bump husky from 7.0.0 to 7.0.1 (#965)
dependabot[bot] Jul 12, 2021
1c0db43
ci: use `actions/setup-node@v2` (#969)
snitin315 Jul 13, 2021
9497813
chore(deps-dev): bump lint-staged from 11.0.0 to 11.0.1 (#970)
dependabot[bot] Jul 14, 2021
dd7232f
chore(deps-dev): bump standard-version
dependabot[bot] Jul 15, 2021
4e90a0f
chore(deps-dev): bump del-cli from 4.0.0 to 4.0.1 (#972)
dependabot[bot] Jul 16, 2021
692f659
chore(deps-dev): bump webpack from 5.44.0 to 5.45.1 (#974)
dependabot[bot] Jul 19, 2021
cced858
chore(deps-dev): bump eslint from 7.30.0 to 7.31.0 (#973)
dependabot[bot] Jul 19, 2021
845eada
chore(deps): bump schema-utils from 3.1.0 to 3.1.1 (#975)
dependabot[bot] Jul 20, 2021
20971b1
chore(deps-dev): bump @babel/preset-env
dependabot[bot] Jul 21, 2021
0cf3b3c
chore(deps-dev): bump supertest from 6.1.3 to 6.1.4 (#980)
dependabot[bot] Jul 22, 2021
683f90e
chore(deps-dev): bump webpack from 5.45.1 to 5.46.0 (#981)
dependabot[bot] Jul 23, 2021
96600fc
chore(deps-dev): bump lint-staged from 11.0.1 to 11.1.0 (#982)
dependabot[bot] Jul 23, 2021
928c475
ci: setup `npm` cache (#983)
snitin315 Jul 24, 2021
64f9663
chore(deps-dev): bump lint-staged from 11.1.0 to 11.1.1 (#985)
dependabot[bot] Jul 27, 2021
9e9eb57
chore(deps-dev): bump @babel/core from
dependabot[bot] Jul 27, 2021
f7c03c1
chore(deps-dev): bump @babel/cli from
dependabot[bot] Jul 27, 2021
c16dd29
chore(deps): bump mime-types from 2.1.31 to 2.1.32 (#987)
dependabot[bot] Jul 28, 2021
70b4ff6
chore(deps-dev): bump webpack from 5.46.0 to 5.47.0 (#988)
dependabot[bot] Jul 28, 2021
1bbc09d
chore(deps-dev): bump webpack from 5.47.0 to 5.47.1 (#989)
dependabot[bot] Jul 31, 2021
d05ad41
chore(deps-dev): bump eslint from 7.31.0 to 7.32.0 (#991)
dependabot[bot] Aug 2, 2021
8e001c0
chore(deps-dev): bump @babel/preset-env
dependabot[bot] Aug 2, 2021
c02ffd2
chore(deps-dev): bump webpack from 5.47.1 to 5.48.0 (#992)
dependabot[bot] Aug 3, 2021
5b9889c
chore: update commitlint action in CI (#993)
anshumanv Aug 4, 2021
fca2070
chore(deps-dev): bump @babel/core
dependabot[bot] Aug 5, 2021
442d5cf
chore(deps-dev): bump @babel/preset-env
dependabot[bot] Aug 5, 2021
f1da1f3
ci: update `codecov-action` (#996)
snitin315 Aug 6, 2021
be7d3a7
chore(deps-dev): bump webpack from 5.48.0 to 5.49.0 (#999)
dependabot[bot] Aug 9, 2021
6b2d16c
chore(deps-dev): bump lint-staged from 11.1.1 to 11.1.2 (#998)
dependabot[bot] Aug 9, 2021
dc62b8e
chore(deps-dev): bump eslint-plugin-import from 2.23.4 to 2.24.0 (#997)
dependabot[bot] Aug 9, 2021
043da8a
chore(deps): bump colorette from 1.2.2 to 1.3.0 (#1001)
dependabot[bot] Aug 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ jobs:
fetch-depth: 0

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: "npm"

- name: Use latest NPM
run: sudo npm i -g npm
Expand All @@ -47,7 +48,7 @@ jobs:
run: npm run security

- name: Check commit message
uses: wagoid/commitlint-github-action@v1
uses: wagoid/commitlint-github-action@v4

test:
name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }}
Expand All @@ -64,9 +65,10 @@ jobs:
- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: "npm"

- name: Use latest NPM on ubuntu/macos
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
Expand Down Expand Up @@ -101,6 +103,6 @@ jobs:
run: npm run test:coverage -- --ci

- name: Submit coverage data to codecov
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
1 change: 0 additions & 1 deletion .husky/.gitignore

This file was deleted.

5,196 changes: 2,593 additions & 2,603 deletions package-lock.json

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"prepare": "husky install && npm run build",
"build": "del dist && babel src -d dist --copy-files",
"release": "standard-version",
"security": "npm audit",
"security": "npm audit --production",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
Expand All @@ -48,34 +48,34 @@
"schema-utils": "^3.0.0"
},
"devDependencies": {
"@babel/cli": "^7.14.3",
"@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.4",
"@babel/cli": "^7.14.5",
"@babel/core": "^7.14.6",
"@babel/preset-env": "^7.14.7",
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^27.0.2",
"babel-jest": "^27.0.6",
"chokidar": "^3.5.1",
"connect": "^3.7.0",
"cross-env": "^7.0.3",
"deepmerge": "^4.2.2",
"del": "^6.0.0",
"del-cli": "^3.0.1",
"eslint": "^7.26.0",
"del-cli": "^4.0.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"execa": "^5.0.1",
"execa": "^5.1.1",
"express": "^4.17.1",
"file-loader": "^6.2.0",
"husky": "^6.0.0",
"jest": "^27.0.3",
"husky": "^7.0.0",
"jest": "^27.0.6",
"lint-staged": "^11.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.0",
"prettier": "^2.3.2",
"standard-version": "^9.3.0",
"strip-ansi": "^6.0.0",
"supertest": "^6.1.3",
"webpack": "^5.38.1"
"webpack": "^5.41.1"
},
"keywords": [
"webpack",
Expand Down
37 changes: 20 additions & 17 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ export default function wrapper(context) {
headers = headers(req, res, context);
}

let content;
let fileSize;

if (!filename) {
await goNext();
return;
}

try {
content = context.outputFileSystem.readFileSync(filename);
fileSize = context.outputFileSystem.lstatSync(filename).size;
} catch (_ignoreError) {
await goNext();
return;
Expand Down Expand Up @@ -100,21 +100,24 @@ export default function wrapper(context) {
}

// Buffer
content = handleRangeHeaders(context, content, req, res);

// Express API
if (res.send) {
res.send(content);
}
// Node.js API
else {
res.setHeader("Content-Length", content.length);

if (req.method === "HEAD") {
res.end();
} else {
res.end(content);
}
const ranges = handleRangeHeaders(context, fileSize, req, res);
const stream = context.outputFileSystem.createReadStream(
filename,
ranges
? {
start: ranges.start,
end: ranges.end,
}
: {}
);

const responseSize = ranges ? 1 + (ranges.end - ranges.start) : fileSize;
res.setHeader("Content-Length", responseSize);

if (req.method === "HEAD") {
res.end();
} else {
stream.pipe(res);
}
}
};
Expand Down
20 changes: 8 additions & 12 deletions src/utils/handleRangeHeaders.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import parseRange from "range-parser";

export default function handleRangeHeaders(context, content, req, res) {
export default function handleRangeHeaders(context, size, req, res) {
// assumes express API. For other servers, need to add logic to access
// alternative header APIs
if (res.set) {
Expand All @@ -21,20 +21,20 @@ export default function handleRangeHeaders(context, content, req, res) {
}

if (range) {
const ranges = parseRange(content.length, range);
const ranges = parseRange(size, range);

// unsatisfiable
if (ranges === -1) {
// Express API
if (res.set) {
res.set("Content-Range", `bytes */${content.length}`);
res.set("Content-Range", `bytes */${size}`);
res.status(416);
}
// Node.js API
else {
// eslint-disable-next-line no-param-reassign
res.statusCode = 416;
res.setHeader("Content-Range", `bytes */${content.length}`);
res.setHeader("Content-Range", `bytes */${size}`);
}
} else if (ranges === -2) {
// malformed header treated as regular response
Expand All @@ -47,16 +47,13 @@ export default function handleRangeHeaders(context, content, req, res) {
"A Range header with multiple ranges was provided. Multiple ranges are not supported, so a regular response will be sent for this request."
);
} else {
// valid range header
const { length } = content;

// Express API
if (res.set) {
// Content-Range
res.status(206);
res.set(
"Content-Range",
`bytes ${ranges[0].start}-${ranges[0].end}/${length}`
`bytes ${ranges[0].start}-${ranges[0].end}/${size}`
);
}
// Node.js API
Expand All @@ -66,14 +63,13 @@ export default function handleRangeHeaders(context, content, req, res) {
res.statusCode = 206;
res.setHeader(
"Content-Range",
`bytes ${ranges[0].start}-${ranges[0].end}/${length}`
`bytes ${ranges[0].start}-${ranges[0].end}/${size}`
);
}

// eslint-disable-next-line no-param-reassign
content = content.slice(ranges[0].start, ranges[0].end + 1);
return ranges[0];
}
}

return content;
return null;
}
26 changes: 23 additions & 3 deletions test/middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,23 +231,43 @@ describe.each([
});

it('should return the "206" code for the "GET" request with the valid range header', (done) => {
const fileData = instance.context.outputFileSystem.readFileSync(
path.resolve(outputPath, "bundle.js"),
"utf8"
);
request(app)
.get("/bundle.js")
.set("Range", "bytes=3000-3500")
.expect("Content-Length", "501")
.expect("Content-Range", `bytes 3000-3500/${codeLength}`)
.expect(206, done);
.expect(206)
.then((response) => {
expect(response.text).toBe(fileData.substr(3000, 501));
expect(response.text.length).toBe(501);
done();
});
});

it('should return the "200" code for the "GET" request with malformed range header which is ignored', (done) => {
request(app).get("/bundle.js").set("Range", "abc").expect(200, done);
request(app)
.get("/bundle.js")
.set("Range", "abc")
.expect(200)
.then((response) => {
expect(response.text.length).toBe(codeLength);
done();
});
});

it('should return the "200" code for the "GET" request with multiple range header which is ignored', (done) => {
request(app)
.get("/bundle.js")
.set("Range", "bytes=3000-3100,3200-3300")
.expect(200, done);
.expect(200)
.then((response) => {
expect(response.text.length).toBe(codeLength);
done();
});
});

it('should return the "404" code for the "GET" request with to the non-public path', (done) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ Array [
]
`;

exports[`handleRangeHeaders should handle multiple ranges 1`] = `
exports[`handleRangeHeaders should handle multiple ranges by sending a regular response 1`] = `
Array [
Array [
"A Range header with multiple ranges was provided. Multiple ranges are not supported, so a regular response will be sent for this request.",
],
]
`;

exports[`handleRangeHeaders should handle multiple ranges 2`] = `
exports[`handleRangeHeaders should handle multiple ranges by sending a regular response 2`] = `
Array [
Array [
"Accept-Ranges",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ Array [
]
`;

exports[`handleRangeHeaders should handle multiple ranges 1`] = `
exports[`handleRangeHeaders should handle multiple ranges by sending a regular response 1`] = `
Array [
Array [
"A Range header with multiple ranges was provided. Multiple ranges are not supported, so a regular response will be sent for this request.",
],
]
`;

exports[`handleRangeHeaders should handle multiple ranges 2`] = `
exports[`handleRangeHeaders should handle multiple ranges by sending a regular response 2`] = `
Array [
Array [
"Accept-Ranges",
Expand Down
18 changes: 9 additions & 9 deletions test/utils/handleRangeHeaders.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ describe("handleRangeHeaders", () => {
},
};

const contentRes = handleRangeHeaders(context, content, req, res);
expect(contentRes).toEqual("bcde");
const ranges = handleRangeHeaders(context, content.length, req, res);
expect(ranges).toEqual({ start: 1, end: 4 });
expect(res.statusCode).toEqual(206);
expect(res.set.mock.calls).toMatchSnapshot();
});
Expand All @@ -53,8 +53,8 @@ describe("handleRangeHeaders", () => {
},
};

const contentRes = handleRangeHeaders(context, content, req, res);
expect(contentRes).toEqual("abcdef");
const ranges = handleRangeHeaders(context, content, req, res);
expect(ranges).toEqual(null);
expect(context.logger.error.mock.calls).toMatchSnapshot();
expect(res.statusCode).toBeUndefined();
expect(res.set.mock.calls).toMatchSnapshot();
Expand All @@ -78,13 +78,13 @@ describe("handleRangeHeaders", () => {
},
};

const contentRes = handleRangeHeaders(context, content, req, res);
expect(contentRes).toEqual("abcdef");
const ranges = handleRangeHeaders(context, content.length, req, res);
expect(ranges).toEqual(null);
expect(res.statusCode).toEqual(416);
expect(res.set.mock.calls).toMatchSnapshot();
});

it("should handle multiple ranges", () => {
it("should handle multiple ranges by sending a regular response", () => {
const content = "abcdef";
const req = {
headers: {
Expand All @@ -102,8 +102,8 @@ describe("handleRangeHeaders", () => {
},
};

const contentRes = handleRangeHeaders(context, content, req, res);
expect(contentRes).toEqual("abcdef");
const ranges = handleRangeHeaders(context, content.length, req, res);
expect(ranges).toEqual(null);
expect(context.logger.error.mock.calls).toMatchSnapshot();
expect(res.statusCode).toBeUndefined();
expect(res.set.mock.calls).toMatchSnapshot();
Expand Down