session 基于cookie ,我的比较喜欢session,可是 koa确实比较轻量javascript
koa须要安装html
let Koa = require('koa');
let app = new Koa();
let path = require('path');
// ctx中还包含了 request response
let fs = require('fs');
app.use( (ctx,next)=> {
// ctx.request上 封装了请求的属性 会被代理到ctx
ctx.set('Content-Type','application/json');
ctx.body = fs.createReadStream(path.resolve(__dirname,'./package.json'));
});
app.listen(3000);
复制代码
同步的时候实际上是同样的,只不过异步会有不一样,express不会等待下一个next的完成而koa会java
koa中间件实现react
let Koa = require('koa');
let app = new Koa();
//next前面要么跟return,要么跟await不然不知道会不会影后后面的异步出现问题
app.use(async (ctx,next)=> {
console.log(1);
await next();
console.log(2);
});
function log(){
return new Promise((resolve,reject)=>{
setTimeout(()=> {
resolve('123');
})
})
}
app.use(async (ctx,next)=> {
console.log(3);
let r = await log();
console.log(r);
next();
console.log(4);
});
app.use( (ctx,next)=> {
console.log(5);
next();
console.log(6);
});
// 当全部中间件执行完后 会将ctx.body中的内容 取出来 res.end()
app.listen(3000);
复制代码
结果就跟同步同样输出135642 对于上述问题在express能不能用await解决呢web
express中间件实现redis
let express = require('express');
app = express();
function log(){
return new Promise((resolve,reject)=>{
setTimeout(()=> {
resolve('123');
})
})
}
app.use(async (req,res,next)=> {
console.log(1);
await next();
console.log(2);
});
app.use(async (req,res,next)=> {
console.log(3);
let r = await log();
console.log(r);
next();
console.log(4);
});
app.use( (req,res,next)=> {
console.log(5);
next();
console.log(6);
});
app.listen(3000);
复制代码
输出132 123 64,由于在执行到第二个next的时候发现须要等待,他就不会等待,会直接执行下一步nextexpress
koa的中间件会在内部处理next将其变成中间件,那么咱们如何让express像koa同样呢?json
function app(){
}
function log(){
return new Promise((resolve,reject)=>{
setTimeout(()=> {
resolve('123');
})
})
}
app.routes = [];
app.use = function(cb){
app.routes.push(cb)
}
app.use( async(next)=> {
console.log(1);
await next();
console.log(2);
})
app.use(async (next)=> {
console.log(3);
let r = await log();
console.log(r);
next();
console.log(4);
})
app.use((next)=> {
console.log(5);
console.log(6);
})
let index = 0;
function next(){
if(index === app.routes.lenth) return;
//在原来内部实现方法执行的时候return
return app.routes[index++](next)
}
next();
复制代码
在原来内部实现方法执行的时候return,第一个函数中若是等待的是promise那么会等待这个promise执行完以后在执行,若是返回的是undefined就会跳过,不会等待下一我的执行完以后在执行数组
利用这个咱们写一个文件上传的例子promise
以前咱们文件上传,看怎么解析请求体,之前咱们解析请求体多是json或者a=b&c=d,此次咱们用表单格式
let Koa = require('koa');
// app是监听函数
let app = new Koa();
let path = require('path');
let fs = require('fs');
app.use(async (ctx,next)=> {
if(ctx.path == '/user' && ctx.method == 'GET'){
ctx.body = ` <form method="POST"> <input name='username' type="text" autoComplete='off'> <input name='password' type="text" autoComplete='off'> <input type="submit"> </form> `
}
await next()
});
function bodyParser(ctx){
return new Promise((resolve,reject)=>{
let buffers = [];
ctx.req.on('data',function(data){
buffers.push(data);
})
ctx.req.on('end',function(){
resolve(Buffer.concat(buffers).toString());
})
})
}
app.use(async (ctx,next)=> {
if(ctx.path == '/user' && ctx.method == 'POST'){
ctx.body = await bodyParser(ctx);
}
next()
});
app.listen(3000);
复制代码
咱们看处处理data用的buffer,koa自己对这些并无封装,固然咱们一样可使用中间件
...
let bodyParser = require('koa-bodyparser');
app.use(bodyParser()); // 会把请求体的结果放到 req.request.body
...
app.use(async (ctx, next) => {
if (ctx.path === '/user' && ctx.method === 'POST') {
ctx.body = ctx.request.body;
}
next();
});
app.listen(3000)
复制代码
根据上述koa-bodypaser替代部分咱们能够大体推测出其实现返回的是promise,可是因为返回的结果在ctx.request.body上,因此会在promise外在包一层(ctx, next)
koa本身实现中间件 写一个函数返回async函数,内部处理好内容,继续执行便可
function bodyParser() {
return async (ctx,next)=>{
await new Promise((resolve, reject)=>{
let buffers = [];
ctx.req.on('data',function (data) {
buffers.push(data);
})
ctx.req.on('end',function () {
let result = Buffer.concat(buffers);
ctx.request.body = result.toString();
resolve();
})
});
await next();
}
}
复制代码
可是bodyparser有个缺点,不支持上传文件,好比上传图片格式,传递方式是二进制,就不能用tostring转化了,并且文件上传的格式是enctype="multipart/form-data"
这种格式请求后返回的样子如图:
Content-Type
会是
: multipart/form-data; boundary=----WebKitFormBoundarywAZ6ljeDoXBrZps6
boundary的内容和请求题的第一行是同样的 咱们如何解析这种格式呢?
let Koa = require('koa');
let app = new Koa();
let fs = require('fs');
Buffer.prototype.split = function (sep) {
let arr = [];
let index = 0;
let len = Buffer.from(sep).length;
let offset = 0;
while (-1 !== (offset = this.indexOf(sep,index))) {
arr.push(this.slice(index,offset));
index = offset + len;
}
arr.push(this.slice(index));
return arr;
}
function bodyParser() {
return async (ctx,next)=>{
await new Promise((resolve, reject)=>{
let buffers = [];
ctx.req.on('data',function (data) {
buffers.push(data);
})
ctx.req.on('end',function () {
let result = Buffer.concat(buffers);
let value = ctx.get('Content-Type');
let boundary = value.split('=')[1];
if(boundary){ // 提交文件的格式是文件类型 multipart/form-data
boundary = '--' + boundary; // 分界线
// 将内容 用分界线进行分割 buffer.split()
let arr = result.split(boundary); // []
arr = arr.slice(1,-1);//取出的数组包括前面的的空格后面的--不要
let obj = {};
arr.forEach(line=>{ // 拆分每一行
let [head,content] = line.split('\r\n\r\n');
// 看一下头中是否有filename属性
head = head.toString();
if(head.includes('filename')){ //文件有filename
// 文件 content是文件的内容
let filename = head.match(/filename="(\w.+)"/m);
filename = filename[1].split('.');
filename = Math.random() + '.' + filename[filename.length-1];//文件名惟一
let c = line.slice(head.length+4,-2);
fs.writeFileSync(filename, c ); //写入文件名字和内容
obj['filename'] = filename;
}else{//普通文本
let key = head.match(/name="(\w+)"/m);//m是多行
key = key[1];
let value = content.toString().slice(0,-2);//内容后面的换行回撤也关掉/r/n
obj[key] = value
}
});
ctx.request.body = obj;
}else{
ctx.request.body = result.toString();
}
resolve();
})
});
await next();
}
}
app.use(bodyParser()); // 会把请求体的结果放到 req.request.body
app.use(async (ctx, next) => {
if (ctx.path === '/user' && ctx.method === 'GET') {
ctx.body = ` <form method="post" enctype="multipart/form-data"> ... </form> `
}
await next();
});
...
app.listen(3000)
复制代码
通常咱们的cookie不加密,由于它自己容易被劫持,其次加密以后,可能出来的结果会比原油字符串长不少,产生流量消耗,
koa中的cookie是内置的,express也是设置cookie可是例如加{signed:true}这些东西是有cookie-parser提供的
这个过程咱们须要安装koa
koa-router
koa-views
koa-session
koa-static
cookie使用
let Koa = require('koa');
let app = new Koa();
let Router = require('koa-router');
let router = new Router();
app.use(router.routes())
//告诉客户端服务端支持的方法
app.use(router.allowedMethods()) //405
app.keys = ['hello'];
router.get('/write',(ctx,next)=>{
ctx.cookies.set('name','zdl',{
dimain:'localhist',
path:'/',
maxAge:10*1000,
httpOnly:false,
overwrite:true,
signed:true //用这个属性必须加app.key
})
ctx.body = 'write ok'
})
router.get('/read',(ctx,next)=>{
ctx.body = ctx.cookies.get('name',{sugned:true}) || 'not fond'
})
app.listen(3000);
复制代码
let Koa = require('koa');
let app = new Koa();
let Router = require('koa-router');
let router = new Router();
let session = require('koa-session');
app.keys = ['hello'];
app.use(session({dimain:'localhost'},app));
router.get('/cross',(ctx,next)=>{
let n = ctx.session.n || 0;
ctx.session.n = ++n;
ctx.body = ctx.session.n;
})
app.use(router.routes())
app.use(router.allowedMethods()) //405
app.listen(3000);
复制代码
基于cookie 和express的相似,这里咱们就不作介绍了,请参考权限处理 - 用redis实现分布式session~ (cookie && session )
三个路由
koa-session.js
let Koa = require('koa');
let app = new Koa();
let Router = require('koa-router');
let router = new Router();
let fs = require('fs');
let path = require('path');
router.get('/',(ctx,next)=>{
ctx.set('Content-Type','text/html');
ctx.body = fs.createReadStream(path.join(__dirname,'index.html'))
})
router.get('/login',(ctx,next)=>{
ctx.cookies.set('isLogin',true);
ctx.body = {'login':true}
})
router.get('/valiate',(ctx,next)=>{
console.log('hello')
let isLogin = ctx.cookies.get('isLogin');
console.log(isLogin)
ctx.body = isLogin;
})
app.use(router.routes());
app.listen(3000);
复制代码
index.html
...
<body>
<div>
<button id='login'>登陆</button>
<button id='valiadate'>验证登陆</button>
</div>
<script> login.addEventListener('click',function(){ let xhr = new XMLHttpRequest(); xhr.open('get','/login',true); xhr.send(); }) valiadate.addEventListener('click',function(){ let xhr = new XMLHttpRequest(); xhr.open('get','/valiate',true); xhr.onload = function(){ alert(xhr.response) } xhr.send(); }) </script>
</body>
复制代码
将上述html文件以ejs的模式渲染 koa-express.js
let Koa = require('koa');
let app = new Koa();
let Router = require('koa-router');
let router = new Router();
let fs = require('fs');
let path = require('path');
let views = require('koa-views');
app.use(views(__dirname, {//以当前路径做为查找范围
map:{html:'ejs'}//设置默认后缀
}));
router.get('/',async (ctx,next)=>{
// 若是不写return 这个函数执行完就结束了 模板尚未被渲染,ctx.body = ''
// 若是使用return会等待这个返回的promise执行完后才把当前的promise完成
return ctx.render('ejs.html',{title:'zdl'});
})
app.use(router.routes());
app.listen(3000);
复制代码
ejs.html
...
<body>
hello <%=title%>
</body>
复制代码
let Koa = require('koa');
let app = new Koa()
let Router = require('koa-router');
let router = new Router;
// let static = require('koa-static');
let fs = require('fs');
let util = require('util');
let path = require('path');
let stat = util.promisify(fs.stat);
let mime = require('mime');
function static(p){
return async (ctx,next) => {
let execFile ;
execFile = path.join(p, ctx.path); // 是一个绝对路径
try{
let statObj = await stat(execFile);
if(statObj.isDirectory()){
let execFile = path.join(p, 'index.html');
ctx.set('Content-Type', 'text/html');
ctx.body = fs.createReadStream(execFile);
}else{
ctx.set('Content-Type', mime.getType(execFile));
ctx.body = fs.createReadStream(execFile);
}
}catch(e){
// 若是文件找不到调用下一个中间件(要加return),下一个中间件可能会有异步操做,但愿下一个中间件的结果获取完后再让当前的promise执行完成
//await也能够,只是return明确表示后面没有可执行代码了
return next();
}
}
}
app.use(static(path.join(__dirname,'public')));
function fn(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{resolve('hello world')},3000)
})
}
router.get('/test',async(ctx,next)=>{
ctx.body = await fn();
})
app.use(router.routes());
app.listen(3000)
复制代码
test.html是和当前js一个目录,可是index.html在public文件夹中,public和当前js在同级目录
实现个简单的koa,包括样子和错误消息监控,咱们先写一个测试用例,将其基本功能展示,在koa里面有个lib文件夹,里面有4个js文件,下面咱们根据功能逐个实现一下这四个文件
http.creactServer
case.js
let Koa = require('koa');
let app = new Koa();
app.use((ctx, next) => {
//res.end = 'hello'
//ctx.req = ctx.request.req = req
console.log(ctx.req.url);
console.log(ctx.request.req.url);
console.log(ctx.request.url);
console.log(ctx.url);
//ctx 会代理 ctx.request属性
//数据劫持,基本经过set get实现
console.log(ctx.req.path);
console.log(ctx.request.req.path);
console.log(ctx.request.path);
console.log(ctx.path);
ctx.body = 'hello';
//throw Error('出错啦')
//ctx.body = {hi:'hello'}
//ctx.body = fs.createReadStream(path.join(__dirname,'package.json'))
})
app.use((ctx,next) => {
ctx.body = 'hello'
})
app.listen(3000)
复制代码
先将case.js改为原始的,最后,在经过上下问串在一块儿
application.js
//框架的核心就是http服务
let http = require('http');
let EventEmitter = require('events');//错误监听事件用的,发布订阅
let context = require('./context');
let request = require('./request');
let response = require('./response');
class Koa extends EventEmitter{
constructor(){
super();//继承专用
//将全局属性放到实例上
this.context = context;
this.request = request;
this.response = response;
this.middlewares = [];
}
//koa的和新方法1
use(fn){//函数保留下来,存储在app里面,由于能够重复调用,因此存的确定是数组
this.middlewares.push = fn;
}
//经过req,res创造出Context对象
createContext(req,res){
// 建立ctx对象 request和response是本身封装的
//Object.creat建立的不会有链的关系,新属性会放到ctx不会放到原始上
let ctx = Object.create(this.context);
//ctx上有reqest,req,response,res属性
//this.request须要在request.js处理
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;
}
// composeFn是组合后的promise
compose(middlewares,ctx){
//目的将第一个函数执行,包装成promise返回去
function dispatch(index) {
if (index === middlewares.length) return; Promise.resolve();
let fn = middlewares[index];//取第0个
//取出来后让函数执行,在执行下一个
return Promise.resolve(fn(ctx,()=>dispatch(index+1)))
}
//返回第一个执行完的promise
return dispatch(0);
}
// 经过req和res产生一个ctx对象
handleRequest(req,res){
let ctx = this.createContext(req,res);
//若是没给ctx.body,咱们设置个默认值只要设置了,就改为200
//可是在response.js里改
res.statusCode = 404;
//koa对函数作了异步处理,因此conpose是组合后的promise
//而后执行每个函数,等函数都执行完以后把包取出来,返回函数;
let composeFn = this.conpose(this.middleware,ctx)
composeFn.then(()=>{
let body = ctx.body;
if (body instanceof stream) {
body.pipe(res);
}else if(typeof body === 'object'){
res.end(JSON.stringify(body));
}else if(typeof body === 'string' || Buffer.isBuffer(body)){
res.end(body);
}else{//没有写就是not found
res.end('Not Found');
}
}).catch(err=>{ // 若是其中一个promise出错了就发射错误事件便可
this.emit('error',err);
res.statusCode = 500;
res.end('Internal Server Error');
})
}
//koa的核心方法二
listen(){
//fn = (req,res) => {...})
//自己fn里面有req,res,然而在ctx里面,咱们在fn外面在套一层函数
let server = http.createServer(this.handleRequest.bind(this));
server.listen(...arguments)
}
}
module.exports = Koa;
复制代码
this.request没有url,path等属性,咱们须要在此文件处理 request.js
let url = require('url');
let request = {
//ctx.req = ctx.request.req = req;
//自己没有req属性,但在aplication.js,调用url的是ctx.request,ctx.request上有req的属性,故能够经过ctx.request.url = ctx.request.req.url
get url(){
return this.req.url
},
//处理path
get path(){
return url.parse(this.req.url).pathname
},
get query() {
return url.parse(this.req.url).query
}
...
}
module.exports = request;
复制代码
response.js
let response = {
set body(value){
this.res.statusCode = 200;
this._body = value;
},
get body(){
return this._body
}
//这样取值只能经过ctx.response.body
//咱们但愿ctx.body = ctx.response.body
//因此须要在context.js文件代理
//咱们同时须要在ctx.body 的时候设置到ctx.request
//一样取context.js作设置的代理
}
module.exports = response;
复制代码
context
//ctx.path 取的是 ctx.request.path 为链让其互不影响,咱们在此用代理的方式
let proto = {};
// ctx.path = ctx.request.path //设置获取方式默认属性
//定义获取器
//defineGetter('request','path');
function defineGetter(property,name) {
proto.__defineGetter__(name,function () {
//ctx.request.path
return this[property][name];
})
}
//ctx = require('context')
//ctx.body = 'hello' 设置的是 ctx.response.body ='hello'
function defineSetter(property, name) {
proto.__defineSetter__(name,function (value) {
this[property][name] = value;
})
}
defineGetter('request','path');
defineGetter('request','url');
defineGetter('response','body');
defineSetter('response','body');
module.exports = proto;
复制代码
application
let http = require('http');
let EventEmitter = require('events');//错误监听事件用的
let context = require('./context');
let request = require('./request');
let response = require('./response');
let stream = require('stream');
class Koa extends EventEmitter{
constructor(){
super();
this.context = context;
this.request = request;
this.response = response;
this.middlewares = []
}
use(fn){//函数保留下来
this.middlewares.push(fn);
}
compose(middlewares,ctx){
function dispatch(index) {
if (index === middlewares.length) return Promise.resolve()
let fn = middlewares[index];
return Promise.resolve(fn(ctx,()=>dispatch(index+1)))
}
return dispatch(0);
}
createContext(req,res){
// 建立ctx对象 request和response是本身封装的
let ctx = Object.create(this.context);
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;
}
handleRequest(req,res){
// 经过req和res产生一个ctx对象
let ctx = this.createContext(req,res);
// composeFn是组合后的promise
res.statusCode = 404;
let composeFn = this.compose(this.middlewares, ctx)
composeFn.then(()=>{
let body = ctx.body;
if (body instanceof stream) {
body.pipe(res);
}else if(typeof body === 'object'){
res.end(JSON.stringify(body));
}else if(typeof body === 'string' || Buffer.isBuffer(body)){
res.end(body);
}else{
res.end('Not Found');
}
}).catch(err=>{ // 若是其中一个promise出错了就发射错误事件便可
this.emit('error',err);
res.statusCode = 500;
res.end('Internal Server Error');
})
}
listen(){
let server = http.createServer(this.handleRequest.bind(this));
server.listen(...arguments)
}
}
module.exports = Koa;
复制代码