Koajs成长之路--何时设置content-type?

1,911 阅读20分钟

Koajs作为一个优秀的nodejs web框架,会自动根据我们写入的内容进行判断设定合适的content-type。那么是否我们就不用关心究竟返回的content-type为什么呢?如果我们不关心,那么返回到浏览器就可能会是一塌糊涂。

举个简单的例子:

const Koa = require("koa");
const app = new Koa();
const koaRouter = require("koa-router");
const router = new koaRouter();
router.get("/", (ctx) => {     
    ctx.body = "使用到了路由组件";     
    let url = ctx.url;
    let request = ctx.request;
    let response = ctx.response;
    ctx.body = ctx.request.url == ctx.url;
    ctx.body = ctx.query;
    // 请注意 我们多次设定了ctx.body的值 也就是按照我们的理解,koa会根据我们写入的内容设定具体的type 但究竟是不是这样呢     
    //ctx.type = "text/plain";
    ctx.body = ctx.querystring;
    console.log(ctx.querystring);
 });
router.post("/", (ctx) => {     ctx.body = "这是一个post请求";})
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);

通过查看输出,我们看到输出到浏览器断的content-type竟然是application/json,我们最后设定的输入内容为字符串,也就是这句代码 ctx.body=ctx.querystring; 但为什么输出的不是我们想到的结果呢?

我们打开koajs的源码来一探究竟,还是那句老话,要想深入的学习koa或者说js,看源码是必经之路。

源码分析:

打开koajs源码中response.js,查找对type赋值的地方,

  set body(val) {
    const original = this._body;
    this._body = val;
    // no content
    if (null == val) {
      if (!statuses.empty[this.status]) this.status = 204;
      if (val === null) this._explicitNullBody = true;
      this.remove('Content-Type');
      this.remove('Content-Length');
      this.remove('Transfer-Encoding');
      return;    }
    // set the status
    if (!this._explicitStatus) this.status = 200;
    // set the content-type only if not yet set
    const setType = !this.has('Content-Type');
    // string
    if ('string' === typeof val) {
      if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
      this.length = Buffer.byteLength(val);      return;    } 
   // buffer 
   if (Buffer.isBuffer(val)) {
    if (setType) this.type = 'bin';      this.length = val.length;      return;    }    // stream    if (val instanceof Stream) {      onFinish(this.res, destroy.bind(null, val));      if (original != val) {
        val.once('error', err => this.ctx.onerror(err));
        // overwriting
        if (null != original) this.remove('Content-Length');
      }
      if (setType) this.type = 'bin';      return;    }
    // json
    this.remove('Content-Length');
    this.type = 'json';  },

我们看到在body的set方法中,依据不同的数据类型对type进行了不同的设置,但还有一个重要的点,就是

    // set the content-type only if not yet set
    const setType = !this.has('Content-Type');

只有在type为空的时候才会进行设置,也就是说只会自动的设置一次,这是考点,务必牢记。

现在我们来看下,什么情况下会设置什么类型?

至上而下,我们来逐句分析下源码。

const original=this._body; 获取当前上下文中body的初始值

this._body=val; 将当前的参数赋值给_body变量

if(null==val){
if(!statuses.empty[this.status])this.status=204; 如果当前状态码不为空 那么设置成204。多说一句
204 状态码是什么呢,就是告知浏览器此次未返回任何内容,浏览器不需要执行任何操作,其实浏览器也确实未执行任何操作,
甚至连刷新都没有执行
if(val===null) this._explicitNullBody=true; 显式的设置空body属性为true
this.remove("Content-Type"); // 移除content-type 返回
this.remove("Content-Lenfth"); 移除content-length返回
this.remove("Transfer-Encoding"); 移除transfer-encoding 返回
return;
}

最终效果就是这样,响应的属性会少。

if(!this._explicitStatus) this.status=200; 只有内容为空时,设置为了false,且status设置成了204,其他情况下均为200

const  setType=!this.has("Content-Type") ; 判断是否已经存在Content-Type属性,这个也是为了后面只会更新一次type做准备

if("string"==typeof val){
if(setType) this.type = /^\s*</.test(val) ? 'html' : 'text'; 如果未设置Content-TYpe,

且输入的值为字符串 如果字符串包含</  那么设置为html,否则设置为text
this.length=Buffer.byteLength(val);
return;
}

如果输入的数据为字符串,那么只可能设置成为html或text。

if(Buffer.isBuffer(val)){
if(setType)this.type="bin";
this.length=val.length;
return;
}

如果输入的数据为Buffer,那么type为bin,内容长度为buffer的长度。

    if (val instanceof Stream) {
      onFinish(this.res, destroy.bind(null, val));
      if (original != val) {
        val.once('error', err => this.ctx.onerror(err));
        // overwriting
        if (null != original) this.remove('Content-Length');      }
     如果为stream类型,那么设置的type为bin
      if (setType) this.type = 'bin';      return;    }

this.remove("Content-Length");

this.type="json";

如果不满足以上所有条件,那么移除ContentLength,且type设置成为json。

总结:

⒈如果为字符串,那么会自动设置的类型为text或html;如果为stream或buffer,那么自动设置的类型为bin;如果均不满足,那么会设置为json

2.仅在未设置Content-Type的时候,会自动设置,也就是说仅会在首次Content-Type为赋值时自动设置。

3.如果设置值为true、false,那么会设置为json;如果想输出一个包含了html片段的字符串,那么只能手工设置为text。而stream的使用估计就是在下载文件的时候了。

4.几个有用的属性:Transfer-Encoding,

5、如果输入的数据为null,那么会返回状态码204.

6.我们设置type为json,输出到浏览器端的可不是单纯的json,而是application/json。映射关系在mime-type的npm包中。