第一次迭代

快速迭代是一种不错的开发方式,因此我们在第一次迭代时先实现服务器的基本功能。

设计

简单分析了需求之后,我们大致会得到以下的设计方案。

           +---------+   +-----------+   +----------+
request -->|  parse  |-->|  combine  |-->|  output  |--> response
           +---------+   +-----------+   +----------+

也就是说,服务器会首先分析URL,得到请求的文件的路径和类型(MIME)。然后,服务器会读取请求的文件,并按顺序合并文件内容。最后,服务器返回响应,完成对一次请求的处理。

另外,服务器在读取文件时需要有个根目录,并且服务器监听的HTTP端口最好也不要写死在代码里,因此服务器需要是可配置的。

实现

根据以上设计,我们写出了第一版代码如下。

var fs = require('fs'),
    path = require('path'),
    http = require('http');

var MIME = {
    '.css': 'text/css',
    '.js': 'application/javascript'
};

function combineFiles(pathnames, callback) {
    var output = [];

    (function next(i, len) {
        if (i < len) {
            fs.readFile(pathnames[i], function (err, data) {
                if (err) {
                    callback(err);
                } else {
                    output.push(data);
                    next(i + 1, len);
                }
            });
        } else {
            callback(null, Buffer.concat(output));
        }
    }(0, pathnames.length));
}

function main(argv) {
    var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),
        root = config.root || '.',
        port = config.port || 80;

    http.createServer(function (request, response) {
        var urlInfo = parseURL(root, request.url);

        combineFiles(urlInfo.pathnames, function (err, data) {
            if (err) {
                response.writeHead(404);
                response.end(err.message);
            } else {
                response.writeHead(200, {
                    'Content-Type': urlInfo.mime
                });
                response.end(data);
            }
        });
    }).listen(port);
}

function parseURL(root, url) {
    var base, pathnames, parts;

    if (url.indexOf('??') === -1) {
        url = url.replace('/', '/??');
    }

    parts = url.split('??');
    base = parts[0];
    pathnames = parts[1].split(',').map(function (value) {
        return path.join(root, base, value);
    });

    return {
        mime: MIME[path.extname(pathnames[0])] || 'text/plain',
        pathnames: pathnames
    };
}

main(process.argv.slice(2));

以上代码完整实现了服务器所需的功能,并且有以下几点值得注意:

  1. 使用命令行参数传递JSON配置文件路径,入口函数负责读取配置并创建服务器。

  2. 入口函数完整描述了程序的运行逻辑,其中解析URL和合并文件的具体实现封装在其它两个函数里。

  3. 解析URL时先将普通URL转换为了文件合并URL,使得两种URL的处理方式可以一致。

  4. 合并文件时使用异步API读取文件,避免服务器因等待磁盘IO而发生阻塞。

我们可以把以上代码保存为server.js,之后就可以通过node server.js config.json命令启动程序,于是我们的第一版静态文件合并服务器就顺利完工了。

另外,以上代码存在一个不那么明显的逻辑缺陷。例如,使用以下URL请求服务器时会有惊喜。

    http://assets.example.com/foo/bar.js,foo/baz.js

经过分析之后我们会发现问题出在/被自动替换/??这个行为上,而这个问题我们可以到第二次迭代时再解决。

友情链接
轻松育儿世界奇观
苏ICP备16066217号-2