实现express之一:通过几十行代码实现一个最简单的框架

要学习熟悉一个框架,我认为最重要的是要学到其中的”道”, 而不是”术”的层面。那么要学习express,有那么多的api,有那么多行源码,源码中又不断引用其他三方的npm组件,很容易就一叶障目,用时很长,但收效甚微。 为什么这么说呢?因为我就是在这个坑中爬了挺长时间才上来,不断摸清了作者的架构思想。 所以,我在这一篇,通过用100行左右的代码,实现一个最简单,但也可用的微框架。

一、原理

在使用express时, 我们主要在处理两类请求,一是对静态资源的请求,而是对非静态资源的请求。

1.1 对静态资源的请求

在express的开头,我们一般都会使用下面代码:

    const app = express();
    app.use(express.static('public'));

复制代码

我想对express有些了解的同学,都知道这是对静态资源请求进行初始化处理。当后面请求静态资源(css,js,image…)时,会进入处理处理,并把静态资源返回给客户端。

1.2 对非静态资源的请求

在express中,我们会频繁的使用,比如:

// 比如
app.use((req, res, next) => {
    // ....
    next();
});

// 再比如
app.get('/', (req, res) => {
    ejs.renderFile('./views/info.ejs', {
        title: 'info页面',
        description: 'info描述描述。。。'
    },
    (err, data) => {
        if (err) {
            return;
        }
        res.end(data);
    });
});
复制代码

好了,最重要的两个点做了提示,那么就开始分析我的微代码吧~~

二、代码

// 入口文件
const http = require('http');
const app = require('./route');
const ejs = require('ejs');
app.static('public');

app.get('/login', (req, res) => {
    // 渲染页面
    console.log('进行逻辑处理');
    res.end('login success');
});

http.createServer(app).listen(3000);
console.log('http server started on 3000');

复制代码

下面是route.js, 逻辑就在这里面

const url = require('url');
const fs = require('fs');
const mime = require('mime');
const path = require('path');

let G = {
     _get: {},
     _post: {},
     staticPath: 'static'
 };
 
 const initStatic = async (req, res, staticPath) => {
    let pathname = url.parse(req.url).pathname;
    pathname = pathname === '/' ? '/index.html' : pathname;
    try {
        let data = fs.readFileSync('./' + staticPath + pathname);
        if (data) {
            let mimes = mime.getType(pathname);
            res.writeHead(200, {'Content-Type': '' + mimes + ';charset="utf-8'});
            res.end(data);
        }
    } catch (error) {
        
    }
  }
  
  const app = async (req, res) => {
    // 当请求来的时候,先去找静态服务下有没有(匹配静态资源)
    await initStatic(req, res, G.staticPath);
    let pathname = url.parse(req.url).pathname;
    let method = req.method.toLowerCase();
    // 先只考虑get和post情况
    if (G['_' + method][pathname]) {
        if (method === 'get') {
            G['_' + method][pathname](req, res);
        } else {
            let postData = '';
            req.on('data', chunk => {
                postData += chunk;
            });
            req.on('end', () => {
                req.body = postData;
                G['_' + method][pathname](req, res);
            });
        }
    } else {
        res.writeHead(404, {'Content-Type': 'text/plain;charset="utf-8"'});
        res.end('页面不存在');
    }
 }
 app.get = (str, cb) => {
    G._get[str] = cb;
 }

 app.post = (str, cb) => {
    G._post[str] = cb;
 }

 // 配置静态web服务目录
 app.static = staticPath => {
    G.staticPath = staticPath;
 }

 module.exports = app;
复制代码

2.1 代码分析

从上面的代码中,可以看到,开始时创建了一个G的变量,这个变量存储了get、post请求以及默认的static目录地址。

当我们使用app.get()或者app.post()进行注册时,会把回调函数push进 _get和_post中。
所以最终的G应该是这样的:

// 伪代码
G = {
    _post: [{‘/login’: fn3}, {'/postdata': fn4}],
    _get: [{'/': fn1}, {'getname': fn2}],
    staticPath: 'static'
}

复制代码

当请求来的时候,那么我们可以从req.method中获取到时get还是post, 那么就取出来执行。

虽然很简单,但是可以很比较准确的把原理体现出来。其中有非常非常多的边界情况没有考虑,但是,这显然不需要在这个开篇中进行分析。 大家能够理解上面的原理,那么看express源码的时候,就会顺手很多。

我不打算在后面的文章中逐句分析express源码,我会通过重写一个express框架,来带给大家精彩内容。 在讲的时候,会抓住主干,舍弃很多边际情况,力争做到代码简洁,逻辑突出。 喜欢就继续查看吧~~ 喜欢的话辛苦点赞~~

实现express之二 : 总体路径

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享