Nodejs入门 - 搭建一个简单的静态资源站点(一)

momo314相同方式共享非商业用途署名转载

最近 ASP.NET5 一直处于Beta状态,变化实在太多,更新实在太麻烦,用着实在坑,还是等RC版再玩吧。总不能闲着,Nodejs貌似有点意思,跟我一起来玩玩吧?

首先我们默认大家看到这里的时候已经对Javascript有一定的了解了,并基本知道Nodejs的大概情况了。

第一步 - Windows下的Nodejs环境安装

额,这个话题已经有一千多万人讲过了,就不细讲了。我们以5.0.0版本为例: 下载地址:官网下载地址 下载完之后直接安装就好了,安装程序默认会配置好node的环境变量

第二步 - 使用Nodejs搭建一个web服务器

本文的目标很简单,做一个静态资源站点嘛,然而,在Nodejs下,这有些不同,我们不单单需要实现这个站点,还需要实现一个web服务器,不过先别慌,这也很简单:

var http = require('http');

http.createServer(function (request, response) {

    //设置HttpHeader
    response.writeHead(200, {'Content-Type': 'text/plain'});

    //发送响应数据
    response.end('Hello World');
}).listen(8888);

//在服务器端打印调试信息
console.log('Server running at http://127.0.0.1:8888/');

OK,一个简单的Http服务器就这么实现了:首先引入http模块,然后用createServer创建一个服务器并传入一个function来执行具体的请求处理逻辑,再然后listen(8888)来监听8888端口,最后在服务器初始化完成之后打印一条调试信息:我已经在监听8888端口啦!

但是,这里有两个问题:

  1. 模块化:我们通过require来一如一个模块,同时使用exports来输出一个模块,一个模块就相当于其他语言中的一个class,具体内容稍后再说。
  2. 事件驱动的回调:我敢打赌,你在第一次听说Node的时候肯定伴随着一句话:“他很快!”。但是为什么呢?这和Javascript的工作方式有关,他是事件驱动的(当然还有另一个原因:异步)。你看,在createServer的时候你会传入一个匿名的回调函数,用来在请求到达的时候做点什么。而请求可能会在任何时候突然到达,这时候你的server就会调用这个回调函数来执行操作了,其他语言也有相关特性,不再详述。

第三步 - 运行你的HelloWorld

这真的非常非常简单,简单到只需要:

  1. 将上面的代码保存好,假设我们保存到了D:\demo\start.js
  2. 使用 Windows + R 调出cmd窗口
  3. 输入:node D:\demo\start.js
  4. 在浏览器中访问http://localhost:8888

如下图所示: 运行Nodejs运行Nodejs

第四步 - 通过URL确定需要返回的静态文件

说好的的搭建一个简单的静态资源站点嘛,最重要的就是根据我的URL返回对应的文件啦。那么,到底怎么来确定URL所指定的文件呢,额,还是看我们的 start.js

var http = require('http');
var url = require('url'); //首先,引入另一个重要的模块

http.createServer(function (request, response) {

    var pathname = url.parse(request.url).pathname;
    //设置HttpHeader
    response.writeHead(200, {'Content-Type': 'text/plain'});
    //发送响应数据
    response.end(pathname);

}).listen(8888);

好啦,重启我们的node应用,现在假装我们已经有一个静态文件了,然后从浏览器访问 http://localhost:8888/images/face.png

嗯。。大家那么聪明,猜也猜到了,这时候,我们的页面上应该显示成这样子: 打印pathname打印pathname

诶!有点意思了,可是仅仅这样还得远啊,那么怎么办呢?

第五步 - 返回一张图片

大家都知道,在HTTP请求中返回文件多半是试用流的方式,首先将文件从磁盘上读取到内存中,然后返回字节流。。。

我勒个去,这玩意,不会要自己写吧?那可麻烦了!

别担心,下面我们再引入一个牛XX的模块:fs。这个模块呢,是专门用来操作文件的,简介如下:

文件系统模块是一个简单包装的标准 POSIX 文件 I/O 操作方法集。可以通过调用 require("fs") 来获取该模块。文件系统模块中的所有方法均有异步和同步版本。

  • 文件系统模块中的异步方法需要一个完成时的回调函数作为最后一个传入形参。
  • 回调函数的构成由调用的异步方法所决定,通常情况下回调函数的第一个形参为返回的错误信息。
  • 如果异步操作执行正确并返回,该错误形参则为null或者undefined。如果使用的是同步版本的操作方法,一旦出现错误,会以通常的抛出错误的形式返回错误。
  • 可以用try和catch等语句来拦截错误并使程序继续进行。

好哒好哒!我已经跃跃欲试了!不过开始之前,还是让我们真的找一张face.png放到项目中吧,不要再骗自己了。。。

嗯!那么,我的目录为:/assets/iamges/face.png

var http = require('http');
var url = require('url');
var fs = require('fs'); //别忘了先把fs模块搞进来

http.createServer(function (request, response) {

    var pathname = url.parse(request.url).pathname;

    fs.readFile('D:\\demo\\assets\\images\\face.png', 'binary', function(err, file) {
        if (err) {
            response.writeHead(500, {'Content-Type': 'text/plain'});
            response.end(err);
        } else {
            response.writeHead(200, {'Content-Type': 'image/png'});
            response.write(file, 'binary');
            response.end();
        }
     });

}).listen(8888);

好啦,让我们再来访问一下 http://localhost:8888/images/face.png 吧 ^ ^ 返回静态文件返回静态文件

嗯。。。不错,正确返回啦!但是细心的小伙伴也发现了,不管我访问什么URL都会返回这张图片啊,什么 /images/face.png 都是我们一厢情愿自己骗自己啊!!!

这可不行,那么:

第六步 - 返回正确图片

var http = require('http');
var url = require('url');
var fs = require('fs');

http.createServer(function (request, response) {

    var pathname = url.parse(request.url).pathname;
    var realPath = 'D:\\demo\\assets' + pathname.replace(/\//, '\\');

    fs.exists(realPath, function (exists) {
        if (!exists) {
            response.writeHead(404, {'Content-Type': 'text/plain'});
            response.write('URL NOT FOUND: ' + pathname);
            response.end();
        } else {
            fs.readFile(realPath, 'binary', function(err, file) {
                if (err) {
                    response.writeHead(500, {'Content-Type': 'text/plain'});
                    response.end(err);
                } else {
                    response.writeHead(200, {'Content-Type': 'image/png'});
                    response.write(file, 'binary');
                    response.end();
                }
            });
        }
    });

}).listen(8888);

好啦,感觉自己棒棒哒!

我知道我知道,看着这段代码你肯定很不爽啊,最起码还有两个大问题需要解决呀:

  1. 这尼玛只能返回图片啊好不好!你这 Content-Type 都写死了啊,这不行啊。。。
  2. 'D:\\demo\\assets' + pathname.replace(/\//, '\\')这又是什么鬼啊,这明显应该搞成可配置的好不好,哪天想改个路径难不成还要改代码吗!

不知道大家有木有发现,其实这两个问题都可以通过配置文件来解决,静态资源文件路径就不说了, Content-Type 其实也不过就是个 {文件扩展名:Content-Type} 的字典而已。

第七步 - 引入配置模块

模块,对的,就是上面第二步中提到的模块,首先,我们来看下如何新建一个简单的模块:

// 文件路径: D:\demo\calc\calc.js
var add = function(a, b) {
    return a + b;
};

var minus = function(a, b) {
    return a - b;
};

exports.add = add;
exports.minus = minus;
// 在start.js中引用calc
var calc = require('./calc/calc');

console.log(calc.add(1, 2));
console.log(calc.minus(4, 3));

没错,就是这么简单,唯一需要注意的就是自己定义的模块在require的时候需要写好路径,而引用外部模块时并不需要。

那么,我们就开始来做一个configurations配置模块啦,首先在项目下新建一个javascript文件: \configurations\configurations.js

exports.configurations = {
    port: 8888,
    wwwroot: 'D:\\demo',
    assetspath: 'D:\\demo\\assets',
    mime: {
        'css':'text/css',
        'gif':'image/gif',
        'html':'text/html',
        'htm':'text/html',
        'ico':'image/x-icon',
        'jpeg':'image/jpeg',
        'jpg':'image/jpeg',
        'js':'text/javascript',
        'json':'application/json',
        'pdf':'application/pdf',
        'png':'image/png',
        'svg':'image/svg+xml',
        'swf':'application/x-shockwave-flash',
        'tiff':'image/tiff',
        'txt':'text/plain',
        'wav':'audio/x-wav',
        'wma':'audio/x-ms-wma',
        'wmv':'video/x-ms-wmv',
        'xml':'text/xml',
        'unknown':'text/plain'
    }
};

好啦,现在我们就可以在start.js中引用并使用配置文件了,对于 Content-Type ,我们先来获取pathname对应文件的扩展名,然后根据扩展名来获取对应的 Content-Type ,如果不存在呢,我们就直接返回 text/plain

var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');
var configurations = require('./configurations/configurations').configurations;

http.createServer(function (request, response) {

    var pathname = url.parse(request.url).pathname;
    var realPath = configurations.assetspath + pathname.replace(/\//, '\\');

    fs.exists(realPath, function (exists) {
        if (!exists) {
            response.writeHead(404, {'Content-Type': 'text/plain'});
            response.write('URL NOT FOUND: ' + pathname);
            response.end();
        } else {
            fs.readFile(realPath, "binary", function(err, file) {
                if (err) {
                    response.writeHead(500, {'Content-Type': 'text/plain'});
                    response.end(err);
                } else {
                    var ext = path.extname(realPath); 
                    ext = ext ? ext.slice(1) : 'unknown';
                    var contentType = configurations.mime[ext] || 'text/plain';

                    response.writeHead(200, {'Content-Type': contentType});
                    response.write(file, 'binary');
                    response.end();
                }
            });
        }
    });

}).listen(configurations.port);

嗯,大功告成,现在我们已经得到了一个非常非常简陋的静态资源文件站点,你可以在assets文件夹中随便扔一些乱七八糟的文件试一下啦。

至此,我们已经在Node.js中迈出了第一步,如果你对Node.js还有兴趣,欢迎继续关注后续博文。

在后续的博文中,我们将会:

  1. 重构项目,使其更加模块化
  2. 为其增加浏览器缓存功能
  3. 解决跨域限制问题
  4. 使用Nginx反向代理Nodejs站点

本文代码下载

✎﹏ 本文来自于 momo314和他们家的猫,文章原创,转载请注明作者并保留原文链接。