lib/response.js
//继承了node原生的响应原型
var res = Object.create(http.ServerResponse.prototype)
module.exports = res
//字符集正则表达式
var charsetRegExp = /;\s*charset\s*=/;
//设置响应码
res.status = function status(code) {
this.statusCode = code;
return this;
};
//设置Link响应头
res.links = function(links){
var link = this.get('Link') || '';
if (link) link += ', ';
return this.set('Link', link + Object.keys(links).map(function(rel){
return '<' + links[rel] + '>; rel="' + rel + '"';
}).join(', '));
};
//发送响应,可以发送二进制数据、json对象、字符串
res.send = function send(body) {
//块
var chunk = body;
//编码
var encoding;
//请求
var req = this.req;
var type;
//应用实例
var app = this.app;
//两个参数
if (arguments.length === 2) {
//第一个参数不是数字,第二个参数是数字
if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
deprecate('res.send(body, status): Use res.status(status).send(body) instead');
//第二个参数为响应码,但是这个方式已废弃
this.statusCode = arguments[1];
} else {
//第一个参数是状态码也废弃了,不该用这个函数设置状态码
deprecate('res.send(status, body): Use res.status(status).send(body) instead');
this.statusCode = arguments[0];
chunk = arguments[1];
}
}
// 参数只有一个数字
if (typeof chunk === 'number' && arguments.length === 1) {
// res.send(status) will set status message as text string
if (!this.get('Content-Type')) {
this.type('txt');
}
//将其作为状态码
deprecate('res.send(status): Use res.sendStatus(status) instead');
this.statusCode = chunk;
//发送指定状态码的默认信息
chunk = statuses[chunk]
}
switch (typeof chunk) {
// 字符串类型
case 'string':
if (!this.get('Content-Type')) {
//作为html文本发送
this.type('html');
}
break;
case 'boolean':
case 'number':
case 'object':
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
//Buffer则发送二进制数据
if (!this.get('Content-Type')) {
this.type('bin');
}
} else {
//其他Boolean、number、object都作为json发送
return this.json(chunk);
}
break;
}
// 如果是字符串
if (typeof chunk === 'string') {
encoding = 'utf8';
type = this.get('Content-Type');
if (typeof type === 'string') {
//设置响应头,加入编码格式
this.set('Content-Type', setCharset(type, 'utf-8'));
}
}
//是否生成ETag
var etagFn = app.get('etag fn')
//如果响应头中ETag不存在且etagFn函数存在,则需要生成ETag
var generateETag = !this.get('ETag') && typeof etagFn === 'function'
// 声明响应长度
var len
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// Buffer直接获取长度
len = chunk.length
} else if (!generateETag && chunk.length < 1000) {
// 字符串时,不生成ETag且长度小于1000,直接按照指定编码获取长度
len = Buffer.byteLength(chunk, encoding)
} else {
// 转化为Buffer再获取长度
chunk = Buffer.from(chunk, encoding)
encoding = undefined;
len = chunk.length
}
//设置长度响应头
this.set('Content-Length', len);
}
// 生成ETag
var etag;
if (generateETag && len !== undefined) {
//使用etagFn函数生成ETag响应头的值
if ((etag = etagFn(chunk, encoding))) {
this.set('ETag', etag);
}
}
//如果请求是新鲜的,状态码为304,说明缓存是新鲜的,不用再次发送
if (req.fresh) this.statusCode = 304;
// 移除指定响应头,载荷为空字符串
if (204 === this.statusCode || 304 === this.statusCode) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
this.removeHeader('Transfer-Encoding');
chunk = '';
}
//head方法,不发送任何东西
if (req.method === 'HEAD') {
this.end();
} else {
//其他方法还得发送载荷
this.end(chunk, encoding);
}
return this;
};
//发送json响应,当send boolean、number、object时都会调用它
res.json = function json(obj) {
var val = obj;
//最好不要使用它发送状态码
if (arguments.length === 2) {
if (typeof arguments[1] === 'number') {
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
} else {
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
var app = this.app;
//获取json的格式
var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
//根据格式转化为字符串
var body = stringify(val, replacer, spaces, escape)
//设置请求头
if (!this.get('Content-Type')) {
this.set('Content-Type', 'application/json');
}
//调用send发送
return this.send(body);
};
//作为jsonp发送
res.jsonp = function jsonp(obj) {
var val = obj;
//最好还是不要用来设置响应码
if (arguments.length === 2) {
if (typeof arguments[1] === 'number') {
deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
} else {
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
// 获取express实例设置,即jsonp的格式
var app = this.app;
var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
//json字符串
var body = stringify(val, replacer, spaces, escape)
//获取回调函数,从查询字符串中获取?
var callback = this.req.query[app.get('jsonp callback name')];
//响应头
if (!this.get('Content-Type')) {
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'application/json');
}
if (Array.isArray(callback)) {
callback = callback[0];
}
// jsonp
if (typeof callback === 'string' && callback.length !== 0) {
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'text/javascript');
// restrict callback charset
callback = callback.replace(/[^\[\]\w$.]/g, '');
// replace chars not allowed in JavaScript that are in JSON
body = body
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
}
return this.send(body);
};
//发送指定http状态码,与它的默认消息
res.sendStatus = function sendStatus(statusCode) {
//获取指定状态码的默认消息
var body = statuses[statusCode] || String(statusCode)
//设置状态码
this.statusCode = statusCode;
this.type('txt');
//发送消息
return this.send(body);
};
//发送指定路径的文件
res.sendFile = function sendFile(path, options, callback) {
//回调函数
var done = callback;
//请求对象
var req = this.req;
//响应对象
var res = this;
var next = req.next;
var opts = options || {};
//路径不存在抛出错误
if (!path) {
throw new TypeError('path argument is required to res.sendFile');
}
//选项可以为空
if (typeof options === 'function') {
done = options;
opts = {};
}
//如果选项中没有root根路径且path不是绝对路径,抛出错误
if (!opts.root && !isAbsolute(path)) {
throw new TypeError('path must be absolute or specify root to res.sendFile');
}
//对文件路径编码
var pathname = encodeURI(path);
//获取要发送的文件流
var file = send(req, pathname, opts);
//传输文件,将文件流pipe到res
sendfile(res, file, opts, function (err) {
if (done) return done(err);
if (err && err.code === 'EISDIR') return next();
if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
next(err);
}
});
};
//也是一个发送文件函数,注意f为小写
res.sendfile = function (path, options, callback) {
var done = callback;
var req = this.req;
var res = this;
var next = req.next;
var opts = options || {};
if (typeof options === 'function') {
done = options;
opts = {};
}
//获取文件流
var file = send(req, path, opts);
// 传输文件
sendfile(res, file, opts, function (err) {
if (done) return done(err);
if (err && err.code === 'EISDIR') return next();
if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
next(err);
}
});
};
//已废弃
res.sendfile = deprecate.function(res.sendfile,
'res.sendfile: Use res.sendFile instead');
//将文件作为附件传输,即设置相应的响应头,使浏览器弹出下载框
res.download = function download (path, filename, options, callback) {
//回调函数、文件名、选项,其中选项与sendFile中相同
var done = callback;
var name = filename;
var opts = options || null
if (typeof filename === 'function') {
done = filename;
name = null;
opts = null
} else if (typeof options === 'function') {
done = options
opts = null
}
//要设置的响应头
var headers = {
'Content-Disposition': contentDisposition(name || path)
};
// 与选项中的头信息合并
if (opts && opts.headers) {
var keys = Object.keys(opts.headers)
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
if (key.toLowerCase() !== 'content-disposition') {
headers[key] = opts.headers[key]
}
}
}
opts = Object.create(opts)
opts.headers = headers
//全路径
var fullPath = resolve(path);
//调用sendFile函数
return this.sendFile(fullPath, opts, done)
};
//设置Content-type响应头
res.contentType =
res.type = function contentType(type) {
//如果包含/,如text/plain,则直接设置,否则查找相应MIME类型设置
var ct = type.indexOf('/') === -1
? mime.lookup(type)
: type;
return this.set('Content-Type', ct);
};
//参数为一个对象,如果对象键对应的MIME类型可接受,则执行对应的回调
res.format = function(obj){
var req = this.req;
var next = req.next;
//默认回调?
var fn = obj.default;
if (fn) delete obj.default;
var keys = Object.keys(obj);
//获取可接受的最佳匹配key
var key = keys.length > 0
? req.accepts(keys)
: false;
this.vary("Accept");
if (key) {
//设置响应头
this.set('Content-Type', normalizeType(key).value);
//执行回调
obj[key](req, this, next);
} else if (fn) {
//不存在时执行默认回调
fn();
} else {
//默认回调也不存在
var err = new Error('Not Acceptable');
err.status = err.statusCode = 406;
err.types = normalizeTypes(keys).map(function(o){ return o.value });
next(err);
}
return this;
};
//设置下载响应头,浏览器根据它弹出下载框
res.attachment = function attachment(filename) {
if (filename) {
this.type(extname(filename));
}
this.set('Content-Disposition', contentDisposition(filename));
return this;
};
//追加指定响应头的值
res.append = function append(field, val) {
//获取之前的值
var prev = this.get(field);
var value = val;
//如果存在
if (prev) {
//如果之前的值是数组,直接concat,如果之前的值不是数组而要添加的值是数组,也是concat,否则将他俩弄成一个数组
//有重复值怎么办,concat能去重吗
value = Array.isArray(prev) ? prev.concat(val)
: Array.isArray(val) ? [prev].concat(val)
: [prev, val];
}
//重新设置
return this.set(field, value);
};
//set、header方法都设置响应头
res.set =
res.header = function header(field, val) {
if (arguments.length === 2) {
var value = Array.isArray(val)
? val.map(String)
: String(val);
//content-type不能设置为数组
if (field.toLowerCase() === 'content-type') {
if (Array.isArray(value)) {
throw new TypeError('Content-Type cannot be set to an Array');
}
//设置字符集
if (!charsetRegExp.test(value)) {
var charset = mime.charsets.lookup(value.split(';')[0]);
if (charset) value += '; charset=' + charset.toLowerCase();
}
}
//setHeader好像是原型方法
this.setHeader(field, value);
} else {
//当第一个参数为对象时,设置所有键值对为响应头
for (var key in field) {
this.set(key, field[key]);
}
}
return this;
};
//get方法返回响应头
res.get = function(field){
return this.getHeader(field);
};
//清除指定名称cookie
res.clearCookie = function clearCookie(name, options) {
var opts = merge({ expires: new Date(1), path: '/' }, options);
//设置指定名称cookie为空字符串
return this.cookie(name, '', opts);
};
//设置cookie键值对
res.cookie = function (name, value, options) {
//选项
var opts = merge({}, options);
//秘钥
var secret = this.req.secret;
//是否签名
var signed = opts.signed;
if (signed && !secret) {
throw new Error('cookieParser("secret") required for signed cookies');
}
//value为对象时,转化为j:开头的json字符串
var val = typeof value === 'object'
? 'j:' + JSON.stringify(value)
: String(value);
//如果签名,转化为s:开头的签名字符串
if (signed) {
val = 's:' + sign(val, secret);
}
//如果设置了设置最大生存毫秒
if ('maxAge' in opts) {
opts.expires = new Date(Date.now() + opts.maxAge);
opts.maxAge /= 1000;
}
//cookie路径
if (opts.path == null) {
opts.path = '/';
}
//对Set-Cookie响应头追加信息,如果指定名称cookie已存在怎么办?append中没有处理重复值的情况
this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
return this;
};
//设置Location响应头
res.location = function location(url) {
var loc = url;
//back代表了重定向到Referrer,即上次请求的url,相当于回退
if (url === 'back') {
loc = this.req.get('Referrer') || '/';
}
//设置url编码后的响应头
return this.set('Location', encodeUrl(loc));
};
//重定向到指定url
res.redirect = function redirect(url) {
var address = url;
var body;
var status = 302;
//第一个参数可以指定状态码,第二个参数为url
if (arguments.length === 2) {
if (typeof arguments[0] === 'number') {
status = arguments[0];
address = arguments[1];
} else {
deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
status = arguments[1];
}
}
//设置location响应头,告诉浏览器重定向url
address = this.location(address).get('Location');
//格式化响应体,如果MIME类型可接受,则执行回调
this.format({
//纯文本
text: function(){
body = statuses[status] + '. Redirecting to ' + address
},
//html
html: function(){
var u = escapeHtml(address);
body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
},
//默认
default: function(){
body = '';
}
});
// 设置响应码,长度
this.statusCode = status;
this.set('Content-Length', Buffer.byteLength(body));
//head请求不发送响应体
if (this.req.method === 'HEAD') {
this.end();
} else {
this.end(body);
}
};
//添加一个字段名到vary,vary是一个http头信息
res.vary = function(field){
if (!field || (Array.isArray(field) && !field.length)) {
deprecate('res.vary(): Provide a field name');
return this;
}
vary(this, field);
return this;
};
//渲染指定视图,调用了app.render,最后调用了View.render
//当给出回调函数时,不会立即呈现响应,而是将渲染得到的文本传输给回调函数
res.render = function render(view, options, callback) {
//express实例
var app = this.req.app;
//回调
var done = callback;
var opts = options || {};
var req = this.req;
//绑定作用域到回调函数
var self = this;
if (typeof options === 'function') {
done = options;
opts = {};
}
opts._locals = self.locals;
// 默认回调,渲染结束,发送文本
done = done || function (err, str) {
if (err) return req.next(err);
self.send(str);
};
// 最后还是调用express实例的render
app.render(view, opts, done);
};
//发送可读文件流到可写响应流,工具函数,sendFile、sendfile、download都调用了它
function sendfile(res, file, options, callback) {
var done = false;
var streaming;
//请求取消监听函数
function onaborted() {
//如果已经完成,直接返回
if (done) return;
done = true;
var err = new Error('Request aborted');
err.code = 'ECONNABORTED';
callback(err);
}
//路径为目录的回调函数
function ondirectory() {
if (done) return;
done = true;
var err = new Error('EISDIR, read');
err.code = 'EISDIR';
callback(err);
}
// 发生错误回调函数
function onerror(err) {
if (done) return;
done = true;
callback(err);
}
//结束回调
function onend() {
if (done) return;
done = true;
callback();
}
//路径指向文件时
function onfile() {
streaming = false;
}
// 完成时,完成、结束有什么区别
function onfinish(err) {
if (err && err.code === 'ECONNRESET') return onaborted();
if (err) return onerror(err);
if (done) return;
setImmediate(function () {
if (streaming !== false && !done) {
onaborted();
return;
}
if (done) return;
done = true;
callback();
});
}
//流动时
function onstream() {
streaming = true;
}
//设置监听器
file.on('directory', ondirectory);
file.on('end', onend);
file.on('error', onerror);
file.on('file', onfile);
file.on('stream', onstream);
//设置完成监听器
onFinished(res, onfinish);
//如果选项包含头信息,如download中设置Content-Disposition
if (options.headers) {
//headers事件
file.on('headers', function headers(res) {
//设置所有头信息到响应对象
var obj = options.headers;
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
res.setHeader(k, obj[k]);
}
});
}
// 连接可读流到可写流
file.pipe(res);
}
//v8优化后的json字符串化的函数,能够转义触发html嗅探的字符
function stringify (value, replacer, spaces, escape) {
var json = replacer || spaces
? JSON.stringify(value, replacer, spaces)
: JSON.stringify(value);
//如果转义
if (escape) {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
return '\\u003c'
case 0x3e:
return '\\u003e'
case 0x26:
return '\\u0026'
default:
return c
}
})
}
return json
}