Node.js 实现的简易服务器 (三)

index.js

const server = require('./server');
const router = require('./router');

server.start(router.route);

server.js

const http = require('http');  // Node.js 内置的 http 模块
const url = require('url');  // Node.js 内置的 url 模块


function start(route, port = 8080) {
    /**
     * 请求事件发生时调用的回调函数 (Node.js 是执行单线程异步非阻塞事件驱动的)
     */
    function requestListener(req, resp) {
        let pathname = url.parse(req.url).pathname;
        console.log(`Request for ${pathname} received.`);

        route(pathname, resp, req);
    }


    /**
     * 创建一个基础的 HTTP 服务器
     * 
     * 请求 http 模块提供的函数 createServer 创建一个服务器对象, 并调用该对象的 listen 方法监听 8080 端口
     * 
     * See Also: 
     * createServer 函数的源码解析: https://blog.csdn.net/THEANARKH/article/details/88385964
     * Node.js 是事件驱动: https://segmentfault.com/a/1190000014926921
     */
    http.createServer(requestListener).listen(port);

    console.log(`Server has started on ${port}`);
}


module.exports = {
    start
}

router.js

const requestHandlers = require('./requestHandlers');

// 路由注册
const handler = {
    '/': requestHandlers.index,
    '/index': requestHandlers.index,
    '/start': requestHandlers.start,
    '/upload': requestHandlers.upload,
    '/show': requestHandlers.show,
};

// 路由处理
function route(pathname, resp, req) {
    console.log(`About to route a request for ${pathname}`);
    let handle = handler[pathname];
    if (typeof handle === 'function') {
        handle(resp, req);
        console.log(`${handle.name} was called`);
    } else {
        console.log(`No request handle found for ${pathname}`);
        requestHandlers.NotFound(resp);
    }
}


module.exports = {
    route
}

requestHandlers.js

const fs = require('fs');
const querystring = require('querystring');

// third-party modules
const formidable = require('formidable');


function index(resp) {
    fs.readFile('./index.html', function (err, data) {
        if (err) {
            ServerError(err, resp);
        } else {
            resp.writeHead(200, { 'Content-Type': 'text/html' });
            resp.write(data);
            resp.end();
        }
    });
}


function start(resp) {
    const exec = require('child_process').exec;
    let result = 'empty';

    setTimeout(function () {
        exec(`echo 'hello'`, function (err, stdout, stderr) {
            result = stdout;
            console.log(result);
            resp.writeHead(200,);
            resp.write(`You've sent: ${result}`);
            resp.end();  // 等待事件循环调用回调函数拿到结果后才完成响应
        });
    }, 10000);
}


function upload(resp, req) {
    let form = new formidable.IncomingForm();
    form.parse(req, function (err, fields, files) {
        if (err) {
            ServerError(err, resp);
        } else {
            let imgName = `${fields.imgName  || new Date().getTime().toString()}.jpg`;
            try {
                fs.renameSync(files.upload.path, imgName);
            } catch {
                imgName = `${new Date().getTime().toString()}.jpg`;
                fs.renameSync(files.upload.path, imgName);
            }
            resp.writeHead(200, { 'Content-Type': 'text/html' });
            resp.write(`<img src="/show?imgName=${imgName}">`);
            resp.end();
        }
    })
}


function show(resp, req) {
    let imgName = querystring.parse(req.url.split('?')[1]).imgName;
    fs.readFile(imgName, 'binary', function (err, file) {
        if (err) {
            ServerError(err, resp);
        } else {
            resp.writeHead(200, { 'Content-Type': 'image/jpg' });
            resp.write(file, 'binary');
            resp.end();
        }
    })
}


function NotFound(resp, req) {
    resp.writeHead(200, { 'Content-Type': 'text/html' });
    resp.write(`<h1>NOT FOUND</h1>`);
    resp.end();
}


function ServerError(err, resp, req) {
    resp.writeHead(500, { 'Content-Type': 'text/plain' });
    resp.write(err + '\n');
    resp.end();
}


module.exports = {
    index,
    start,
    upload,
    show,
    NotFound
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
</head>

<body>
    <form action="/upload" enctype="multipart/form-data" method="post">
        <input type="text" name="imgName">
        <input type="file" name="upload" multiple>
        <input type="submit" value="Upload file">
    </form>
</body>

</html>

特点

  • 请求处理程序进行阻塞操作时, 会阻塞其他请求的处理
    (原因: 主(执行)线程被阻塞代码阻塞后, 其余所有请求必须等待该阻塞代码处理完毕之后才能执行)
  • 请求处理程序进行非阻塞操作时, 可以正确返回响应内容
    (原因: 请求处理程序是以阻塞方式运行的, 非阻塞代码的回调函数执行时通过 resp 对象返回正确的响应内容)

总结

该服务器分为三层:

  • server - 用于启动服务器, 接收请求与返回响应
  • router - 用于路由注册和路由处理
  • requestHandlers - 用于编写业务逻辑, 实现各个请求处理函数

各层之间的通信顺序为: req -> server -> router -> requestHandlers -> router -> server -> resp, 采用将服务器响应对象传递给请求处理程序, 在请求处理程序中返回响应的方式, 使用了一个三方模块 formidable 更方便的处理表单, 同时新增了一个上传 jpg 图片的功能

姊妹篇见->Node.js 实现的简易服务器 (二) (非阻塞处理存在问题)

posted @ 2020-10-16 19:38  ayuuuuuu  阅读(111)  评论(0编辑  收藏  举报