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.createServer
classes encapsulated into the Myhttp
- Separate the callbacks.
listen
Method 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.listen
The 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
context
Flash debut
context
function, 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:
use
Can be called multiple times, middleware middleware is executed sequentially.
use
In 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:
use
Asynchronous 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. dispatch
Be 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