手撸手, 用 Node.js 摸一个仿 express 的 http 服务器

用 Node.js 能够快速搭建一个 http 服务器, 本文将会手把手从头搭建, 最后还会实现一个简易的 express 服务器, 开始吧~
javascript

1. 搭建基本雏形

首先, 搭建 http 服务器, 须要先引用 Node.js 的核心模块: http, 实际上这是一个对象, 利用 http 上的 createServer 方法来建立一个 http 服务器, createServer 方法须要接受一个事件函数, 用于处理请求和响应html

const http = require('http')
const server = http.createServer((req, res) => {
  // req 是请求对象, 能够获取请求的一些方法路径数据等属性
  // res 是响应对象, 能够进行设置响应数据等操做
})
复制代码

到如今, 这个 http 服务器的雏形已经基本搭建好了!
可是想一想还差点东西, 一个 url 里包含 协议 域名 端口, 咱们还没指定端口呢.前端

const http = require('http')
const server = http.createServer((req, res) => {
})
server.listen(8000) // 监听 8000 端口
复制代码

OK, 大功告成
java

2. 响应数据

如今这个 http 服务器的框架已经搭好了. 启动一下, 在浏览器输入localhost:8000试一下吧
什么? 你说你试了一下, 没有响应?
固然, 咱们这里尚未返回任何数据呢, 若是没有访问数据, 浏览器端确定是显示无响应的.
git

这里咱们先随便返回点数据给浏览器, 而后重启服务器github

const http = require('http')
const server = http.createServer((req, res) => {
  res.end('这是我返回的数据噢!')
})
server.listen(8000)
复制代码

相信你已经看到页面上显示的....一堆乱码了吧^_^, 是的, 由于 JavaScript 默认字符集对中文的支持很差. 咱们须要指定一下返回数据的 Content-Typeexpress

const http = require('http')
const server = http.createServer((req, res) => {
  res.setHeader("Content-Type","text/html;charset=utf-8")
  res.end('这是我返回的数据噢!')
})
server.listen(8000)
复制代码

3. 处理路由

接下来, 咱们须要对路由进行处理, 如今咱们无论访问什么路径, 都统一返回同样的数据.
接下来咱们实现一下, 访问 /a , /b, /c 三个路由, 返回不一样的数据
数组

以前说过, req 对象上存放着请求的一些属性. req.url 上记录着请求的路径, 获取后就能够判断访问路径来返回不一样的数据了浏览器

const {url} = req
复制代码

完整代码:服务器

const http = require('http')
const server = http.createServer((req, res) => {
  const {url} = req
  res.setHeader("Content-Type","text/html;charset=utf-8")
  if(url === '/a') res.end(`访问a路由`)
  else if(url === '/b') res.end(`访问b路由`)
  else if(url === '/c') res.end(`访问c路由`)
})
server.listen(8000)
复制代码

4. 处理查询参数

接下来, 咱们对查询参数作一下处理, 这时候, 是否是想到了什么, 上面咱们的 url 没有考虑到查询参数的状况, 路由里应该滤除掉查询参数, 咱们来一并处理

咱们知道, 查询参数的形式是 a=x&b=x, 这里为了方便使用, 咱们引用另外一个模块 querystring, 他能够把查询参数字符串切割成键值对形式

const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
  const {url} = req
  const path = url.split('?')[0]
  const query = querystring.parse(url.split('?')[1])  // 保存着查询参数的对象

  res.setHeader("Content-Type","text/html;charset=utf-8")

  if(path === '/a') res.end(`访问a路由, 查询参数对象为${JSON.stringify(query)}`)  // 返回序列化的查询参数
  else if(path === '/b') res.end(`访问b路由`)
  else if(path === '/c') res.end(`访问c路由`)
})
server.listen(8000)
复制代码

5.处理 POST 请求

OK, 接下来要作啥呢? 想了想, 咱们目前好像只处理了 GET 请求, 那咱们来处理一下 POST 请求吧.
同样的, 请求的 method 能够经过req.method获取
这里要注意: req 对象实现了 ReadableStream 接口, 咱们能够用信息流的方式读取传来的数据 (关于的概念能够看后面个人文章)

let postData = ''
req.on('data', chunk => {  // 接收数据流
  postData += chunk.toString()  // 拼接信息流, 注意chunk是二进制格式, 须要转为二进制
})
req.on('end', () => {
  // 接收数据流完毕的回调函数
})
复制代码

完整代码 :

const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
  const {url, method} = req
  const path = url.split('?')[0]
  const query = querystring.parse(url.split('?')[1])  

  res.setHeader("Content-Type","text/html;charset=utf-8")

  if(path === '/a' && method === 'GET') res.end(`访问a路由, 查询参数对象为${JSON.stringify(query)}`)
  else if(path === '/b' && method === 'GET') res.end(`访问b路由`)
  else if(path === '/c' && method === 'GET') res.end(`访问c路由`)
  else if(path === 'p' && method === 'POST'){
    let postData = ''
    req.on('data', chunk => {  // 接收数据流
      postData += chunk.toString()  // 拼接信息流, 注意chunk是二进制格式, 须要转为二进制
    })
    req.on('end', () => {
      res.end(`我接受到了数据了:${postData}`)
    })
  }
})
server.listen(8000)
复制代码

OK, 来回顾一下咱们作了什么:

  • 咱们建立了一个基本的 http 服务器
  • 对路由作了处理
  • 获取了查询参数
  • 处理了 POST 请求

6. 优化路由处理

咱们如今来回顾一下本身的代码, 能够看到, 在路由处理的部分有一堆的 if else, 假如每多一个路由就多一个 if , 那就太冗余了.

这里咱们用一个数组来存放一个个路由对象, 路由对象里包含了路径, 方法, 回调等必要信息

const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
  const {url, method: realMethod} = req
  const realPath= url.split('?')[0]
  const query = querystring.parse(url.split('?')[1])
  let router = []  // 存放路由信息 

  res.setHeader("Content-Type","text/html;charset=utf-8")
  
  router.push({
    path: '/a',
    method: 'GET',
    handler(req, res){
      res.end(`访问a路由, 查询参数对象为${JSON.stringify(query)}`)
    }
  })
  router.push({
    path: '/b',
    method: 'GET',
    handler(req, res){
      res.end(`访问b路由`)
    }
  })
  router.push({
    path: '/c',
    method: 'GET',
    handler(req, res){
      res.end(`访问c路由`)
    }
  })
  router.push({
    path: '/p',
    method: 'POST',
    handler(req, res){
      let postData = ''
      req.on('data', chunk => {
        postData += chunk.toString()
      })
      req.on('end', () => {
        res.end(`我接受到了数据了:${postData}`)
      })
    }
  })

  // 统一处理路由
  router.forEach(route => {
    let {path, method, handler} = route
    console.log(realPath, realMethod)
    if(realPath === path && realMethod === method){
      return handler()
    }
  })
})
server.listen(8000)
复制代码

7. 改写为 express 形式

是否是感受稍微好看一点了, 加一个路由就 push 一个路由对象.
咱们离最终目标很接近了, 接下来, 让咱们模仿 express , 写一个 express 形式的 http 服务器(^▽^)

先来看看 express 是怎么写的

const express = require("express");
const app = express(); 
app.get("/a", 
  (req, res) => { 
    res.end("a路由");
  }
);

app.get("/b", 
  (req, res) => {
    res.end('b路由');
  });

app.listen(3000, () => {
  console.log("Example app listen at 3000");
});
复制代码

能够看到, 导出的 express 是一个函数, 函数内部会 new 一个实例对象出来, 大概的架子即是这样.

const http = require('http')
class Express{
  
}
module.exports = function(){
    return new Express()
}
复制代码

接下来让咱们实现完整代码:

const http = require('http')
class Express{
  constructor(){
    this.router = []  // 存放路由对象
  }
  get(path, handler){
    this.router.push({
      path,
      method: 'GET',
      handler
    })
  }
  post(path, handler){
    this.router.push({
      path,
      method: 'POST',
      handler
    })
  }
  listen(port, listenCallback){
    const server = http.createServer((req,res) => {
      const {url, method:realMethod} = req
      const realPath = url.split('?')[0]
      this.router.forEach((route) => {  // 遍历路由对象
        const {path, method, handler} = route
        if(realPath === path && method === realMethod){
          handler(req, res)
        }
      })
    })
    server.listen(port)
    listenCallback()
  }
}

module.exports = function(){
  return new Express()
}
复制代码

到这里, 咱们已经简单将咱们的 http 服务器改写成 express 形式了, 不过考虑的细节还远远不够 express 那么完善.

撒花ヾ(◍°∇°◍)ノ゙

大三小前端一枚, 最近在写一些博客, 欢迎关注(^▽^)

我的博客地址 : github

相关文章
相关标签/搜索