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包中。