http静态服务

582 阅读2分钟

一、http常用状态码


// 1xx
100  # 服务器接收到一部分,正在等待其余部分。
101  # websocket协议

// 2xx 成功
200  # 成功
204  # 请求成功但是没有返回具体内容
206  # 分片传输

// 3xx 重定向、缓存
301  # 永久重定向
302  # 临时重定向
304  # 缓存

// 4xx 客户端错误
400  # 参数错误,服务器解析不了参数
401  # 用户未登陆,且没有权限
403  # 访问被禁止,用户已登陆但是没有权限
404  # Not Found
405  # 客户端请求方法,服务端不支持
416  # 请求范围无效 Range: bytes 5-

// 5xx 服务端错误
500  # 服务器内部错误
502  # 网关错误
504  # 网关超时
505  # http版本不支持

二、简单请求和复杂请求


复杂请求或者跨域请求,都会发送options预检请求。

什么样的请求是复杂请求呢?

  • get和post默认是简单请求,如果自定义了header信息,这就会变成复杂请求。
  • 除get和post请求之外的请求都是复杂请求。

三、http静态服务


├─http-server
│  ├─bin
│  │  ├─config          # 命令行配置文件
│  │  └─www             # 配置命令行,启动静态服务入口
│  ├─src
│  |  ├─index.js        # 启动静态服务
|  |  ├─util.js         # 工具函数
│  |  └─template.html   # 展示文件夹列表的页面
|  ├─package.json       # 配置启动服务命令及依赖

1)配置启动命令

// package.json
"bin": {
    "fs": "./bin/www.js",
    "file-server": "./bin/www.js"
}
// 生成软链
npm link

2)配置命令行参数

// bin/config.js

// 命令行描述配置
const options = {
    'port': {
        option: '-p, --port <n>', // 根据commander 的 option('')
        default: 8080,
        usage: 'fs --port 3000',
        description: 'set fs port'
    },
    'gzip': {
        option: '-g, --gzip <n>',
        default: 1,
        usage: 'fs --gzip 0', // 禁用压缩
        description: 'set fs gzip'
    },
    'cache': {
        option: '-c, --cache <n>',
        default: 1,
        usage: 'fs --cache 0', // 禁用缓存
        description: 'set fs gzip'
    },
    'directory': {
        option: '-d, --directory <d>',
        default: process.cwd(),
        usage: 'fs --directory d:', // 禁用缓存
        description: 'set fs directory'
    }
}

module.exports = options;

3)命令行配置,静态服务入口

// bin/www.js

#! /usr/bin/env node

// 命令行的帮助文档
const program = require('commander');
const options = require('./config');
program.name('fs')
program.usage('[options]')

// 解析 当前运行进程传递的参数
const examples = new Set();
const defaultMapping = {};
Object.entries(options).forEach(([key, value]) => {
    // 示例配置
    examples.add(value.usage)
    // 默认命令行配置
    defaultMapping[key] = value.default;
    // commander命令
    program.option(value.option, value.description)
})

// 命令行执行--help打印的命令列表
program.on('--help', function () {
    console.log('\nExamples:')
    examples.forEach(item => {
        console.log(`  ${item}`)
    })
})

// 获取用户输入参数
program.parse(process.argv);
let userArgs = program.opts();

// 合并最终的参数 需要启动一个服务
let serverOptions = Object.assign(defaultMapping, userArgs);

// 启动一个服务
const Server = require('../src/index');
let server = new Server(serverOptions);
server.start();

4) ejs模板

// src/template.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <%dirs.forEach(dir=>{%>
        <li><a href="<%=dir.url%>"><%=dir.name%></a></li>
    <%})%>
</body>

5) 工具函数,用来获取ip

// src/util.js

const os =require('os');

// 获取ip
function getIp(){
    let interfaces = os.networkInterfaces();
    interfaces = Object.values(interfaces).reduce((memo,current)=>{
        return memo.concat(current);
    },[]);
    let ip = interfaces.find(item=>{
        return item.family === 'IPv4' && item.cidr.startsWith('172')
    });
    return ip
}

this.getIp = getIp

6)读取配置,启动服务,展示页面

const http = require('http');
const url = require('url');
const path = require('path');
const util = require('./util');
const fs = require('fs').promises
const chalk = require('chalk');
const mime = require('mime');
const { createReadStream, readFileSync } = require('fs');
// ejs解析模板
const ejs = require('ejs');
const template = readFileSync(path.resolve(__dirname, 'template.html'), 'utf8');
class Server {
    constructor(serverOptions) {
        this.port = serverOptions.port;
        this.directory = serverOptions.directory;
        this.cache = serverOptions.cache;
        this.gzip = serverOptions.gzip
        this.handleRequest = this.handleRequest.bind(this);
        this.template = template;
    }
    async handleRequest(req, res) {
        // 1. 解析路径  =>  /src
        let { pathname } = url.parse(req.url);
        // 2. 文件命名是中文,解析会出问题,需要decode一下
        pathname = decodeURIComponent(pathname)
        // 3. 完整路径,用户不传directory,则默认cwd()的目录
        // C:\Users\Desktop\http-server\http-server\src
        let requestFile = path.join(this.directory, pathname);

        try {
            let statObj = await fs.stat(requestFile);
            // 如果是文件夹
            if (statObj.isDirectory()) {
                // 读取文件夹下的文件
                // [ 'index.js', 'template.html', 'util.js' ]
                const dirs = await fs.readdir(requestFile);

                // 根据数据和模板渲染内容
                let fileContent = await ejs.render(this.template, {
                    dirs: dirs.map((dir) => ({
                        name: dir,
                        url: path.join(pathname, dir)
                    }))
                });

                res.setHeader('Content-Type', 'text/html;charset=utf-8');
                
                // 将模板替换后的文件内容,返回给浏览器进行渲染
                res.end(fileContent);
            // 如果是文件,则发送文件
            } else {
                this.sendFile(req, res, requestFile)
            }
        } catch (e) {
            console.log(e)
            this.sendError(req, res, e);
        }
    }
    // 根据路径边读编写,输出给浏览器解析
    sendFile(req, res, requestFile) {
        // 返回文件,需要给浏览器提供内容类型 和 内容的编码格式,浏览器才能识别出来去解析
        res.setHeader('Content-Type', mime.getType(requestFile) + ';charset=utf-8');
        // 将文件读取出来并且返回
        // 如果不结束,浏览器相当于没有接受完毕
        createReadStream(requestFile).pipe(res); // 流. ws.write() ws.write() ws.end()
    }
    // 访问文件地址不存在
    sendError(req, res, e) {
        res.statusCode = 404;
        res.end(`Not Found`)
    }
    start() {
        // 启动服务监听错误信息,如果端口占用 累加1
        const server = http.createServer(this.handleRequest);
        server.listen(this.port, () => { // 订阅方法 监听成功后会触发
            console.log(chalk.yellow('Starting up http-server, serving ./'))
            console.log(chalk.yellow('Available on:'));
            console.log(`http://` + util.getIp().address + `:${chalk.green(this.port)}`);
            console.log(`http://127.0.0.1:${chalk.green(this.port)}`);
        });

        // 如果报错是EADDRINUSE,端口占用,则+1监听新端口
        server.on('error', (err) => {
            if (err.errno === 'EADDRINUSE') {
                server.listen(++this.port)
            }
        })
    }
}
module.exports = Server;

接下来,我们调一调,看一看:

cd http-server
fs --port 3000

image.png