likes
comments
collection

使用 Node.js http 模块做一个 node-server

作者站长头像
站长
· 阅读数 14

完整代码:Gitee | GitHub

1. 最简单的服务器

http.createServer 返回 http.Server 的新实例,http.Server 类提供了一些可用的事件和方法,如 request事件、listen方法 等。

  • request 事件,在每次有请求时触发,它的事件监听函数接受两个参数:
  • listen 方法,用于启动 HTTP 服务器监听连接。
// index.js

const http = require('http')
const server = http.createServer()

server.on('request', (request, response) => {
  response.end('hello')
})

server.listen('8888', () => {
  console.log('服务启动成功,请使用 8888 端口打开')
})

使用 Node.js http 模块做一个 node-server

使用浏览器访问 http://127.0.0.1:8888/

使用 Node.js http 模块做一个 node-server

2. 根据 url 返回不同的内容

// index.js(部分代码)

const http = require('http')
const server = http.createServer()
const fs = require('fs')
const path = require('path')
const publicDir = path.resolve(__dirname, 'public')

server.on('request', (request, response) => {
  switch (request.url) {
    case '/':
    case '/index.html':
      response.setHeader('Content-Type', 'text/html; charset=utf-8')
      fs.readFile(path.resolve(publicDir, 'index.html'), (error, data) => {
        if (error) throw error
        response.end(data.toString())
      })
      break
    case '/style.css':
      response.setHeader('Content-Type', 'text/css; charset=utf-8')
      fs.readFile(path.resolve(publicDir, 'style.css'), (error, data) => {
        if (error) throw error
        response.end(data.toString())
      })
      break
    case '/main.js':
      response.setHeader('Content-Type', 'text/javascript; charset=utf-8')
      fs.readFile(path.resolve(publicDir, 'main.js'), (error, data) => {
        if (error) throw error
        response.end(data.toString())
      })
      break
    default:
      response.statusCode = 404
      response.end()
  }
})

现在,浏览器输入不同的 url,服务器就会返回不同的内容给浏览器,如:

使用 Node.js http 模块做一个 node-server

但是,当访问不存在的路径,或路径中含有查询参数时,浏览器会显示 404 页面,这是因为查询参数也被包含在了 request.url 中。

使用 Node.js http 模块做一个 node-server

3. 处理查询参数

使用 url 模块的 parse 方法,可以将字符串形式的 url 转成对象形式,如:

使用 Node.js http 模块做一个 node-server

// index.js(部分代码)

const url = require('url')

server.on('request', (request, response) => {
  const { pathname } = url.parse(request.url)  // 关键代码
  switch (pathname) {
    case '/':
    case '/index.html':
      // ...
    case '/style.css':
      // ...
    case '/main.js':
      // ...
    default:
      // ...
  }
})

现在,使用 pathname 去匹配 url 路径,即使 url 中带有查询参数,也能正确匹配。

使用 Node.js http 模块做一个 node-server

4. 匹配任意文件

现在,这个 node-server 只能匹配到已经设置好的 url,如果新增文件,那么就要新增相应的 url,这明显是不好的。我们希望新增文件时,可以不用修改服务器配置,浏览器也能访问到新增的文件。

// index.js(部分代码)

server.on('request', (request, response) => {
  const { pathname } = url.parse(request.url)
  // 从路径中取到文件的名字,如果是空字符串就默认显示 index.html
  const filename = pathname.substr(1) || 'index.html'   

  fs.readFile(path.resolve(publicDir, filename), (error, data) => {
    if (error) throw error
    response.end(data.toString())
  })

})

服务器中新增 jquery.js 并直接在浏览器中访问:

使用 Node.js http 模块做一个 node-server

5. 解决 bug

此时,当在浏览器中访问 main.js 时,会发现出现了乱码,这是由于没有设置 Content-Type 引起的。

使用 Node.js http 模块做一个 node-server

解决方案就是根据访问的文件给 Content-Type 设置不同的值:

// index.js(部分代码)

const typeMap = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8'
}

server.on('request', (request, response) => {
  const { pathname } = url.parse(request.url)
  const filename = pathname.substr(1) || 'index.html'
  const endType = '.' + filename.split('.')[1]    // 获取文件后缀
  
  fs.readFile(path.resolve(publicDir, filename), (error, data) => {
    if (error) {
      throw error
    } else {
      response.setHeader('Content-Type', typeMap[endType])
      response.end(data.toString())
    }
  })

})

此时再访问 main.js,就没有乱码了:

使用 Node.js http 模块做一个 node-server

6. 处理不存在的文件

// index.js(部分代码)

server.on('request', (request, response) => {
  // ...

  fs.readFile(path.resolve(publicDir, filename), (error, data) => {
    if (error) {
      if (error.errno === -4058) {         // 访问的资源不存在
        response.statusCode = 404
        fs.readFile(path.resolve(publicDir, '404.html'), (error, data) => {
          response.end(data.toString())
        })
      } else if (error.errno === -4068) {  // 访问目录
        response.statusCode = 403
        response.setHeader('Content-Type', 'text/plain; charset=utf-8')
        response.end('无权查看目录内容')
      } else {
        response.statusCode = 500
        response.setHeader('Content-Type', 'text/plain; charset=utf-8')
        response.end('服务器发生未知错误,请稍后重试')
      }
    } else {
      // ...
    }
  })

})

当访问不存在的资源:

使用 Node.js http 模块做一个 node-server

当访问的是某个目录:

使用 Node.js http 模块做一个 node-server

7. 处理非 GET 请求

因为这是一个静态服务器,所以我们要求它只能接受 GET 请求,不允许其他请求。

使用 Node.js http 模块做一个 node-server

上图中,当在页面上使用表单发送 POST 请求时,由于服务器不存在对应的资源,所以会返回 404。

// index.js(部分代码)

server.on('request', (request, response) => {
  // 处理非 GET 请求
  if (request.method !== 'GET') {
    response.statusCode = 405
    response.setHeader('Content-Type', 'text/plain; charset=utf-8')
    response.end('这是一个假的响应')
    return
  }
})

再次使用表单发送 POST 请求时,就会返回 405:

使用 Node.js http 模块做一个 node-server

8. 添加缓存选项

没有添加缓存之前:

使用 Node.js http 模块做一个 node-server

// index.js(部分代码)

server.on('request', (request, response) => {
  fs.readFile(path.resolve(publicDir, filename), (error, data) => {
    if (error) {
      // ...
    } else {
      response.setHeader('Content-Type', typeMap[endType])
      response.setHeader('Cache-Control', 'public, max-age=31536000')  // 设置缓存
      response.end(data.toString())
    }
  })

})

添加缓存之后:

使用 Node.js http 模块做一个 node-server

使用 Node.js http 模块做一个 node-server