Refer to the koa,5 step handwritten a rough web frame

Source: Internet
Author: User

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:

    • Object.defineproperty
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

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.