前言
又是一周过去了,常规学习不能断!但是选择什么主题呢?一时间不知道选什么好,于是又想起简单的 koajs 非常愉快的就选择他了 https://koajs.com/,了解一下?
他是个什么东西呢?
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
hello world
首先新建一个 node 项目,其实很简单,只需要一个 package.json 文件,
{
"name": "koa-hello",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.7.0"
}
}
然后执行
npm i koa
代码 index.js 文件,新建一个 koa 实例,使用 app.use 写一个 async 方法,设置 ctx.body 的值就可以了。最后使用 app.listen 启动。
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
这样的话,一个 web 服务器就搭建好了,访问 http://localhost:3000/ 就会得到 hello world 返回结果了。你可以尝试更改字段从而得到不同的返回结果。
源码解析
koa 的源码只有四个文件,不包含其他引用的话
.
├── History.md
├── LICENSE
├── Readme.md
├── lib
│ ├── application.js
│ ├── context.js
│ ├── request.js
│ └── response.js
└── package.json
主入口可以在 package.json 的 main 中得到,是 application.js,暂时先知道 middleware 是中间接,通常一个请求过来就会依次执行中间件的方法。
构造函数
module.exports = class Application extends Emitter {
/**
* Initialize a new `Application`.
*
* @api public
*/
constructor() {
super();
this.proxy = false;
this.middleware = [];
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}
}
app.use 其实就是添加一个中间件,我们通常使用 async 的函数,generator 被抛弃了!
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
app.listen 创建一个服务器,监听 3000 端口,http.createServer 是 node 的服务器。
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback 是提供一个函数,所有请求都会走到这个函数里面进行处理。每次请求过来都会调用这个函数,所以,我们可以看到,每次请求都会创建一个 ctx 的对象。
compose 的作用就是将所有的中间件整合成一个函数,使用 next 函数继续调用下一个函数。
callback() {
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
初始化 ctx 对象,这里 this.request 将会把原生的 request 参数进行解析,方便我们进行相关参数获取。
/**
* Initialize a new context.
*
* @api private
*/
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.state = {};
return context;
}
比如我们之后就可以使用
** ctx.query.key ** 来获取 http://localhost:3000?key=value,为什么可以使用 ctx.query 又可以获取参数呢,这个要靠 Object.create 的本事了,它相当于创造了一个对象,继承了原来的对象,而 this.request 有 query 的参数,而最为重要的是 this.context = Object.create(context); context 委托(使用了 Delegator)了这些 request 的相关属性和方法。【第一次体会到 js 委托,以前知识听说不知道是啥】
/**
* Request delegation.
*/
delegate(proto, 'request')
.access('method')
.access('query')
.access('path')
.access('url')
....... // 省略
handleRequest 请求处理,fnMiddleware 就是所有的中间件,
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
调用完中间件以后,就执行 handleResponse 将数据返回,返回数据也就是将 ctx.body 拿出来,使用 response.end 返回数据,返回时,会对数据进行处理,在最后面可以体会到~
/**
* Response helper.
*/
function respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
if (!ctx.writable) return;
const res = ctx.res;
let body = ctx.body;
const code = ctx.status;
// ignore body
if (statuses.empty[code]) {
// strip headers
ctx.body = null;
return res.end();
}
if ('HEAD' == ctx.method) {
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}
// status body
if (null == body) {
if (ctx.req.httpVersionMajor >= 2) {
body = String(code);
} else {
body = ctx.message || String(code);
}
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
// body: json
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
到这里,基本的请求已经清楚了~~
End
再来看一眼最简单的 http server 代码,对比一下,比 koa 代码的 hello world 相比并没有多复杂
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Hello World!');
res.end();
}).listen(8080);
但是,获取参数,使用路由等等插件,koa 生态做了很多,非常方便,快来体验吧!