前言
又是一周過(guò)去了,常規(guī)學(xué)習(xí)不能斷!但是選擇什么主題呢?一時(shí)間不知道選什么好,于是又想起簡(jiǎn)單的 koajs 非常愉快的就選擇他了 https://koajs.com/,了解一下?
他是個(gè)什么東西呢?
Koa 是一個(gè)新的 web 框架,由 Express 幕后的原班人馬打造, 致力于成為 web 應(yīng)用和 API 開發(fā)領(lǐng)域中的一個(gè)更小、更富有表現(xiàn)力、更健壯的基石。 通過(guò)利用 async 函數(shù),Koa 幫你丟棄回調(diào)函數(shù),并有力地增強(qiáng)錯(cuò)誤處理。 Koa 并沒(méi)有捆綁任何中間件, 而是提供了一套優(yōu)雅的方法,幫助您快速而愉快地編寫服務(wù)端應(yīng)用程序。
hello world
首先新建一個(gè) node 項(xiàng)目,其實(shí)很簡(jiǎn)單,只需要一個(gè) 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"
}
}
然后執(zhí)行
npm i koa
代碼 index.js 文件,新建一個(gè) koa 實(shí)例,使用 app.use 寫一個(gè) async 方法,設(shè)置 ctx.body 的值就可以了。最后使用 app.listen 啟動(dòng)。
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
這樣的話,一個(gè) web 服務(wù)器就搭建好了,訪問(wèn) http://localhost:3000/ 就會(huì)得到 hello world 返回結(jié)果了。你可以嘗試更改字段從而得到不同的返回結(jié)果。
源碼解析
koa 的源碼只有四個(gè)文件,不包含其他引用的話
.
├── History.md
├── LICENSE
├── Readme.md
├── lib
│ ├── application.js
│ ├── context.js
│ ├── request.js
│ └── response.js
└── package.json
主入口可以在 package.json 的 main 中得到,是 application.js,暫時(shí)先知道 middleware 是中間接,通常一個(gè)請(qǐng)求過(guò)來(lái)就會(huì)依次執(zhí)行中間件的方法。
構(gòu)造函數(shù)
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 其實(shí)就是添加一個(gè)中間件,我們通常使用 async 的函數(shù),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 創(chuàng)建一個(gè)服務(wù)器,監(jiān)聽 3000 端口,http.createServer 是 node 的服務(wù)器。
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback 是提供一個(gè)函數(shù),所有請(qǐng)求都會(huì)走到這個(gè)函數(shù)里面進(jìn)行處理。每次請(qǐng)求過(guò)來(lái)都會(huì)調(diào)用這個(gè)函數(shù),所以,我們可以看到,每次請(qǐng)求都會(huì)創(chuàng)建一個(gè) ctx 的對(duì)象。
compose 的作用就是將所有的中間件整合成一個(gè)函數(shù),使用 next 函數(shù)繼續(xù)調(diào)用下一個(gè)函數(shù)。
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 對(duì)象,這里 this.request 將會(huì)把原生的 request 參數(shù)進(jìn)行解析,方便我們進(jìn)行相關(guān)參數(shù)獲取。
/**
* 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 ** 來(lái)獲取 http://localhost:3000?key=value,為什么可以使用 ctx.query 又可以獲取參數(shù)呢,這個(gè)要靠 Object.create 的本事了,它相當(dāng)于創(chuàng)造了一個(gè)對(duì)象,繼承了原來(lái)的對(duì)象,而 this.request 有 query 的參數(shù),而最為重要的是 this.context = Object.create(context); context 委托(使用了 Delegator)了這些 request 的相關(guān)屬性和方法?!镜谝淮误w會(huì)到 js 委托,以前知識(shí)聽說(shuō)不知道是啥】
/**
* Request delegation.
*/
delegate(proto, 'request')
.access('method')
.access('query')
.access('path')
.access('url')
....... // 省略
handleRequest 請(qǐng)求處理,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);
}
調(diào)用完中間件以后,就執(zhí)行 handleResponse 將數(shù)據(jù)返回,返回?cái)?shù)據(jù)也就是將 ctx.body 拿出來(lái),使用 response.end 返回?cái)?shù)據(jù),返回時(shí),會(huì)對(duì)數(shù)據(jù)進(jìn)行處理,在最后面可以體會(huì)到~
/**
* 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);
}
到這里,基本的請(qǐng)求已經(jīng)清楚了~~
End
再來(lái)看一眼最簡(jiǎn)單的 http server 代碼,對(duì)比一下,比 koa 代碼的 hello world 相比并沒(méi)有多復(fù)雜
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Hello World!');
res.end();
}).listen(8080);
但是,獲取參數(shù),使用路由等等插件,koa 生態(tài)做了很多,非常方便,快來(lái)體驗(yàn)吧!