基本功能

不急着写下第一行代码,而是先梳理一下就基本功能而言有哪些步骤。

  1. 在本地根据指定端口启动一个http server,等待着来自客户端的请求
  2. 当请求抵达时,根据请求的url,以设置的静态文件目录为base,映射得到文件位置
  3. 检查文件是否存在
  4. 如果文件不存在,返回404状态码,发送not found页面到客户端
  5. 如果文件存在:
    • 打开文件待读取
    • 设置response header
    • 发送文件到客户端
  6. 等待来自客户端的下一个请求

实现基本功能

代码结构

创建一个nodejs-static-webserver目录,在目录内运行npm init初始化一个package.json文件。

mkdir nodejs-static-webserver && cd "$_"
// initialize package.json
npm init

接着创建如下文件目录:

-- config
---- default.json
-- static-server.js
-- app.js

default.json

{
    "port": 9527,
    "root": "/Users/sheila1227/Public",
    "indexPage": "index.html"
}

default.js存放一些默认配置,比如端口号、静态文件目录(root)、默认页(indexPage)等。当这样的一个请求http://localhost:9527/myfiles/抵达时. 如果根据root映射后得到的目录内有index.html,根据我们的默认配置,就会给客户端发回index.html的内容。

static-server.js

const http = require('http');
const path = require('path');
const config = require('./config/default');

class StaticServer {
    constructor() {
        this.port = config.port;
        this.root = config.root;
        this.indexPage = config.indexPage;
    }

    start() {
        http.createServer((req, res) => {
            const pathName = path.join(this.root, path.normalize(req.url));
            res.writeHead(200);
            res.end(`Requeste path: ${pathName}`);
        }).listen(this.port, err => {
            if (err) {
                console.error(err);
                console.info('Failed to start server');
            } else {
                console.info(`Server started on port ${this.port}`);
            }
        });
    }
}

module.exports = StaticServer;

 

在这个模块文件内,我们声明了一个StaticServer类,并给其定义了start方法,在该方法体内,创建了一个server对象,监听rquest事件,并将服务器绑定到配置文件指定的端口。在这个阶段,我们对于任何请求都暂时不作区分地简单地返回请求的文件路径。path模块用来规范化连接和解析路径,这样我们就不用特意来处理操作系统间的差异。

app.js

const StaticServer = require('./static-server');

(new StaticServer()).start();

 

在这个文件内,调用上面的static-server模块,并创建一个StaticServer实例,调用其start方法,启动了一个静态资源服务器。这个文件后面将不需要做其他修改,所有对静态资源服务器的完善都发生在static-server.js内。

在目录下启动程序会看到成功启动的log:

> node app.js

Server started on port 9527

在浏览器中访问,可以看到服务器将请求路径直接返回了。

路由处理

之前我们对任何请求都只是向客户端返回文件位置而已,现在我们将其替换成返回真正的文件:

    routeHandler(pathName, req, res) {
        
    }

    start() {
        http.createServer((req, res) => {
            const pathName = path.join(this.root, path.normalize(req.url));
            this.routeHandler(pathName, req, res);
        }).listen(this.port, err => {
            ...
        });
    }

 

将由routeHandler来处理文件发送。

读取静态文件

读取文件之前,用fs.stat检测文件是否存在,如果文件不存在,回调函数会接收到错误,发送404响应。

   respondNotFound(req, res) {
        res.writeHead(404, {
            'Content-Type': 'text/html'
        });
        res.end(`<h1>Not Found</h1><p>The requested URL ${req.url} was not found on this server.</p>`);
    }

    respondFile(pathName, req, res) {
        const readStream = fs.createReadStream(pathName);
        readStream.pipe(res);
    }

    routeHandler(pathName, req, res) {
        fs.stat(pathName, (err, stat) => {
            if (!err) {
                this.respondFile(pathName, req, res);
            } else {
                this.respondNotFound(req, res);
            }
        });
    }

 

Note:

读取文件,这里用的是流的形式createReadStream而不是readFile,是因为后者会在得到完整文件内容之前将其先读到内存里。这样万一文件很大,再遇上多个请求同时访问,readFile就承受不来了。使用文件可读流,服务端不用等到数据完全加载到内存再发回给客户端,而是一边读一边发送分块响应。这时响应里会包含如下响应头:

Transfer-Encoding:chunked

默认情况下,可读流结束时,可写流的end()方法会被调用。

版权声明:本文为xuxiaoyu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xuxiaoyu/p/10512526.html