当前位置: 首页 > 工具软件 > Response JS > 使用案例 >

express之response.js

仲孙经赋
2023-12-01
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
}

 

 类似资料: