Koa原理和封装
相关文章
最基础
实现一个简单的koa2框架
实现一个简版koa
koa实践及其手撸
Koa源码只有4个js文件
- application.js:简单封装http.createServer()并整合context.js
- context.js:代理并整合request.js和response.js
- request.js:基于原生req封装的更好用
- response.js:基于原生res封装的更好用
如果我们要封装一个Koa,
需要实现use加载中间件,
next下一个中间件,并且是环形的,
中间件是promise的
ctx=>对应常用的是 body(可读写)/ url(只读)/ method(只读)
// request.js
const request = {
get url() {
return this.req.url;
},
set url(val) {
this.req.url = val;
}
};
module.exports = request;
// response.js
const response = {
get body() {
return this._body;
},
set body(data) {
this._body = data;
},
get status() {
return this.res.statusCode;
},
set status(statusCode) {
if (typeof statusCode !== 'number') {
throw new Error('statusCode 必须为一个数字');
}
this.res.statusCode = statusCode;
}
};
module.exports = response;
// context.js
const context = {
get url() {
return this.request.url;
},
set url(val) {
this.request.url = val;
},
get body() {
return this.response.body;
},
set body(data) {
this.response.body = data;
},
get status() {
return this.response.statusCode;
},
set status(statusCode) {
if (typeof statusCode !== 'number') {
throw new Error('statusCode 必须为一个数字');
}
this.response.statusCode = statusCode;
}
};
module.exports = context;
const Emitter = require('events');
const http = require('http');
// 引入 context request, response 模块
const context = require('./context');
const request = require('./request');
const response = require('./response');
class Application extends Emitter {
/* 构造函数 */
constructor() {
super();
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
// 保存所有的中间件函数
this.middlewares = [];
}
// 开启 http server 并且传入参数 callback
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
use(fn) {
// this.callbackFunc = fn;
// 把所有的中间件函数存放到数组里面去
this.middlewares.push(fn);
return this;
}
callback() {
return (req, res) => {
// 创建ctx
const ctx = this.createContext(req, res);
// 响应内容
const response = () => this.responseBody(ctx);
// 响应时 调用error函数
const onerror = (err) => this.onerror(err, ctx);
//调用 compose 函数,把所有的函数合并
const fn = this.compose();
return fn(ctx).then(response).catch(onerror);
}
}
/**
监听失败,监听的是上面的catch
*/
onerror(err) {
if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
if (404 == err.status || err.expose) return;
if (this.silent) return;
const msg = err.stack || err.toString();
console.error();
console.error(msg.replace(/^/gm, ' '));
console.error();
}
/*
构造ctx
@param {Object} req实列
@param {Object} res 实列
@return {Object} ctx实列
*/
createContext(req, res) {
// 每个实列都要创建一个ctx对象
const ctx = Object.create(this.context);
// 把request和response对象挂载到ctx上去
ctx.request = Object.create(this.request);
ctx.response = Object.create(this.response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
/*
响应消息
@param {Object} ctx 实列
*/
responseBody(ctx) {
const content = ctx.body;
if (typeof content === 'string') {
ctx.res.setHeader('Content-Type', 'text/pain;charset=utf-8')
ctx.res.end(content);
} else if (typeof content === 'object') {
ctx.res.setHeader('Content-Type', 'text/json;charset=utf-8')
ctx.res.end(JSON.stringify(content));
}
}
/*
把传进来的所有的中间件函数合并为一个中间件
@return {function}
*/
compose(){
let middlewares = this.middlewares
return function(ctx){
return dispatch(0)
function dispatch(i){
let fn = middlewares[i]
if(!fn){
return Promise.resolve()
}
return Promise.resolve(fn(ctx, function next(){
return dispatch(i+1)
}))
}
}
}
}
module.exports = Application;
// 使用
const testKoa = require('./application');
const app = new testKoa();
app.use((ctx) => {
str += 'hello world'; // 没有声明该变量, 所以直接拼接字符串会报错
ctx.body = str;
});
app.on('error', (err, ctx) => { // 捕获异常记录错误日志
console.log(err);
});
app.listen(3000, () => {
console.log('listening on 3000');
});
优化
如果有一个中间件写了两个next,会执行两次,需要通过判断next的总执行次数和中间件的长度,如果不一样,就要报错
环形【洋葱】有什么好处
上面的洋葱圈可能没看懂,上一个简易版的
var arr = [function(next){
console.log(1)
next()
console.log(2)
},function(next){
console.log(3)
next()
console.log(4)
}]
var i = 0;
function init(){
arr[i](function(){
i++
if(arr[i]){
init()
}
})
}
init()
// 1342
为什么是1342
上面的代码打个断点就知道了
// 这样应该看得懂吧
function(){
console.log(1)
var next = function(){
console.log(3)
var next = ...
console.log(4)
}
next()
console.log(2)
}
在以前不是express设计的框架,整个请求到响应结束是链结构的,一个修改响应的插件就需要放到最后面,但是有个环形的设计,只要把要修改响应的代码写到next执行后就行了,对于开发者也是,获取请求的数据,修改请求的数据,next,查数据库,响应body
文件访问中间件
module.exports = (dirPath = "./public") => {
return async (ctx, next) => {
if (ctx.url.indexOf("/public") === 0) {
// public开头 读取文件
const url = path.resolve(__dirname, dirPath);
const fileBaseName = path.basename(url);
const filepath = url + ctx.url.replace("/public", "");
console.log(filepath);
// console.log(ctx.url,url, filepath, fileBaseName)
try {
stats = fs.statSync(filepath);
if (stats.isDirectory()) {
const dir = fs.readdirSync(filepath);
const ret = ['<div style="padding-left:20px">'];
dir.forEach(filename => {
console.log(filename);
// 简单认为不带小数点的格式,就是文件夹,实际应该用statSync
if (filename.indexOf(".") > -1) {
ret.push(
`<p><a style="color:black" href="${
ctx.url
}/${filename}">${filename}</a></p>`
);
} else {
// 文件
ret.push(
`<p><a href="${ctx.url}/${filename}">${filename}</a></p>`
);
}
});
ret.push("</div>");
ctx.body = ret.join("");
} else {
console.log("文件");
const content = fs.readFileSync(filepath);
ctx.body = content;
}
} catch (e) {
// 报错了 文件不存在
ctx.body = "404, not found";
}
} else {
// 否则不是静态资源,直接去下一个中间件
await next();
}
}
}
// 使用
const static = require('./static')
app.use(static(__dirname + '/public'));
路由中间件
class Router {
constructor() {
this.stack = [];
}
// 每次定义一个路由,都注册一次
register(path, methods, middleware) {
let route = { path, methods, middleware }
this.stack.push(route);
}
// 现在只支持get和post,其他的同理
get(path, middleware) {
this.register(path, 'get', middleware);
}
post(path, middleware) {
this.register(path, 'post', middleware);
}
//调用
routes() {
let stock = this.stack;
return async function (ctx, next) {
let currentPath = ctx.url;
let route;
for (let i = 0; i < stock.length; i++) {
let item = stock[i];
if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
// 判断path和method
route = item.middleware; break;
}
}
if (typeof route === 'function') {
route(ctx, next);
return;
}
await next();
};
}
}
module.exports = Router;
// 使用
const Koa = require('Koa')
const Router = require('./router')
const app = new Koa()
const router = new Router();
router.get('/index', async ctx => { ctx.body = 'index page'; });
router.get('/post', async ctx => { ctx.body = 'post page'; });
router.get('/list', async ctx => { ctx.body = 'list page'; });
router.post('/index', async ctx => { ctx.body = 'post page'; });
// 路由实例输出父中间件
app.use(router.routes());
下一篇mongodb插件mongoose的使用
Koa原理和封装的更多相关文章
- jsonp原理,封装,应用(vue项目)
jsonp原理 JSON是一种轻量级的数据传输格式. JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 ...
- Ajax原理与封装详解
Ajax大家每天都在用,jquery库对Ajax的封装也很完善.很好用,下面我们看一下他的内部原理,并手动封装一个自己的Ajax库. 更多有关ajax封装及数据处理,请参看上海尚学堂<Ajax中 ...
- Altium designer软件如何设计原理图库封装图库以及交互式布局
欢迎大家关注http://www.raymontec.com(个人专博) Altium Designer学习—认识界面以及PCB设计整体要求 http://www.raymontec.com/alti ...
- 深入springboot原理——动手封装一个starter
从上一篇文章<深入springboot原理——一步步分析springboot启动机制(starter机制)> 我们已经知道springboot的起步依赖与自动配置的机制.spring-bo ...
- Python基础(17)_面向对象程序设计(抽象类、继承原理、封装、多态,绑定方法)
一.抽象类 抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化 1.在python中实现抽象类 import abc #利用abc模块实现抽象类 class All_file(metacl ...
- Socket编程实践(11) --epoll原理与封装
常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(Process Per Connection,PPC), TPC(Thread Per Connection)模型,以及 se ...
- python3 uper(),继承实现原理,封装
抽象类:本身不能被实例化,也不应该不实例化,它的作用就定义标准,并不用具体实现 import abc class Parent(metaclass=abc.ABCMeta): x=1 @abc.abs ...
- ajax原理及封装
一:AJAX 简介 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术,通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新. AJAX = 异步 JavaScri ...
- [妙味Ajax]第一课:原理和封装
知识点总结: ajax是异步的javascrip和xml,用异步的形式去操作xml 访问的是服务端,即https://127.0.0.1/ 或者 https://localhost 1.创建一个aja ...
随机推荐
- CSS - 背景半透明
就一句话 background: rgba(0, 0, 0, .2); body { background-color: pink; } div { width: 200px; height: 200 ...
- ios APP进程杀死之后和APP在后台接收到推送点击跳转到任意界面处理
https://www.jianshu.com/p/ce0dc53eb627 https://www.cnblogs.com/er-dai-ma-nong/p/5584724.html github: ...
- rc
1,协同过滤. 2,协方差:用来衡量,他们的变化趋势是不是一致的. 3,皮尔逊相关系数:-1,负相关.1:正相关. 4,用皮尔逊相关系数来算相关性是最多的.
- unittest中的parameterized参数化
一.安装插件 pip install parameterized 二.有默认参数情况与没有默认参数情况---1 注意:这种写法,只能给单个用例进行参数化,不能给多个用例使用,要每个用例都进行参数化. ...
- 十八 OGNL特殊符号的作用,#,%,$
主要有哪些字符? #:获取Context的数据,构建map %: 强制解析OGNL,强制不解析OGNL $ : 在配置文件中(xml,属性文件(国际化))使用OGNL #的用法: <body&g ...
- LeetCode 24. Swap Nodes in Pairs(交换链表中每两个相邻节点)
题意:交换链表中每两个相邻节点,不能修改节点的val值. 分析:递归.如果以第三个结点为头结点的链表已经两两交换完毕(这一步递归实现---swapPairs(head -> next -> ...
- nginx 书籍
1.<实战nginx> 2.<深入理解nginx> 3.nginx开发从入门到精通 http://tengine.taobao.org/book/ 4.Nginx源码学习,配置 ...
- 【PAT甲级】1013 Battle Over Cities (25 分)(并查集,简单联通图)
题意: 输入三个整数N,M,K(N<=1000,第四个数据1e5<=M<=1e6).有1~N个城市,M条高速公路,K次询问,每次询问输入一个被敌军占领的城市,所有和该城市相连的高速公 ...
- FF获6亿美元投资九城或许比贾跃亭更着急
互联网企业第九城市(以下简称"九城")确认,已透过旗下子公司与总部位于美国加州的法拉第未来公司签定协议,双方共同建立合资公司,在中国制造.营销及运营电动汽车.根据合资公司协议条款, ...
- Windows服务器权限分析
一.Windows常见用户 二.Windows常见用组 2.1Windows常见组 2.2Windows2003常见组 三.Windows目录权限 四.Windows2003默认权限 五.不同环境下的 ...