I often see similar articles on the Internet KOA VS express , and everyone is discussing which one is good and which is better. As a small white, I really do not see him two who better. All I know is that I will only follow the official document start to do a demo, and then I will declare that I will use the KOA or express framework. But after a few weeks, I forgot all about it. The web framework is the equivalent of a tool, which is a matter of minutes to use. After all, people write this framework is to facilitate everyone to use. But this kind of blunt copy pattern, is not suitable for me this kind of understanding ability extremely poor user. So I decided to pick a PA source, through the official API, write a Web framework, in fact, the equivalent of "copy" the source code, coupled with their own understanding, thereby deepening the impact. Not only need to know it, but also need to know why.
I choose KOA here as a reference model, there is only one reason! He's very streamlined! Core only 4 JS files! is basically a package for the createserver.
Before you begin to gouge out the KOA, the Createserver usage still needs to be reviewed:
const http = require(‘http‘);let app=http.createServer((req, res) => { //此处省略其他操作 res.writeHead(200, { ‘Content-Type‘: ‘text/plain‘ }); res.body="我是createServer"; res.end(‘okay‘);});app.listen(3000)
Looking back at Createserver, the next is the 4 files that were unpacked KOA:
- Application.js
- This JS is mainly on the Createserver package, one of the main purpose is to separate his callback, let us through
app.use(callback); to call, which callback is probably to make everyone feared the middleware (middleware).
- Request.js
- Encapsulates the req returned in Createserver, primarily for read-write properties.
- Response.js
- Encapsulates the res returned in Createserver, primarily for read-write properties.
- Context.js
- This file is very important, it is mainly encapsulated the request and response, for the framework and middleware communication. So he's called context, and it makes sense.
Okay ~ start writing frames ~
Just analyze the approximate idea, analyze the principle of KOA, so not 100% reproduce koa.
This article GitHub address: Point me
Step1 Package
http.createServer
First write an initial version application , let the program run up first. Here we just implement:
http.createServerclasses encapsulated into the Myhttp
- Separate the callbacks.
listenMethod can be used directly
Step1/application.js
let http=require("http")class myhttp{ handleRequest(req,res){ console.log(req,res) } listen(...args){ // 起一个服务 let server = http.createServer(this.handleRequest.bind(this)); server.listen(...args) }}
The complete and the use of this side of the listen server.listen Same, is passed the next parameter
Friendship Link
server.listenThe API
ES6 Deconstruction Assignment...
Step1/testhttp.js
let myhttp=require("./application")let app= new myhttp()app.listen(3000)
Run testhttp.js , the results print out req and res on the success ~
Step2 Package native req and res
Here we need to do the encapsulation, which requires only two steps:
- Read (get) contents of req and res
- Modifying the contents of (set) res
Step2/request.js
let request={ get url(){ return this.req.url }}module.exports=request
Step2/response.js
let response={ get body(){ return this.res.body }, set body(value){ this.res.body=value }}module.exports=response
If the PO on the code, it is so simple, the required properties can be added to their own. So this where does this point come from?? The code is very simple, but this point is not simple.
Back to US application.js , let this this example point to our myhttp.
Step2/application.js
class myhttp{ constructor(){ this.request=Object.create(request) this.response=Object.create(response) } handleRequest(req,res){ let request=Object.create(this.request) let response=Object.create(this.response) request.req=req request.request=request response.req=req response.response=response console.log(request.headers.host,request.req.headers.host,req.headers.host) } ...}
Here, we Object.create copy a copy and then put the request and response separately, and we can see through the last test that we can directly request.headers.host access the information we need without having to pass request.req.headers.host such a long instruction. This for us next, will request and response hang to context hit the ground.
Step3
contextFlash debut
contextfunction, I have no other requirements for him, it can be directly context.headers.host , without context.request.headers.host , but I could not every time to add the required properties, all to write a get/set it? So Object.defineProperty this god came into operation.
Step3/content.js
let context = {}//可读可写function access(target,property){ Object.defineProperty(context,property,{ get(){ return this[target][property] }, set(value){ this[target][property]=value } })}//只可读function getter(target,property){ Object.defineProperty(context,property,{ get(){ return this[target][property] } })}getter(‘request‘,‘headers‘)access(‘response‘,‘body‘)...
So that we can easily define the data, but it should be noted that the Object.defineProperty ground object can only be defined once, can not be defined more than once, will be error drops.
Step3/application.js
Next is the context connection request and response the, and the new one createContext , will response and request prodded to hang context on it.
class myhttp{ constructor(){ this.context=Object.create(context) ... } createContext(req,res){ let ctx=Object.create(this.context) let request=Object.create(this.request) let response=Object.create(this.response) ctx.request=request ctx.response=response ctx.request.req=ctx.req=req ctx.response.res=ctx.res=res return ctx } handleRequest(req,res){ let ctx=this.createContext(req,res) console.log(ctx.headers) ctx.body="text" console.log(ctx.body,res.body) res.end(ctx.body); } ...}
The above 3 steps finally put the preparation work done, then go to the point.??
Friendship Link:
STEP4 implementation
use
Here I need to complete two function points:
useCan be called multiple times, middleware middleware is executed sequentially.
useIn the ctx context of the middleware middleware call
If you want multiple middleware to execute, then build an array, save all the methods in it, and then wait until the execution is done, one at a time. Incoming ctx will be passed in at the time of execution.
Step4/application.js
class myhttp{ constructor(){ this.middleWares=[] ... } use(callback){ this.middleWares.push(callback) return this; } ... handleRequest(req,res){ ... this.middleWares.forEach(m=>{ m(ctx) }) ... } ...}
Here, use a small feature is added, that is, use can implement chained calls, return directly this , because this it refers to myhttp the instance app .
Step4/testhttp.js
...app.use(ctx=>{ console.log(1)}).use(ctx=>{ console.log(2)})app.use(ctx=>{ console.log(3)})...
Step5 implementing asynchronous execution of middleware
Any program with the addition of async, feel the difficulty of rubbing against the rise.
There are two points to deal with:
useAsynchronous execution of middleware in
- Asynchronous execution of the middleware after asynchronous completion
compose .
The first is the use async in
If I need middleware to be asynchronous, then we can use async/await to write this and return a promise
app.use(async (ctx,next)=>{ await next()//等待下方完成后再继续执行 ctx.body="aaa"})
If it is promise, then I can not follow the normal program foreach execution, we need one to complete after the other, then we need to put these functions together in another method to compose process, and then return a promise, and finally a then , tell the program I'm done.
handleRequest(req,res){ .... this.compose(ctx,this.middleWares).then(()=>{ res.end(ctx.body) }).catch(err=>{ console.log(err) }) }
So compose how to write it?
First, the middlewares needs one execution and then the next execution, which is the callback. Next compose need to return a promise, in order to tell the end I finished.
The first version of compose, a simple callback, like this. But this is foreach no different from the other. Here fn is our middleware, that ()=>dispatch(index+1) is next .
compose(ctx,middlewares){ function dispatch(index){ console.log(index) if(index===middlewares.length) return; let fn=middlewares[index] fn(ctx,()=>dispatch(index+1)); } dispatch(0)}
The second version of compose, we add async/await, and return to promise, like this. But this is foreach no different from the other. dispatchBe sure to return a promise.
compose(ctx,middlewares){ async function dispatch(index){ console.log(index) if(index===middlewares.length) return; let fn=middlewares[index] return await fn(ctx,()=>dispatch(index+1)); } return dispatch(0)}
return await fn(ctx,()=>dispatch(index+1));Note here, that's why we need to next add an await before it takes effect? As the promise fn has been implemented, if not waiting for the rear of the promise, then directly then , the rear of the next dead. So if it's asynchronous, we need to add it to the middleware async/await to make sure it's next done and then back to the previous one promise . Can't understand??? The Let's look at a few examples.
Here's how:
function makeAPromise(ctx){ return new Promise((rs,rj)=>{ setTimeout(()=>{ ctx.body="bbb" rs() },1000) })}//如果下方有需要执行的异步操作app.use(async (ctx,next)=>{ await next()//等待下方完成后再继续执行 ctx.body="aaa"})app.use(async (ctx,next)=>{ await makeAPromise(ctx).then(()=>{next()})})
The code above executes ctx.body="bbb" again ctx.body="aaa" , so it prints out aaa . If we reverse the opposite:
app.use(async (ctx,next)=>{ ctx.body="aaa" await next()//等待下方代码完成})
Then the above code executes ctx.body="aaa" ctx.body="bb" and executes, so it prints out bbb .
This time we will think, since I this middleware is not asynchronous, then you can not add async/await it? Practice the truth:
app.use((ctx,next)=>{ ctx.body="aaa" next()//不等了})
Then the program will not wait for the end of the asynchronous ending. So if there is an asynchronous requirement, especially if you need to perform the next step asynchronously, even if there is no asynchronous demand for this middleware, add async/await.
Finally finished, feel brain cells died a lot, then I went to study router and ejs, and so this piece joined my web framework, it is perfect ~
Refer to the koa,5 step handwritten a rough web frame