Skip to content

Commit 7a39dff

Browse files
committed
http: add sendFile to OutgoingMessage
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
1 parent 2aaa21f commit 7a39dff

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed

lib/_http_outgoing.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,90 @@ function connectionCorkNT(conn) {
10051005
conn.uncork();
10061006
}
10071007

1008+
OutgoingMessage.prototype.sendFile = function sendFile (path, optsOrCallback, callback) {
1009+
let start = 0;
1010+
let end = null;
1011+
let fd = null;
1012+
1013+
if (typeof optsOrCallback === 'function') {
1014+
callback = optsOrCallback;
1015+
opts = null;
1016+
} else if (opts != null) {
1017+
start = opts.start ?? 0;
1018+
end = opts.end;
1019+
fd = opts.fd;
1020+
}
1021+
1022+
// TODO (fix): Validate path, fd, start, end
1023+
1024+
if (fd) {
1025+
_sendFile(fd, dst, start, end, callback);
1026+
} else {
1027+
fs.open(path, (err, fd) => {
1028+
if (err) {
1029+
callback(err);
1030+
} else {
1031+
_sendFile(fd, dst, start, end, callback);
1032+
}
1033+
});
1034+
}
1035+
}
1036+
1037+
function _sendFile(fd, dst, start, end, callback) {
1038+
let pos = start;
1039+
let drain = null;
1040+
1041+
const next = (err, bytesRead, buf) => {
1042+
pos += bytesRead;
1043+
1044+
if (err != null && err.code === 'EAGAIN') {
1045+
setImmediate(() => read(buf));
1046+
return;
1047+
} else if (err) {
1048+
// Fallthrough
1049+
} else if (bytesRead === 0) {
1050+
// Fallthrough
1051+
} else if (dst.write(bytesRead < buf.byteLength ? buf.subarray(0, bytesRead) : buf)) {
1052+
// TODO (perf): Use FastBuffer instead of subarray.
1053+
// TODO (perf): Is there a way we can re-use the buffer once we
1054+
// know for sure it's been flushed through the socket?
1055+
// TODO (perf): Avoid creating subarray and use offset parameter in fs.read
1056+
// instead.
1057+
read(bytesRead < buf.byteLength < buf.subarray(bytesRead), null);
1058+
return;
1059+
} else if (dst.destroyed) {
1060+
// Fallthrough
1061+
} else if (drain == null) {
1062+
drain = read;
1063+
dst.on('drain', drain);
1064+
return;
1065+
}
1066+
1067+
if (drain) {
1068+
dst.off('drain', drain);
1069+
}
1070+
1071+
fs.close(fd, er => {
1072+
if (err && er) {
1073+
err = new AggregateError([err, er]);
1074+
}
1075+
callback(err, pos);
1076+
});
1077+
};
1078+
1079+
// TODO (perf): Avoid closure...
1080+
const read = (buf) => {
1081+
const n = end ? Math.min(end - pos, Buffer.poolSize) : Buffer.poolSize;
1082+
if (n <= 0) {
1083+
next(null, 0, null)
1084+
} else {
1085+
fs.read(fd, buf ?? Buffer.allocUnsafe(n), 0, n, pos, next);
1086+
}
1087+
};
1088+
1089+
read(null);
1090+
}
1091+
10081092
OutgoingMessage.prototype.addTrailers = function addTrailers(headers) {
10091093
this._trailer = '';
10101094
const keys = ObjectKeys(headers);

0 commit comments

Comments
 (0)