构建一个本地的http-server

本文详细介绍如何使用Node.js的http模块创建一个本地静态文件服务器,包括服务搭建、URL解析、本地目录读取及缓存功能实现。

背景

为了加深对node中的http模块的理解,自己尝试写了一本地的静态服务。相信大部分人都用过http-server。会想起我自己第一次接触http-server是这样的,我要传一本电子书给同事,同事说你直接在你本地起一个server,这样我通过你本机的ip就可以下载了不用u盘,当时我就觉得很神奇。我们要实现的效果大致如下:

  • 打开localhost:3000会展示当前服务所在的目录
  • 可以点开当前的目录查看里面的文件
  • 对于html文件可以展示内容,执行嵌套在里面的js文件

目录结构

- bin目录:主要是用来配置服务器的端口以及启动服务 - public: 存放一些静态资源 - src: 文档的主要内容区域,包括启动服务,及后期测试静态服务器能否启动

配置文件

const config = {
    port: 3000,
    host: 'localhost',
    dir: process.cwd() // 获取当前文件所在的目录
}
复制代码

核心模块

这小节先给出核心骨架,然后下面再慢慢填坑。在开撸之前我们先捋捋思路。要在浏览器打开,那我们得起一个服务吧,起了服务之后,我们在url后面输入地址例如:localhost:3000/src得匹配到本地的真实路径吧,匹配到真实路径后,我们需要判断该路径是一个文件夹还是文件,如果是文件那么好办,就直接渲染就行,如果是文件夹,那么我们就循环出目录结构进行展示。所以总共需要一下四步:

  • 起一个服务
  • 根据url链接匹配出本地的目录
  • 读取本地目录
  • 判断是否为文件和文件夹分类处理
    接下来我们就一步一步填坑,首先我们来起一个服务
const config = require('../config') // 引入我们的配置文件
const http = require('http')
class Server {
    constructor () {
        this.config = config
    }
    start () {
        const { host, port } = config
        const server = http.createServer((req, res) => {
            res.end('hello static server')
        })
        server.listen(port, () => {
            console.log(`Server is running http://${host}:${port}`)
        })
    }
}

new Server().start()
module.exports = Server
复制代码

简单的服务构建完啦,这个也很容易理解,就是利用node的http模块起一个服务 接下来就是匹配出本地目录

const config = require('../config') // 引入我们的配置文件
const http = require('http')
const url = require('url')
const path = require('path')
class Server {
    constructor () {
        this.config = config
    }
    start () {
        const { host, port } = config
        // 如果接着在回调里面处理显得比较臃肿
        const server = http.createServer(this.handlerRequest.bind(this))
        
        server.listen(port, () => {
            console.log(`Server is running http://${host}:${port}`)
        })
    }
    handlerRequest (req, res) {
        // 我们引入url模块获取url上的路径
        const { dir } = this.config // 获取启动文件的根目录
        const { pathname } = url.parse(req.url) // localhost:3000/src ==> /src
        // 拼接出真实的本地路径 => 引入path模块
        const realPath = path.join(dir, pathname)
    }
}
new Server().start()
module.exports = Server
复制代码

这一步就是读取本地目录啦。

const config = require('../config') // 引入我们的配置文件
const http = require('http')
const url = require('url')
const path = require('path')
const util = require('util')
const fs = require('fs')
const stat = util.promisify(stat) // 对读取目录进行promisify
const readdir = util.promisify(readdir) 
const temple = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8')
class Server {
    constructor () {
        this.config = config
        this.template = template
    }
    start () {
        const { host, port } = config
        // 如果接着在回调里面处理显得比较臃肿
        const server = http.createServer(this.handlerRequest.bind(this))
        
        server.listen(port, () => {
            console.log(`Server is running http://${host}:${port}`)
        })
    }
    async handlerRequest (req, res) {
        // 我们引入url模块获取url上的路径
        const { dir } = this.config // 获取启动文件的根目录
        const { pathname } = url.parse(req.url) // localhost:3000/src ==> /src
        // 拼接出真实的本地路径 => 引入path模块
        const realPath = path.join(dir, pathname)
        try {
            let statObj = await stat(realPath)
            if (statObj.isDirectory()) {
                res.setHeader('Content-Type', 'text/html;charset=utf-8')
                let dirs = await readdir(realPath)
                dirs = dirs.map(p => {
                    return {
                        name: p,
                        path: path.join(pathname, p)
                    }
                })
                let tempStr = ejs.render(this.template, {
                    dirs
                })
                res.end(tempStr)
            } else {
                res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8')
                res.createReadStream(realPah).pipe(res)
            }
        }catch (e) {
            console.log(e)
        }
    }
}
new Server().start()
module.exports = Server
复制代码

添加缓存功能

http协议缓存有两种方式:

cache (req, res, statObj, realPath) {
      console.log('cache')
      /*
      * 缓存类型       服务端              客户端
      * 强制缓存       Expires             Expires
      *               Cache-Control       Cache-Control
      * 协商缓存       Last-Modified       if-modified-since
      *               Etag                if-none-match
      * */
      let etag = statObj.ctime.toGMTString() + statObj.size // 文件修改时间和文件大小
      let lastModified = statObj.ctime.toGMTString() // 文件的修改的时间
      res.setHeader('Cache-Control', 'no-cache')
      res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toGMTString()) // 毫秒为单位
      res.setHeader('Etag', etag)
      res.setHeader('Last-Modified', lastModified)
      let ifNoneMath = req.headers['if-none-match']
      let ifModifinedSince = req.headers['if-modified-since']
      if (etag != ifNoneMath) {
        return false
      }

      if (lastModified != ifModifinedSince) {
        console.log('Last-Modified')
        return false
      }
      return true
    }
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值