Skip to content

Commit

Permalink
support function send file
Browse files Browse the repository at this point in the history
  • Loading branch information
htdangkhoa committed Feb 28, 2021
1 parent b362163 commit b107401
Show file tree
Hide file tree
Showing 15 changed files with 3,275 additions and 302 deletions.
6 changes: 3 additions & 3 deletions .eslintrc.json
@@ -1,15 +1,15 @@
{
"env": {
"commonjs": true,
"es2021": true,
"es5": true,
"node": true,
"mocha": true
"jest/globals": true
},
"extends": ["airbnb-base", "plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": 12
},
"plugins": ["prettier"],
"plugins": ["prettier", "jest"],
"rules": {
"prettier/prettier": "error",
"no-unused-expressions": ["error", { "allowShortCircuit": true }],
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
@@ -1,3 +1,7 @@
# OSX
#
.DS_Store

# Logs
logs
*.log
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
@@ -0,0 +1 @@
lib/mime-types.json
13 changes: 13 additions & 0 deletions API.md
Expand Up @@ -159,6 +159,19 @@ The res object is an enhanced version of Node’s own response object and suppor

> Sends the HTTP response.The body parameter can be a Buffer object, a String, an object, Boolean, or an Array.
#### res.cookie(name, value [, options])

> Sets cookie name to value. The value parameter may be a string or object converted to JSON.
> `options`: An object to configuration the [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes) in header.
#### res.sendFile(path, [, options])

> Transfers the file at the given path. Sets the Content-Type response HTTP header field based on the filename’s extension.
>
> Options:
>
> - `headers`: The addition headers.
#### res.status(code)

> Sets the HTTP status for the response. It is a chainable alias of Node’s [response.statusCode](http://nodejs.org/api/http.html#http_response_statuscode).
Expand Down
6 changes: 6 additions & 0 deletions index.d.ts
Expand Up @@ -26,6 +26,10 @@ declare module 'pure-http' {
decode?(value: string): string;
}

export interface ISendFileOptions {
headers?: IHeader;
}

export interface IRequest {
originalUrl: string;

Expand Down Expand Up @@ -194,6 +198,8 @@ declare module 'pure-http' {
): void;

cookie(name, value, options?: ICookieSerializeOptions): this;

sendFile(filePath: string, options?: ISendFileOptions): void;
}

export interface IResponseHttp extends http.ServerResponse, IResponse {}
Expand Down
1 change: 1 addition & 0 deletions lib/mime-types.json

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions lib/mime.js
@@ -0,0 +1,24 @@
const types = require('./mime-types.json');

function getExtFromPath(path) {
const pathString = String(path);

const last = path.replace(/^.*[/\\]/, '').toLowerCase();
const ext = last.replace(/^.*\./, '').toLowerCase();

const hasPath = last.length < pathString.length;
const hasDot = ext.length < last.length - 1;

return ((hasDot || !hasPath) && '.' + ext) || null;
}

function getMimeType(path) {
const ext = getExtFromPath(path);

return types[ext || '.html'];
}

module.exports = {
getExtFromPath,
getMimeType,
};
38 changes: 35 additions & 3 deletions lib/response.js
@@ -1,9 +1,13 @@
const { STATUS_CODES } = require('http');
const path = require('path');
const fs = require('fs');

const { serialize } = require('./cookie');
const { getMimeType } = require('./mime');

const TYPE = 'content-type';
const TYPE = 'Content-Type';

const CONTENT_LENGTH = 'content-length';
const CONTENT_LENGTH = 'Content-Length';

const X_CONTENT_TYPE_OPTIONS = 'X-Content-Type-Options';

Expand All @@ -19,7 +23,7 @@ const TEXT_HTML = 'text/html;charset=utf-8';

const NOSNIFF = 'nosniff';

const SET_COOKIE = 'set-cookie';
const SET_COOKIE = 'Set-Cookie';

function parseOptions(res, ...args) {
let cached = true;
Expand Down Expand Up @@ -249,6 +253,32 @@ function cookie(res, name, value, options) {
return res;
}

function sendFile(res, filePath, options) {
const { headers } = options || {};

const readStream = fs.createReadStream(filePath);

const mime = getMimeType(filePath);
res.setHeader(TYPE, mime.type);

const stat = fs.statSync(filePath);
res.setHeader(CONTENT_LENGTH, stat.size);

if (typeof headers === 'object') {
for (const key in headers) {
if (Object.hasOwnProperty.call(headers, key)) {
const value = headers[key];

res.setHeader(key, value);
}
}
}

setCookie(res, res.headers);

return readStream.pipe(res);
}

function response(req, rawRes, options, appCallback) {
const res = rawRes;

Expand Down Expand Up @@ -283,6 +313,8 @@ function response(req, rawRes, options, appCallback) {

res.cookie = cookie.bind(this, res);

res.sendFile = sendFile.bind(this, res);

return res;
}

Expand Down
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -35,7 +35,7 @@
"LICENSE"
],
"scripts": {
"test": "mocha --timeout=10000 --exit test/*.test.js"
"test": "jest"
},
"dependencies": {},
"devDependencies": {
Expand All @@ -46,12 +46,13 @@
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.5",
"eslint-plugin-prettier": "^3.2.0",
"express": "^4.17.1",
"fastify": "^3.8.0",
"fastify-express": "^0.3.2",
"husky": "^4.3.5",
"mocha": "^8.2.1",
"jest": "^26.6.3",
"pem": "^1.14.4",
"polka": "^0.5.2",
"prettier": "^2.2.1",
Expand Down
3 changes: 3 additions & 0 deletions tests/index.css
@@ -0,0 +1,3 @@
.test {
background: blue;
}
File renamed without changes.
18 changes: 18 additions & 0 deletions test/server.js → tests/server.js
@@ -1,3 +1,4 @@
const path = require('path');
const bodyParser = require('body-parser');
const pureHttp = require('..');
const router = require('./router');
Expand All @@ -20,4 +21,21 @@ app.use('/', router);

app.all('/status', (req, res) => res.status(200).json({ success: true }));

app.get('/set-cookie', (req, res) => {
res.cookie('foo', 'bar');
res.cookie('ping', 'pong');

res.send('Ok');
});

app.post('/set-cookie', (req, res) => {
res.send(req.cookies);
});

app.all('/send-file', (req, res) => {
const filePath = path.resolve(process.cwd(), 'tests/index.css');

res.sendFile(filePath, { headers: { 'Cache-Control': 'no-store' } });
});

module.exports = app;
34 changes: 34 additions & 0 deletions test/server.test.js → tests/server.test.js
Expand Up @@ -72,3 +72,37 @@ describe('GET /not-found', () => {
await request.get('/not-found').expect(404);
});
});

describe('GET /set-cookie', () => {
it(`The headers must have the 'set-cookie'.`, async (done) => {
const res = await request.get('/set-cookie');

const cookies = res.headers['set-cookie'];

const cookie1 = cookies[0];
expect(cookie1).toBe('foo=bar');

const cookie2 = cookies[1];
expect(cookie2).toBe('ping=pong');

done();
});
});

describe('POST /set-cookie', () => {
it(`The cookie must be set in request.`, async () => {
await request
.post('/set-cookie')
.set('Cookie', ['foo=bar', 'ping=pong'])
.expect({ foo: 'bar', ping: 'pong' });
});
});

describe('ALL /send-file', () => {
it(`The 'content-type' in header should be 'text/css'.`, async () => {
await request
.post('/send-file')
.expect('cache-control', 'no-store')
.expect('content-type', 'text/css');
});
});
File renamed without changes.

0 comments on commit b107401

Please sign in to comment.