本文 Koa 版本爲 2.7.0, 版本不同源碼可能會有變更
源碼倉庫 已經開放,若是本文對你有幫助,歡迎 star ~~html
代碼目錄node
my-applicationgit
const {createServer} = require('http');
module.exports = class Application {
constructor() {
// 初始化中間件數組, 全部中間件函數都會添加到當前數組中
this.middleware = [];
}
// 使用中間件方法
use(fn) {
// 將全部中間件函數添加到中間件數組中
this.middleware.push(fn);
}
// 監聽端口號方法
listen(...args) {
// 使用nodejs的http模塊監聽端口號
const server = createServer((req, res) => {
/*
處理請求的回調函數,在這裏執行了全部中間件函數
req 是 node 原生的 request 對象
res 是 node 原生的 response 對象
*/
this.middleware.forEach((fn) => fn(req, res));
})
server.listen(...args);
}
}
複製代碼
index.jsgithub
// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 建立實例對象
const app = new MyKoa();
// 使用中間件
app.use((req, res) => {
console.log('中間件函數執行了~~~111');
})
app.use((req, res) => {
console.log('中間件函數執行了~~~222');
res.end('hello myKoa');
})
// 監聽端口號
app.listen(3000, err => {
if (!err) console.log('服務器啓動成功了');
else console.log(err);
})
複製代碼
運行入口文件 index.js
後,經過瀏覽器輸入網址訪問 http://localhost:3000/
, 就能夠看到結果了~~web
神奇吧!一個最簡單的服務器模型就搭建完了。固然咱們這個極簡服務器還存在不少問題,接下來讓咱們一一解決npm
createServer
的回調函數,封裝成一個callback
方法(可複用)// 監聽端口號方法
listen(...args) {
// 使用nodejs的http模塊監聽端口號
const server = createServer(this.callback());
server.listen(...args);
}
callback() {
const handleRequest = (req, res) => {
this.middleware.forEach((fn) => fn(req, res));
}
return handleRequest;
}
複製代碼
compose
函數實現next
方法/**
* 負責執行中間件函數的函數
* @param middleware 中間件數組
* @return {function}
*/
function compose(middleware) {
// compose方法返回值是一個函數,這個函數返回值是一個promise對象
// 當前函數就是調度
return (req, res) => {
// 默認調用一次,爲了執行第一個中間件函數
return dispatch(0);
function dispatch(i) {
// 提取中間件數組的函數fn
let fn = middleware[i];
// 若是最後一箇中間件也調用了next方法,直接返回一個成功狀態的promise對象
if (!fn) return Promise.resolve();
/*
dispatch.bind(null, i + 1)) 做爲中間件函數調用的第三個參數,其實就是對應的next
舉個栗子:若是 i = 0 那麼 dispatch.bind(null, 1))
--> 也就是若是調用了next方法 實際上就是執行 dispatch(1)
--> 它利用遞歸從新進來取出下一個中間件函數接着執行
fn(req, res, dispatch.bind(null, i + 1))
--> 這也是爲何中間件函數能有三個參數,在調用時咱們傳進來了
*/
return Promise.resolve(fn(req, res, dispatch.bind(null, i + 1)));
}
}
}
複製代碼
compose
函數callback () {
// 執行compose方法返回一個函數
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
// 調用該函數,返回值爲promise對象
// then方法觸發了, 說明全部中間件函數都被調用完成
fn(req, res).then(() => {
// 在這裏就是全部處理的函數的最後階段,能夠容許返回響應了~
});
}
return handleRequest;
}
複製代碼
// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 建立實例對象
const app = new MyKoa();
// 使用中間件
app.use((req, res, next) => {
console.log('中間件函數執行了~~~111');
// 調用next方法,就是調用堆棧中下一個中間件函數
next();
})
app.use((req, res, next) => {
console.log('中間件函數執行了~~~222');
res.end('hello myKoa');
// 最後的next方法沒發調用下一個中間件函數,直接返回Promise.resolve()
next();
})
// 監聽端口號
app.listen(3000, err => {
if (!err) console.log('服務器啓動成功了');
else console.log(err);
})
複製代碼
next
方法,最核心的就是compose
函數,極簡的代碼實現了功能,難以想象!respond
function respond(req, res) {
// 獲取設置的body數據
let body = res.body;
if (typeof body === 'object') {
// 若是是對象,轉化成json數據返回
body = JSON.stringify(body);
res.end(body);
} else {
// 默認其餘數據直接返回
res.end(body);
}
}
複製代碼
callback
中調用callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
// 當中間件函數所有執行完畢時,會觸發then方法,從而執行respond方法返回響應
const handleResponse = () => respond(req, res);
fn(req, res).then(handleResponse);
}
return handleRequest;
}
複製代碼
// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 建立實例對象
const app = new MyKoa();
// 使用中間件
app.use((req, res, next) => {
console.log('中間件函數執行了~~~111');
next();
})
app.use((req, res, next) => {
console.log('中間件函數執行了~~~222');
// 設置響應內容,由框架負責返回響應~
res.body = 'hello myKoa';
})
// 監聽端口號
app.listen(3000, err => {
if (!err) console.log('服務器啓動成功了');
else console.log(err);
})
複製代碼
// 此模塊須要npm下載
const parse = require('parseurl');
const qs = require('querystring');
module.exports = {
/**
* 獲取請求頭信息
*/
get headers() {
return this.req.headers;
},
/**
* 設置請求頭信息
*/
set headers(val) {
this.req.headers = val;
},
/**
* 獲取查詢字符串
*/
get query() {
// 解析查詢字符串參數 --> key1=value1&key2=value2
const querystring = parse(this.req).query;
// 將其解析爲對象返回 --> {key1: value1, key2: value2}
return qs.parse(querystring);
}
}
複製代碼
module.exports = {
/**
* 設置響應頭的信息
*/
set(key, value) {
this.res.setHeader(key, value);
},
/**
* 獲取響應狀態碼
*/
get status() {
return this.res.statusCode;
},
/**
* 設置響應狀態碼
*/
set status(code) {
this.res.statusCode = code;
},
/**
* 獲取響應體信息
*/
get body() {
return this._body;
},
/**
* 設置響應體信息
*/
set body(val) {
// 設置響應體內容
this._body = val;
// 設置響應狀態碼
this.status = 200;
// json
if (typeof val === 'object') {
this.set('Content-Type', 'application/json');
}
},
}
複製代碼
// 此模塊須要npm下載
const delegate = require('delegates');
const proto = module.exports = {};
// 將response對象上的屬性/方法克隆到proto上
delegate(proto, 'response')
.method('set') // 克隆普通方法
.access('status') // 克隆帶有get和set描述符的方法
.access('body')
// 將request對象上的屬性/方法克隆到proto上
delegate(proto, 'request')
.access('query')
.getter('headers') // 克隆帶有get描述符的方法
複製代碼
module.exports = Delegator;
/**
* 初始化一個 delegator.
*/
function Delegator(proto, target) {
// this必須指向Delegator的實例對象
if (!(this instanceof Delegator)) return new Delegator(proto, target);
// 須要克隆的對象
this.proto = proto;
// 被克隆的目標對象
this.target = target;
// 全部普通方法的數組
this.methods = [];
// 全部帶有get描述符的方法數組
this.getters = [];
// 全部帶有set描述符的方法數組
this.setters = [];
}
/**
* 克隆普通方法
*/
Delegator.prototype.method = function(name){
// 須要克隆的對象
var proto = this.proto;
// 被克隆的目標對象
var target = this.target;
// 方法添加到method數組中
this.methods.push(name);
// 給proto添加克隆的屬性
proto[name] = function(){
/*
this指向proto, 也就是ctx
舉個栗子:ctx.response.set.apply(ctx.response, arguments)
arguments對應實參列表,恰好與apply方法傳參一致
執行ctx.set('key', 'value') 實際上至關於執行 response.set('key', 'value')
*/
return this[target][name].apply(this[target], arguments);
};
// 方便鏈式調用
return this;
};
/**
* 克隆帶有get和set描述符的方法.
*/
Delegator.prototype.access = function(name){
return this.getter(name).setter(name);
};
/**
* 克隆帶有get描述符的方法.
*/
Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.getters.push(name);
// 方法能夠爲一個已經存在的對象設置get描述符屬性
proto.__defineGetter__(name, function(){
return this[target][name];
});
return this;
};
/**
* 克隆帶有set描述符的方法.
*/
Delegator.prototype.setter = function(name){
var proto = this.proto;
var target = this.target;
this.setters.push(name);
// 方法能夠爲一個已經存在的對象設置set描述符屬性
proto.__defineSetter__(name, function(val){
return this[target][name] = val;
});
return this;
};
複製代碼
const {createServer} = require('http');
const context = require('./my-context');
const request = require('./my-request');
const response = require('./my-response');
module.exports = class Application {
constructor() {
this.middleware = [];
// Object.create(target) 以target對象爲原型, 建立新對象, 新對象原型有target對象的屬性和方法
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
use(fn) {
this.middleware.push(fn);
}
listen(...args) {
// 使用nodejs的http模塊監聽端口號
const server = createServer(this.callback());
server.listen(...args);
}
callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
// 建立context
const ctx = this.createContext(req, res);
const handleResponse = () => respond(ctx);
fn(ctx).then(handleResponse);
}
return handleRequest;
}
/**
* 建立context 上下文對象的方法
* @param req node原生req對象
* @param res node原生res對象
*/
createContext(req, res) {
/*
凡是req/res,就是node原生對象
凡是request/response,就是自定義對象
這是實現互相掛載引用,從而在任意對象上都能獲取其餘對象的方法
*/
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;
return context;
}
}
// 將原來使用req,res的地方改用ctx
function compose(middleware) {
return (ctx) => {
return dispatch(0);
function dispatch(i) {
let fn = middleware[i];
if (!fn) return Promise.resolve();
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
}
}
}
function respond(ctx) {
let body = ctx.body;
const res = ctx.res;
if (typeof body === 'object') {
body = JSON.stringify(body);
res.end(body);
} else {
res.end(body);
}
}
複製代碼
// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 建立實例對象
const app = new MyKoa();
// 使用中間件
app.use((ctx, next) => {
console.log('中間件函數執行了~~~111');
next();
})
app.use((ctx, next) => {
console.log('中間件函數執行了~~~222');
// 獲取請求頭參數
console.log(ctx.headers);
// 獲取查詢字符串參數
console.log(ctx.query);
// 設置響應頭信息
ctx.set('content-type', 'text/html;charset=utf-8');
// 設置響應內容,由框架負責返回響應~
ctx.body = '<h1>hello myKoa</h1>';
})
// 監聽端口號
app.listen(3000, err => {
if (!err) console.log('服務器啓動成功了');
else console.log(err);
})
複製代碼
到這裏已經寫完了 Koa 主要代碼,有一句古話 - 看萬遍代碼不如寫上一遍。 還等什麼,趕忙寫上一遍吧~ 當你可以寫出來,再去閱讀源碼,你會發現源碼如此簡單~json