Writing aexpressA series of

expressBasic usage of

const express = require("express"); const app = express(); App. Get ("/test ", (the req, res, next) = > {the console. The log (" club technician in place * 1 "); // res.end(" Club technician starts service 1"); next(); }); App. Get ("/test ", (the req, res, next) = > {the console. The log (" club technician in place * 2 "); Res. end(" Club technician starts service 2"); }); app.listen(8888, (err) => { ! Err && console.log(" Is there big health in the clubhouse?" ); });Copy the code
  • When I visitlocalhost:8888/testWhen it returns:Club technician starts service 2, the server printed
Club technician in place *1 Club technician in place *2Copy the code
  • What can be seen from above?
    • expressOne is returned after the call is introduced by defaultappobject
    • app.listenThe process listening port is started
    • Each time a request is received, the correspondingurlandmethodWill trigger the corresponding mount inappTo the corresponding callback function
    • callnextMethod that triggers the next one

Let’s implement a simple oneexpressThe framework

  • Define what is oursexpressFile entry, used hereclassTo implement the
class express {

}

module.exports = express;
Copy the code
  • Required native moduleshttpTo create a process listening port
const { createServer } = require("http");

Copy the code
  • Give the class definitionlistenMethod to listen on a port
class express { listen(... args) { createServer(cb).listen(... args); }}Copy the code
  • This can be done by callingclasslistenTo callhttpThe modulelistenHerecbWe can ignore that, but you have to know that every time you receive a request, you’re going to callcbDelta function, this is delta functioncreateServerThe native module encapsulates it for us

Implementation receives the request trigger

  • implementationapp.get app.postMethods such as
    • Now when we receive the response, the cb callback is triggered, so let’s print it out and see what the argument is.
Class express {cb() {return (req, res) => {console.log(res, res, "guest "); }; } listen(... args) { createServer(this.cb()).listen(... args); }}Copy the code
  • Found that at this timereqresThat’s what we want for readable and writable streams.

  • Begin to writegetpostmethods
    • Note that there are routes that are ‘/’, which are triggered once regardless of any route
  constructor() {
    this.routers = {
      get: [],
      post: [],
    };
  }

  get(path, handle) {
    this.routers.get.push({
      path,
      handle,
    });
  }
  post(path, handle) {
    this.routers.post.push({
      path,
      handle,
    });
  }

Copy the code
  • Initialize the get and POST arrays to store the correspondingpathhandle.
  • To trigger the route callback, first find the handle method of the url in the corresponding request mode and then trigger the callback.

  • How to find the handle method corresponding to the URL in the corresponding request mode? We go through it once when we get the request

    • We’re thinking about matching multiple routes, which means we might have two of them as we started withgetThe way oftestrouting
cb() { return (req, res) => { const method = req.method.toLowerCase(); console.log(this.routers[method], ",method"); const url = req.url; this.routers[method].forEach((item) => { item.path === url && item.handle(req, res); }); }; } listen(... args) { createServer(this.cb()).listen(... args); }Copy the code
  • Find the corresponding array according to method, traverse to find the route of the request, trigger the callback, at this time can normally return data
[ { method: 'get', path: '/test', handle: [Function] } ] ,method
Copy the code
  • The easiest one at this pointexpressIt’s done, but we seem to have forgotten the most important piece of middleware

Accomplish the most important middleware functionality

  • The first thing to know is,expressThere are two types of middleware, one with routing, which determines whether to trigger based on the route
  • The other type is unrouted, such as static resources. Is triggered once when the user accesses any route
  • Then we need oneallArray storage this arbitrary route needs to match the triggered
 constructor() {
    this.routers = {
      get: [],
      post: [],
      all: [],
    };
  }

Copy the code
  • The previous direct push method was too rough. If the user needs middleware functionality and does not pass routing, then special processing is required, which is handled by an intermediate function
  • transformget,postMethod, definitionhandleAddRoutermethods
  handleAddRouter(path, handle) {
    let router = {};
    if (typeof path === "string") {
      router = {
        path,
        handle,
      };
    } else {
      router = {
        path: "/",
        handle: path,
      };
    }
    return router;
  }

  get(path, handle) {
    const router = this.handleAddRouter(path, handle);
    this.routers.get.push(router);
  }

  post(path, handle) {
    const router = this.handleAddRouter(path, handle);
    this.routers.post.push(router);
  }

  use(path, handle) {
    const router = this.handleAddRouter(path, handle);
    this.routers.all.push(router);
  }


Copy the code
  • Trigger each time before addinghandleAddRouterIf it ispathFor empty middleware, pass function directly, thenpathSet it to ‘/’
  • We also left a point,nextImplementation of, because we now addallThis array means that there may be multiple middleware, so a single request may trigger multiple routes

Note here that the promise. Then source code implementation is slightly similar to express’s next, koA’s Onion Ring, and Redux’s middleware implementation, and when you can actually dig into the source code of the front and back frameworks, most of them are similar

  • Read my article, enough to break all the back and forth source code. And can be handwritten out, we only learn the core, grasp the key study, savage growth!

implementationnext

  • Ideas:
    • The first step is to find all the routes that match
    • Then execute them one by one (looknextA call)
  • definesearchMethod to find all matching routes
  search(method, url) {
    const matchedList = [];
    [...this.routers[method], ...this.routers.all].forEach((item) => {
      item.path === url && matchedList.push(item.handle);
    });
    return matchedList;
  }

  cb() {
    return (req, res) => {
      const method = req.method.toLowerCase();
      const url = req.url;
      const matchedList = this.search(method, url);
    };
  }

Copy the code
  • matchedListThat’s all the routes we’re looking for
  • In order to complete thenext, we are going toreq ,res , matchedListStore it in a closure, definehandlemethods
handle(req, res, matchedList) { const next = () => { const midlleware = matchedList.shift(); if (midlleware) { midlleware(req, res, next); }}; next(); } cb() { return (req, res) => { const method = req.method.toLowerCase(); const url = req.url; const matchedList = this.search(method, url); this.handle(req, res, matchedList); }; }Copy the code

  • So we’re donenextMethod, just call it manuallynextThe next matched route callback is called

  • It takes less than a hundred lines of code to do this simpleexpressThe framework

Write in the last

  • As long as you according to my these articles to realize time, study hard, for one year with a P6 should have no problem, I was at https://github.com/JinJieTan/Peter- all the source code for the warehouse, remember to a star

  • I hope you can learn the principles of frameworks through these articles, and then write some of your own frameworks to move to a higher level

  • My name is Peter, who used to be an architect of desktop software for the 200,000 super group. Now I am working in Mingyuan Cloud as the head of the branch’s front end. At present, Shenzhen needs to recruit two middle and senior front end, majoring in 3D data visualization

  • If you found this article helpful, don’t forget to give it a thumbs up.