In recent days, I took out the two frameworks of KOA and Express to observe them. I knew that I was a front-end novice who could make up for my shortcomings with diligence, so I decided to record my experience on this rainy afternoon (I have been isolated at home for nearly one and a half months, learning how to practice is sweet). There may be improper wording in the article, as well as technical errors, welcome to correct.

This article centers on KOA.

Introduction of koa

Koa is a Web development framework implemented based on the Node built-in module HTTP

Koa is a new web framework designed by the team behind Express, which aims to be a smaller, more expressive, and more robust foundation for web applications and APIs. By leveraging async functions, Koa allows you to ditch callbacks and greatly increase error-handling. Koa does not bundle any middleware within its core, and it provides an elegant suite of methods that make writing servers fast and enjoyable.

From the above official introduction:

  • koaexpressIt’s all from the same team
  • koaDesigned to provide a smaller, more expressive, and more robust programming foundation for Web usage
  • Different from theexpress.koaInternal usePromiseObject that is allowed to be used externallyasync... awaitImplement asynchronous sequential operations insteadexpressThe callback function in
  • Error handling is easier,KoaThe class hierarchyeventsClass, the implementation mechanism is still based onPublish/subscribemodel
  • There is no internal integration of any middleware, download and install as needed; Compared with theexpressIntegrate all possible middleware such as:static.body-parser.multer.views.express-session.cookie-parserMiddleware, so that the file is larger, but users do not need to download.

using

The preparatory work

-npm init Initializes the 'package.json' file -npm install koa -s Downloads the 'koa' packages that the production environment depends onCopy the code

Initialization file

  • Create an app.js application file
const Koa  = require('koa');

const app = new Koa();

const Koa = require('./koa');

const app = new Koa();

app.use(async(ctx, next) => {
    console.log(1);
    ctx.body = '1';
    await next();
    ctx.body = '11';
    console.log(4);
});

app.use((ctx, next) = > {
    console.log(2);
    ctx.body = '2';
    next();
    ctx.body = '22';
    console.log(5);
});

app.use((ctx, next) = > {
    console.log(3);
    ctx.body = '3';
    next();
    ctx.body = '33';
    console.log(6);
});


app.on('err'.console.log);

app.listen(3000);
Copy the code

Print result:

1
2
3
6
5
4
Copy the code

The browser returns the result:

11
Copy the code

Summary features:

  • Based on thenodeThe encapsulationreq.resThe internal method has only three core functionsuse,listen,on('error')
  • contextctxThe context is a collection of REq and RES (request,response)
  • requestEncapsulate your own request method
  • responseSelf-encapsulated response methods

implementation

Onionsmodel

From a practical point of view,koaThe middleware call is similar to oneOnionsModel, as shown below:

The directory structure

koa
        | application.js
    | lib
        | context.js
        | request.js
        | response.js
    | package.json
Copy the code

coding

package.json

{
    "main": "lib/application.js",} for`koa`Package determines the entry file.Copy the code

application.js

const Emitter = require('events');
const http = require('http');
const context = require('./context');
const response = require('./response');
const request = require('./request');
const Stream = require('stream');

module.exports = class Koa extends Emitter{
    constructor() {
        super(a);this.middlewares = [];
        this.context = Object.create(context);
        this.response = Object.create(response);
        this.request = Object.create(request);
    }

    use(middleware) {
        this.middlewares.push(middleware);
    }
    
    handleRequest(req, res) {
        const ctx = this.createContext(req, res);
        ctx.statusCode(404);
        this.compose(this.middlewares, ctx)
            .then((a)= > {
                let body = ctx.body;
                if (body instanceof Stream) {
                    body.pipe(res);
                } else if (typeof body === 'object'){
                    ctx.set('Context-Type'.'application/json');
                    res.end(JSON.stringify(body));
                } else if (typeof body === 'string' || Buffer.isBuffer(body)) {
                    res.end(body);
                } else {
                    res.end('Not Found')
                }
            }, err => {
                this.emit('error', err);
                ctx.statusCode(500);
                ctx.end('Internal Server Error')
            })
    }
    
    compose(middlewares, ctx) {
        function dispatch(index) {
            if (index === middlewares.length) return Promise.resolve();
            try {
                return Promise.resolve(middlewares[index](ctx, () => dispatch(index +1)));
            } catch(err) {
                return Promise.reject(err); }}return dispatch(0);
    }

    createContext(req, res) {
        const context = Object.create(this.context);
        const request  = context.request = Object.create(this.request);
        const response =  context.response = Object.create(this.response);
        context.req = request.req = req;
        context.res =  response.res = res;
        returncontext; } listen(... args) {const server = http.createServer(this.handleRequest.bind(this));
        return server.listen(...args);
    }
}
Copy the code

Resolution:

  • Object.create(targetObject);Here,targetObjectcontext.response.request, this step is to prepare three objects for prototype chain inheritance
  • context: Used internallyObject.prototype.__defineGetter__Object.prototype.__defineSetter, the step is rewritingcontextAttribute is access attribute, value and assignment through the function form, more logical operation. Such as:context.methodThe currentcontextThere is nomethodProperties, byObject.prototype.__defineGetter__Returns internally when calledresponse.methodThen the value can be completed.
  • responserequestBoth are expanded nativereq.resMethods and properties on the object. Such as:req.methodIt’s just expandingresponseAdded to the property of

Here is:

context.js

const proto = {};

function defineGetter(property, key) {
    proto.__defineGetter__(key, function() {
        return this[property][key]; })}function defineSetter(property, key) {
    proto.__defineSetter__(key, function(newValue) {
        this[property][key] = newValue;
    })
}


defineGetter('request'.'method');
defineGetter('request'.'path');
defineGetter('response'.'body');
defineGetter('response'.'set');
defineGetter('request'.'params');
defineGetter('response'.'statusCode');

defineSetter('response'.'body');


module.exports = proto;
Copy the code

request.js

const url = require('url');
const querystring = require('querystring');

module.exports = {
    get method() {
        return this.req.method;
    },

    set method(val) {
        this.req.method = val;
    },

    get path() {
        // Url {
        // protocol: null,
        // slashes: null,
        // auth: null,
        // host: null,
        // port: null,
        // hostname: null,
        // hash: null,
        // search: '? id=1',
        // query: 'id=1',
        // pathname: '/get',
        // path: '/get? id=1',
        // href: '/get? id=1'
        / /}
        const { pathname, query } = url.parse(this.req.url);
        this._params = querystring.parse(query);
        return pathname;
    },

    get params() {
        return this._params;
    },

    get url() {
        return this.req.url; }},Copy the code

response.js

module.exports = {
    _body: undefined,

    get header() {
        const { res } = this;
        return res.getHeaders();
    },

    get headers() {
        return this.header;
    },

    get status() {
        return this.res.statusCode;
    },

    get body() {
        return this._body;
    },

    set body(val) {
        this.statusCode(200);
        this._body = val;
    },
    
    statusCode(newValue) {
        this.res.statusCode = newValue; }, set(... args) {console.log(args);
        this.res.setHeader(...args);
    }
}
Copy the code