相关文章

最基础

实现一个简单的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原理和封装的更多相关文章

  1. jsonp原理,封装,应用(vue项目)

    jsonp原理 JSON是一种轻量级的数据传输格式. JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 ...

  2. Ajax原理与封装详解

    Ajax大家每天都在用,jquery库对Ajax的封装也很完善.很好用,下面我们看一下他的内部原理,并手动封装一个自己的Ajax库. 更多有关ajax封装及数据处理,请参看上海尚学堂<Ajax中 ...

  3. Altium designer软件如何设计原理图库封装图库以及交互式布局

    欢迎大家关注http://www.raymontec.com(个人专博) Altium Designer学习—认识界面以及PCB设计整体要求 http://www.raymontec.com/alti ...

  4. 深入springboot原理——动手封装一个starter

    从上一篇文章<深入springboot原理——一步步分析springboot启动机制(starter机制)> 我们已经知道springboot的起步依赖与自动配置的机制.spring-bo ...

  5. Python基础(17)_面向对象程序设计(抽象类、继承原理、封装、多态,绑定方法)

    一.抽象类 抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化 1.在python中实现抽象类 import abc #利用abc模块实现抽象类 class All_file(metacl ...

  6. Socket编程实践(11) --epoll原理与封装

    常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(Process Per Connection,PPC), TPC(Thread Per Connection)模型,以及 se ...

  7. python3 uper(),继承实现原理,封装

    抽象类:本身不能被实例化,也不应该不实例化,它的作用就定义标准,并不用具体实现 import abc class Parent(metaclass=abc.ABCMeta): x=1 @abc.abs ...

  8. ajax原理及封装

    一:AJAX 简介 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术,通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新. AJAX = 异步 JavaScri ...

  9. [妙味Ajax]第一课:原理和封装

    知识点总结: ajax是异步的javascrip和xml,用异步的形式去操作xml 访问的是服务端,即https://127.0.0.1/ 或者 https://localhost 1.创建一个aja ...

随机推荐

  1. ha-wordy-Write-up

    信息收集 下载地址:点我 bilibili:点我 ➜ ~ nmap -sn 192.168.116.1/24 Starting Nmap 7.80 ( https://nmap.org ) at 20 ...

  2. Swift3.0-字符串和字符

    一.简介 Swift中的字符串和字符与OC中在表示上并无大的区别,考虑到字符串在平时的开发工作中属于频繁使用的类型,重点需要掌握的内容是String各种操作函数的写法. 二.Swift与OC的区别 三 ...

  3. 在IDEA中为SpringBoot配置热部署

    版本:IDEA Community 2019.2.2,Spring Boot  2.1.8.RELEASE 流程:pom文件中添加依赖: <dependency> <groupId& ...

  4. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 表格:为所有表格的单元格添加边框

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  5. java获取指定月份有几个星期x,获取指定月份跨了多少个星期

    例如获取2020年5月一共有多少个星期二,一共跨了多少个星期 public class MainTest { public static void main(String[] args) throws ...

  6. 帆软FineReport报表由于使用HTML显示后无法控制行高

    问题:帆软FineReport报表由于使用HTML显示后无法控制行高. 原因:首先每行的第一个单元格是以HTML显示的,然后,数据库查询的数据集中,sql语句中包含这个代码:'<pre>' ...

  7. nacos集群配置

    一.    环境准备 Nacos 依赖 java环境来运行.如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用: 64 bit OS,支持 Lin ...

  8. java#临时文件目录

    String tmpDir=System.getProperty("java.io.tmpdir");

  9. Rcnn/Faster Rcnn/Faster Rcnn的理解

    基于候选区域的目标检测器 1.  滑动窗口检测器 根据滑动窗口从图像中剪切图像块-->将剪切的图像块warp成固定大小-->cnn网络提取特征-->SVM和regressor进行分类 ...

  10. BZOJ 4166: 月宫的符卡序列

    如果使用回文树,节点 \(u\) 的回文串和 \(fail_u\) 的回文串中心不一样,因为回文树的 \(fail\) 指针指向的是最长回文后缀,没法快速解决异或和 考虑魔改回文树,用马拉车来解决,扩 ...