📖
blog
  • README
  • JavaScript
    • 元素的宽高位置信息梳理
    • dom-align 源码浅析
    • Event Loop
    • 函数实参为对象时的陷阱
    • export 与 utils 方法书写规范
    • 手写 Promise 及相关代码理解
    • call,apply,bind 等函数的模拟实现
    • JavaScript继承
    • JavaScript 数据类型与类型判断
    • for..of 和 for..in 的区别
    • 写给自己看的 next 函数
    • JS 可选链与双问号
    • mouseenter 与 mouseover 事件的区别
    • Immutable相关知识
  • CSS
    • 不简单的 z-index
    • 两列布局,三列布局
    • CSS 居中方案整理
    • CSS 像素,设备像素,2倍图梳理
    • iconfont 的使用
  • Node JS
    • 实现简易的 express
  • React 核心知识点整理
    • 高阶组件
    • React 事件处理
    • React Hooks
    • React Context
  • React 状态管理
    • Redux 基础概念
    • Redux 中间件和异步操作
    • Redux Saga
    • Redux 只能有一个 store 对象嘛
  • React 开发实践
    • Ant Design Menu 组件的使用与深入
    • 讲讲吸顶效果与 react sticky
    • 基于 express,搭建 react 的开发环境
    • 通过 antd input 组件分析受控与非受控组件
    • DebounceClick 组件
    • react component Align 组件分析
    • React Portal 之事件冒泡
    • React Transition Group 源码浅析
    • React.cloneElement 父组件向子组件注入 props
    • 一次 Align 组件的问题记录
    • 如何知道子组件的类型
    • React Router 源码简单分析
    • React Redux 源码简单分析
  • Vue.js
    • Vue.js 概览
    • scoped 样式中的 deep
  • TypeScript 语法
    • 基础类型
    • 变量声明
    • 接口
    • 类
    • 函数
    • 泛型
    • 枚举
    • 类型推论
    • 类型兼容性
    • 高级类型
    • Symbol
    • 迭代器和生成器
    • 模块
    • 命名空间
    • JSX
  • 玩转 webpack
    • 第一章: webpack 与构建发展简史
    • 第二章:webpack基础用法
    • 第三章:webpack进阶用法
    • 第四章:编写可维护的 webpack 构建配置
    • 第五章:webpack构建速度和体积优化策略
    • 第六章:通过源代码掌握webpack打包原理
    • 第七章:编写Loader和插件
  • webpack 实践
    • 如何配置 output.library
  • 测试
    • 初识代码测试
    • Jest 中 如何测试 setTimeout
    • Jest Enzyme React 测试实践记录
  • WEB 开发,过往工作沉淀
    • Web安全(DVWA)
    • 内存泄露与事件移除的必要性
    • url to pdf api 与 服务部署踩坑记录
    • 前端调试指南
    • Markdown 转 email
    • github travis ci 自动部署
    • 浏览器缓存知识梳理
    • WEB 系统登录相关知识梳理
    • 将-Axios-请求参数和返回值进行格式化
    • source-map与源码调试
    • HTTPS
    • 使用 rollup 打造自己的 npm 包 (全流程)
    • father-build 是如何工作的
  • 书籍
    • 图解 HTTP 协议
    • 编写可维护的 JavaScript
    • 鸟哥的 Linux 私房菜
    • JavaScript Promise迷你书
  • Linux
    • vimtutor
    • CURL 使用指南
  • Nginx
    • 一次 nginx 分享
  • Git
    • Git Commit Message 须知
    • .gitignore 模板
    • git tag标签
  • 摄影
    • 摄影基础知识
    • 手机摄影从小白到大师
  • 翻译
    • log4js
    • log4js-node
    • 介绍GitLab上的CI/CD
    • 为GitLab Pages创建并调整GitLab CI/CD
    • 关于 rel=noopener
    • AngularJS 团队 Git 提交信息约定
    • JSON Schema
  • Lifehack
    • 20 个 Google 搜索 Tips 来高效使用 Google
    • 37 个高级 Google 搜索 Tips
Powered by GitBook
On this page
  • 1. 基础
  • 2. 实现
  • 2.1 第一版,基础
  • 2.2 第二版,请求方法和路径
  • 2.3 第三版,中间件
  • 2.4 第四版,错误中间件
  • 2.5 第五版,处理 req 的参数
  • 3. TODO

Was this helpful?

  1. Node JS

实现简易的 express

1. 基础

最简单的服务器

const http = require('http');
const server = http.createServer(function (request, response) {
    // 在这里处理请求

    // 发送 HTTP 头部 
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, {'Content-Type': 'text/plain'});
    // 发送响应数据 "Hello World"
    response.end('Hello World\n');
});

server.listen(8888);

console.log("端口: 8888");

2. 实现

2.1 第一版,基础

首先 express 最简单的服务器代码如下:

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

app.get('/', function(req, res) {
  res.end('You send GET request')
})

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

即 express 是一个函数,函数执行能生成一个 app 对象,对象上具有 listen,于是我们可以写出以下的代码: express/index.js

const http = require("http");

const createServer = () => {
  // 这里取名为 app, 只是为了方便,实际上就是个处理请求的函数
  const app = function (req, res) {
    res.end('Response From Server')
  }

  app.listen = (...args) => {
    const server = http.createServer(app)
    server.listen(...args)
  }
  return app
}

module.exports = createServer;

2.2 第二版,请求方法和路径

在 express 中,能够针对请求的方法和路径,做对应的处理,比如:

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

app.get('/name', function(req, res) {
  res.end('name')
})

app.get('/age', function(req, res) {
  res.end('9')
})

// 统配所有方法和路径
app.all('*', function(req, res) {
  res.end('all response')
})

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

此时访问 localhost:3000/name 返回 name,访问 localhost:3000/age 返回 9,如果没有对应的路由,则会匹配到 all 这个兜底路由: 为了实现以上效果,我们需要扩展我们的 express/index.js :

const http = require("http");
const url = require('url');
const { METHODS } = http;

// METHODS 为
// [
//   'ACL',         'BIND',       'CHECKOUT',
//   'CONNECT',     'COPY',       'DELETE',
//   'GET',         'HEAD',       'LINK',
//   'LOCK',        'M-SEARCH',   'MERGE',
//   'MKACTIVITY',  'MKCALENDAR', 'MKCOL',
//   'MOVE',        'NOTIFY',     'OPTIONS',
//   'PATCH',       'POST',       'PROPFIND',
//   'PROPPATCH',   'PURGE',      'PUT',
//   'REBIND',      'REPORT',     'SEARCH',
//   'SOURCE',      'SUBSCRIBE',  'TRACE',
//   'UNBIND',      'UNLINK',     'UNLOCK',
//   'UNSUBSCRIBE'
// ]

const createServer = () => {
  // 这里取名为 app, 只是为了方便,方便在函数中使用 this,因为 listen 方法也在 app 身上,实际上就是个处理请求的函数
  const app = function (req, res) {
    // 当请求真正到来的时候,调用先前存在的 handles,一一进行对比参数和方法,如果匹配则执行
    for (let i = 0; i < app.handles.length; i++) {
      const { method, path, handler } = app.handles[i];
      // 取出请求的方法
      const reqMethod = req.method.toLowerCase();
      // 取出请求的路径
      const { pathname } = url.parse(req.url, true)
      // 如果方法,路径匹配,则进行处理, 同时处理 all
      if( (method === reqMethod || method === 'all') && (pathname === path || path === '*')) {
        handler(req, res)
      }
    }
    // 如果前面没有 res.end, 就会执行这个默认返回
    res.end('Default Response')
  }

  app.handles = [];  // 存放 layer: (路由和中间件)

  app.listen = function (...args) {
    const server = http.createServer(app)
    server.listen(...args)
  }

  // 给 app 添加 get, post 等 http 请求方法
  // 每个方法其实给 handles 推送一个 layer
  METHODS.forEach(method => {
    method = method.toLowerCase();
    app[method] = function (path, handler) {
      app.handles.push({
        method,
        path,
        handler
      })
    }
  })

  // 处理 all
  app.all = function (path, handler) {
    app.handles.push({
      method: 'all',
      path,
      handler
    })
  }

  return app
}

module.exports = createServer;

2.3 第三版,中间件

在 express 中,可以使用中间件,中间件可以在执行路由之前,对请求进行处理

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

// 中间件
const middle1 = function (req, res, next) {
  console.log('middle1')
  // 必须使用 next 才会进入下一个中间件
  next()
}

const middle2 = function (req, res, next) {
  console.log('middle2')
  next()
}

// 以下两种方式是一样的,即 use 没有第一个参数,则默认是 /, 即针对某个 url 使用中间件
app.use('/', middle1);  // 等价于 app.use(middle1)
app.use('/', middle2);

app.get('/name', function(req, res) {
  res.end('name')
})

app.get('/age', function(req, res) {
  res.end('9')
})

app.all('*', function(req, res) {
  res.end('all response')
})

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

那么继续修改我们的代码:

const http = require("http");
const url = require('url');
const { METHODS } = http;

// METHODS 为
// [
//   'ACL',         'BIND',       'CHECKOUT',
//   'CONNECT',     'COPY',       'DELETE',
//   'GET',         'HEAD',       'LINK',
//   'LOCK',        'M-SEARCH',   'MERGE',
//   'MKACTIVITY',  'MKCALENDAR', 'MKCOL',
//   'MOVE',        'NOTIFY',     'OPTIONS',
//   'PATCH',       'POST',       'PROPFIND',
//   'PROPPATCH',   'PURGE',      'PUT',
//   'REBIND',      'REPORT',     'SEARCH',
//   'SOURCE',      'SUBSCRIBE',  'TRACE',
//   'UNBIND',      'UNLINK',     'UNLOCK',
//   'UNSUBSCRIBE'
// ]

const createServer = () => {
  // 这里取名为 app, 只是为了方便,实际上就是个处理请求的函数
  const app = function (req, res) {
    // 取出请求的方法
    const reqMethod = req.method.toLowerCase();
    // 取出请求的路径
    const { pathname } = url.parse(req.url, true)

    // 中间件中的 next 函数,迭代 handles
    let index = 0;
    function next() {
      if(index === app.handles.length) {
        // 如果最终没有匹配到任何路由或中间件,那么返回 not found
        res.end('not found');
        return;
      }
      // 每次调用 next,就取下一个 layer
      const { method, path, handler } = app.handles[index++];
      if (method === 'middleware') {
        // 如果是中间件,且匹配到了
        if(path === '/' || path === pathname || pathname.startsWidth(path + '/')) {
          // 传入 next 函数
          console.log('匹配到了');
          handler(req, res, next);
        } else {
          // 没有匹配到,则调用下一个 layer
          next()
        }
      } else {
        // 如果方法,路径匹配,则进行处理, 同时处理 all
        if( (method === reqMethod || method === 'all') && (pathname === path || path === '*')) {
          handler(req, res)
        } else {
          // 如果没有找到,则调用下一个 layer
          next();
        }
      }
    }

    next();
  }

  app.handles = [];  // 存放 layer: (路由和中间件)

  app.listen = function (...args) {
    const server = http.createServer(app)
    server.listen(...args)
  }

  // 给 app 添加 get, post 等 http 请求方法
  // 每个方法其实给 handles 推送一个 layer
  METHODS.forEach(method => {
    method = method.toLowerCase();
    app[method] = function (path, handler) {
      app.handles.push({
        method,
        path,
        handler
      })
    }
  })

  app.all = function (path, handler) {
    app.handles.push({
      method: 'all',
      path,
      handler
    })
  }

  // use 方法
  app.use = function(path, handler) {
    // 如果不传 path,那么默认 path 为 '/'
    if(typeof handler !== 'function') {
      handler = path
      path = '/'
    }

    app.handles.push({
      method: 'middleware',  // 表示这是一个中间件
      path,
      handler
    })
  }

  return app
}

module.exports = createServer;

2.4 第四版,错误中间件

错误中间件,即当中间件使用参数调用 next() 函数时,该参数会被当做是 error,此时后续的中间件和路由都不会执行,而是直接执行错误中间件

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

// 中间件
const middle1 = function (req, res, next) {
  console.log('middle1')
  // 必须使用 next 才会进入下一个中间件
  next('12312')
}

const middle2 = function (req, res, next) {
  console.log('middle2')
  next()
}

// 以下两种方式是一样的,即 use 没有第一个参数,则默认是 /, 即针对某个 url 使用中间件
app.use('/', middle1);  // 等价于 app.use(middle1)
app.use('/', middle2);

app.get('/name', function(req, res) {
  res.end('name')
})

app.get('/age', function(req, res) {
  res.end('9')
})

app.all('*', function(req, res) {
  res.end('all response')
})

// 错误中间件, 约定该中间件参数为 4 个参数
app.use(function(err, req, res, next) {
  console.log('error', err);  // 打印 error 12312
})

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

那么继续改造我们的代码:

const http = require("http");
const url = require('url');
const { METHODS } = http;

// METHODS 为
// [
//   'ACL',         'BIND',       'CHECKOUT',
//   'CONNECT',     'COPY',       'DELETE',
//   'GET',         'HEAD',       'LINK',
//   'LOCK',        'M-SEARCH',   'MERGE',
//   'MKACTIVITY',  'MKCALENDAR', 'MKCOL',
//   'MOVE',        'NOTIFY',     'OPTIONS',
//   'PATCH',       'POST',       'PROPFIND',
//   'PROPPATCH',   'PURGE',      'PUT',
//   'REBIND',      'REPORT',     'SEARCH',
//   'SOURCE',      'SUBSCRIBE',  'TRACE',
//   'UNBIND',      'UNLINK',     'UNLOCK',
//   'UNSUBSCRIBE'
// ]

const createServer = () => {
  // 这里取名为 app, 只是为了方便,实际上就是个处理请求的函数
  const app = function (req, res) {
    // 取出请求的方法
    const reqMethod = req.method.toLowerCase();
    // 取出请求的路径
    const { pathname } = url.parse(req.url, true)

    // 中间件中的 next 函数,迭代 handles
    let index = 0;
    function next(err) {
      if(index === app.handles.length) {
        // 如果最终没有匹配到任何路由或中间件,那么返回 not found
        res.end('not found');
        return;
      }
      // 每次调用 next,就取下一个 layer
      const { method, path, handler } = app.handles[index++];

      if(err) {
        // next 有参数,则认为是错误, 那么跳过中间的中间件和路由,去到错误中间件(函数参数为4)
        if(handler.length === 4) {
          handler(err, req, res, next);
        } else {
          // 如果没有匹配到,则继续抛到下一个中间件或路由
          next(err);
        }
      } else {
        if (method === 'middleware') {
          // 如果是中间件,且匹配到了
          if(path === '/' || path === pathname || pathname.startsWidth(path + '/')) {
            // 传入 next 函数
            console.log('匹配到了');
            handler(req, res, next);
          } else {
            // 没有匹配到,则调用下一个 layer
            next()
          }
        } else {
          // 如果方法,路径匹配,则进行处理, 同时处理 all
          if( (method === reqMethod || method === 'all') && (pathname === path || path === '*')) {
            handler(req, res)
          } else {
            // 如果没有找到,则调用下一个 layer
            next();
          }
        }
      }
    }
    next();
  }

  app.handles = [];  // 存放 layer: (路由和中间件)

  app.listen = function (...args) {
    const server = http.createServer(app)
    server.listen(...args)
  }

  // 给 app 添加 get, post 等 http 请求方法
  // 每个方法其实给 handles 推送一个 layer
  METHODS.forEach(method => {
    method = method.toLowerCase();
    app[method] = function (path, handler) {
      app.handles.push({
        method,
        path,
        handler
      })
    }
  })

  app.all = function (path, handler) {
    app.handles.push({
      method: 'all',
      path,
      handler
    })
  }

  // 中间件
  app.use = function(path, handler) {
    // 如果不传 path,那么默认 path 为 '/'
    if(typeof handler !== 'function') {
      handler = path
      path = '/'
    }

    app.handles.push({
      method: 'middleware',  // 表示这是一个中间件
      path,
      handler
    })
  }

  return app
}

module.exports = createServer;

2.5 第五版,处理 req 的参数

express 中,对 req, res 对象进行了封装,可以便捷的得到一些参数:

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

// 中间件
const middle1 = function (req, res, next) {
  console.log(req.path)  // 请求 http://localhost:3000/name 时,打印 /name
  console.log(req.hostname)  // 打印 localhost
  console.log(req.query)  // 打印 {}
  next()
}

const middle2 = function (req, res, next) {
  console.log('middle2')
  next()
}

app.use('/', middle1);
app.use('/', middle2);

app.get('/name', function(req, res) {
  res.end('name')
})

app.get('/age', function(req, res) {
  res.end('9')
})

app.all('*', function(req, res) {
  res.end('all response')
})

// 错误中间件, 约定该中间件参数为 4 个参数
app.use(function(err, req, res, next) {
  console.log('error', err);  // 打印 error 12312
  res.end('error')
})

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

这是因为 express 内部有内置的中间件,通过该中间件给 req 设置了属性, 代码如下:

const http = require("http");
const url = require('url');
const { METHODS } = http;

const createServer = () => {
  // 这里取名为 app, 只是为了方便,实际上就是个处理请求的函数
  const app = function (req, res) {
    // 取出请求的方法
    const reqMethod = req.method.toLowerCase();
    // 取出请求的路径
    const { pathname } = url.parse(req.url, true)

    // 中间件中的 next 函数,迭代 handles
    let index = 0;
    function next(err) {
      if(index === app.handles.length) {
        // 如果最终没有匹配到任何路由或中间件,那么返回 not found
        res.end('not found');
        return;
      }
      // 每次调用 next,就取下一个 layer
      const { method, path, handler } = app.handles[index++];

      if(err) {
        // next 有参数,则认为是错误, 那么跳过中间的中间件和路由,去到错误中间件(函数参数为4)
        if(handler.length === 4) {
          handler(err, req, res, next);
        } else {
          // 如果没有匹配到,则继续抛到下一个中间件或路由
          next(err);
        }
      } else {
        if (method === 'middleware') {
          // 如果是中间件,且匹配到了
          if(path === '/' || path === pathname || pathname.startsWidth(path + '/')) {
            // 传入 next 函数
            handler(req, res, next);
          } else {
            // 没有匹配到,则调用下一个 layer
            next()
          }
        } else {
          // 如果方法,路径匹配,则进行处理, 同时处理 all
          if( (method === reqMethod || method === 'all') && (pathname === path || path === '*')) {
            handler(req, res)
          } else {
            // 如果没有找到,则调用下一个 layer
            next();
          }
        }
      }
    }
    next();
  }

  app.handles = [];  // 存放 layer: (路由和中间件)

  app.listen = function (...args) {
    const server = http.createServer(app)
    server.listen(...args)
  }

  // 给 app 添加 get, post 等 http 请求方法
  // 每个方法其实给 handles 推送一个 layer
  METHODS.forEach(method => {
    method = method.toLowerCase();
    app[method] = function (path, handler) {
      app.handles.push({
        method,
        path,
        handler
      })
    }
  })

  app.all = function (path, handler) {
    app.handles.push({
      method: 'all',
      path,
      handler
    })
  }

  // 中间件
  app.use = function(path, handler) {
    // 如果不传 path,那么默认 path 为 '/'
    if(typeof handler !== 'function') {
      handler = path
      path = '/'
    }

    app.handles.push({
      method: 'middleware',  // 表示这是一个中间件
      path,
      handler
    })
  }

  // 内置中间件,设置 req
  app.use(function(req, res, next) {
    let { pathname, query } = url.parse(req.url, true);
    let hostname = req.headers.host.split(":")[0];

    req.hostname = hostname;
    req.path = pathname;
    req.query = query;
    next();
  })

  return app
}

module.exports = createServer;

3. TODO

  1. 路径参数 /user/:id

  2. 子路由

  3. res 的封装

  4. 模板的渲染

    参考资料

Previousiconfont 的使用Next高阶组件

Last updated 4 years ago

Was this helpful?

视频:

从零实现简易express框架
渐进式Express源码学习