实现简易版express

194 阅读3分钟

express 基本功能

首先看一个简单的实例,通过这个实例可以看出express大体需要做哪些事情。

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

从这个实例中大体可以看出express提供一个入口方法express,返回一个实例,实例上提供一个监听方法,监听对应端口的http请求,以及提供一些类似get这种,路由监听方法

基础结构实现

function createApplication(): any {

  // app是一个监听函数
  let app = function (req: Request, res) {
  };

  // @ts-ignore
  app.listen = function () {
    let server = http.createServer(app);
    // arguments就是参数(3000, 'localhost', function () {})
    server.listen(...arguments);
    return server;
  };

  return app;
}

const app = createApplication();
app.listen(3000, () => {
  console.log("listen 3000");
});

主要使用nodeJs原生的createServer方法,创建server,通过listen方法暴露出来。

路由监听方法

框架要提供所有http请求方法的对应路由监听方法,可以通过http模块的METHODS获取,获取到后遍历然后将对应的路由监听回调方法存储在路由数组中。

  app.routes = [];
  http.METHODS.forEach((method) => {
    method = method.toLocaleLowerCase();
    app[method] = function (path: string, handler) {
      app.routes.push({
        path,
        method,
        handler,
      });
    };
  });

路由监听回调函数执行

最后可以将执行路由回调函数的任务放在app方法中做,该方法在每次有http请求的时候都会被执行。

let app: App = function (req, res) {
    let method = req.method.toLocaleLowerCase();
    let { pathname } = url.parse(req.url, true);
    app.routes.forEach((route) => {
      if (route.method === method && route.path === pathname) {
        route.handler(req, res);
      }
    });
  };

目前大体的基本功能已经实现,完整代码如下:

import * as http from "http";
import * as url from "url";
import { App } from "./config";

function createApplication(): App {
  // @ts-ignore
  let app: App = function (req, res) {
    let method = req.method.toLocaleLowerCase();
    let { pathname } = url.parse(req.url, true);
    app.routes.forEach((route) => {
      if (route.method === method && route.path === pathname) {
        route.handler(req, res);
      }
    });
  };
  app.routes = [];
  app.listen = function () {
    // @ts-ignore
    let server = http.createServer(app);
    // arguments就是参数(3000, 'localhost', function () {})
    server.listen(...arguments);
    return server;
  };
  http.METHODS.forEach((method) => {
    method = method.toLocaleLowerCase();
    app[method] = function (path: string, handler) {
      app.routes.push({
        path,
        method,
        handler,
      });
    };
  });
  return app;
}

const app = createApplication();
app.listen(3000, (req,res) => {
  console.log("listen 3000");
});
app.get("/gettest", (req, res) => {
  res.end('hello get test')
});

中间件功能

实现中间件功能其实很简单,中间件可以看做一个特殊的路由监听事件。 中间件通过use方法注册:

  app.use = function (path, handler) {
    app.routes.push({
      path,
      method: MIDDLE_WARE_METHOD,
      handler,
    });
  };

接着在app方法中加入中间件判断

 const MIDDLE_WARE_METHOD = "middleware";

  let app: App = function (req, res) {
    let httpMethod = req.method.toLocaleLowerCase();
    let { pathname } = url.parse(req.url, true);
    let index = 0;
    function next() {
      if (index === app.routes.length) {
        return;
      }

      let { method, path, handler } = app.routes[index++];
      if (method === MIDDLE_WARE_METHOD) {
        if (path === pathname) {
          handler(req, res, next);
        } else {
          next();
        }
      } else {
        if (method === httpMethod && pathname === path) {
          handler(req, res);
        } else {
          next();
        }
      }
    }
    next();
  };

在app方法中加入next方法以及是否是中间件路由监听的判断

完整代码

import * as http from "http";
import * as url from "url";
import { IncomingMessage, ServerResponse } from "http";
import { App } from "./interface";

 const MIDDLE_WARE_METHOD = "middleware";

function createApplication(): App {
  let app = <App>function (req: IncomingMessage, res: ServerResponse) {
    let httpMethod = req.method.toLocaleLowerCase();
    let { pathname } = url.parse(req.url, true);
    let index = 0;
    function next() {
      if (index === app.routes.length) {
        return;
      }

      let { method, path, handler } = app.routes[index++];
      if (method === MIDDLE_WARE_METHOD) {
        if (path === pathname) {
          handler(req, res, next);
        } else {
          next();
        }
      } else {
        if (method === httpMethod && pathname === path) {
          handler(req, res);
        } else {
          next();
        }
      }
    }
    next();
  };
  app.use = function (path, handler) {
    app.routes.push({
      path,
      method: MIDDLE_WARE_METHOD,
      handler,
    });
  };

  app.routes = [];

  app.listen = function (...arg) {
    let server = http.createServer(app);
    // arguments就是参数(3000, 'localhost', function () {})
    server.listen(...arg);
    return server;
  };

  http.METHODS.forEach((method) => {
    method = method.toLocaleLowerCase();
    app[method] = function (path: string, handler) {
      app.routes.push({
        path,
        method,
        handler,
      });
    };
  });

  return app;
}

const app = createApplication();

app.listen(3000, () => {
  console.log("listen 3000");
});
app.get("/gettest", (req, res) => {
  res.end("hello get test");
});

app.use("/test", (req, res, next) => {
  next();
});

总结

这个只是简单的mock,还有很多功能没有完善,大家可以有选择的进行阅读,后续我会逐步完善。