nodejs 入门

参考一本非常好的入门书籍

依赖的东西:

  • node@4.2.1
  • npm@2.14.6

创建服务器

创建文件夹 start, 在目录上使用如下命令 初始化项目:

npm init

根据提示输入项目信息,会在项目目录下生成node的标准包配置文件 package.json

{
  "name": "start",
  "version": "1.0.0",
  "description": "Node.js start",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Albert Chen",
  "license": "MIT"
}

项目入口文件是 index.js, 现在还没有。首先新建一个 server.js 文件,这里使用 http 模块配置一个服务器。

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type':'text/plain'});
    res.write('Hello World');
    res.end();
});

server.listen(3000);
console.log('Server started at: ' + 3000);

使用命令node server.js 启动服务器,访问 http://localhost:3000 可以访问。

封装服务器

前边启动服务器是直接运行server.js脚本,实现的不够优美,启动脚本与服务器在一个文件里,偶合度太高, 更好的做法是封装服务器的创建与启动功能,分离出专门的脚本去调用。

  1. 首先将 http.createServer里的回调函数提取出来成 onRequest 函数
  2. http模块创建的server 导出成单独的模块
  3. 将服务器启动过程封装到一个start方法,然后开放出去

重构后代码为:

var http = require('http');

function start() {
        function onRequest(req, res) {
            res.writeHead(200, {'Content-Type':'text/plain'});
            res.write('Hello World');
            res.end();
        }
    var server = http.createServer(onRequest);
    server.listen(3000);
    console.log('Server started at: ' + 3000);
}

exports.start = start;

新建一个入口脚步(和之前 package.json 里配置的一样,导入之前的 server 模块,调用模块的 start方法

var server = require('./server');

server.start();

这样就可以使用命令node index.js 启动。

创建路由

之前服务器是写死了,只能输出特定的字符串,希望在其基础上修改对不同的访问路径输出不同的东西。获取请求的路径可以使用 url 模块,输出不同的东西是不同的处理过程,使用不同的处理方法封装,而路径与处理方法之间的映射使用 js 的对象。按照书中具体的过程如下:

新建处理方法的模块 requestHandler.js

function start() {
    console.log('Request handler start was called');
}

function upload() {
    console.log('Request handler upload was called');
}

exports.start = start;
exports.upload = upload;

新建路由模块 router.js与映射对象handler

分析一下:路由的功能是根据路径来调用之前的处理方法,封装成一个模块后,应该被server使用,可以直接在 server里导入,但是按照解耦合与依赖注入的思想,server模块本身就是在 index.js 里导入使用的,可以将所有模块的依赖导入集中到一起管理,通过参数传递使用。router.js又依赖于 requestHandler.js 模块, 所以路由这部分的项目结构是:

index.js里导入 router.jsrequestHandler.js , 并配置路径与处理方法的映射; server.js通过参数获得 router.js模块, router.js通过参数获取映射配置handler对象。

index.js内容为:

var server = require('./server');
var router = require('./router');
var requestHandler = require('./requestHandler');

var handler = {};
handler['/'] = requestHandler.start;
handler['/start'] = requestHandler.start;
handler['/upload'] = requestHandler.upload;

server.start(router.route, handler);

router.js 内容:

function route(pathname, handler) {
    if(typeof handler[pathname] == 'function') {
        handler[pathname]();
    } else {
        console.log('Request handler for ' + pathname + ' not found');
    }
}

exports.route = route;

server.jsstart 方法添加两个参数(route, handler),然后在onRequest里 添加调用

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

route(pathname, handler);

需要添加 url依赖:

var url = require('url');

使用 node index.js 启动服务器后访问

http://localhost:3000/starthttp://loalhost:3000/upload 可以在命令端看到处理方法被调用的log输出

传递 request, response

所谓request是http对请求的抽象, request 是对回复的抽象,这两个是创建server时回调方法onRequest可以获得的。

之前的路由其实只完成了一半,我们可以在命令端发现处理方法被调用了,但是实际上浏览器上看不出差别的。要做到浏览器获取不同数据,需要将对应请求的responseserver传递给router 再传递给requestHander, requestHander里对应的处理方法调用此 resonse返回数据给浏览器。

server.js修改:

//调用时传递
route(pathname, handler, res);
//删除对res的调用
//res.writeHead(200, {'Content-Type':'text/plain'});
//res.write('Hello World');
//res.end();

router.js修改:

//声明时多一个参数
function route(pathname, handler, res)
//调用时传递
handler[pathname](res);

requestHandler.js 修改为:

function start(res) {
    res.writeHead(200, {'Content-Type':'text/plain'});
    res.write('start');
    res.end();
}

function upload(res) {
    res.writeHead(200, {'Content-Type':'text/plain'});
    res.write('upload');
    res.end();
}

exports.start = start;
exports.upload = upload;

上传附件功能

按照之前的步骤,一个简单的nodejs web 小示例就完成了,麻雀虽小,五脏具全。可以在其基础上继续添加新的路由映射,重构以丰富其功能。

如下是书籍上对上传附件功能的简单实现:

index.js 添加路由映射:

handler['/show'] = requestHandler.show;

server.jsrequest传递给路由

        route(pathname, handler, res, req);

router.js 继续传递 request

function route(pathname, handler, res, req)

requestHandler.js 完善:

var fs = require('fs');
var formidable = require('formidable');
function start(res, req) {
    var body = '<html>'+
        '<head>'+
        '<meta http-equiv="Content-Type" content="text/html; '+
        'charset=UTF-8" />'+
        '</head>'+
        '<body>'+
        '<form action="/upload" enctype="multipart/form-data" '+
        'method="post">'+
        '<input type="file" name="upload" multiple="multiple">'+
        '<input type="submit" value="Upload file" />'+
        '</form>'+
        '</body>'+
        '</html>';
    res.writeHead(200, {'Content-Type':'text/html'});
    res.write(body);
    res.end();
}
function upload(res, req) {
    var form = new formidable.IncomingForm();
    form.parse(req, function(err, fields, files) {
        fs.renameSync(files.upload.path, '/tmp/test.png');
        res.writeHead(200, {'Content-Type':'text/html'});
        res.write('<img src="/show" />');
        res.end();
    });
}
function show(res, req) {
    fs.readFile('/tmp/test.png', 'binary', function(err, file) {
        if(err) {
            res.end('File not found');
        } else {
            res.writeHead(200, {'Content-Type':'image/png'});
            res.write(file, 'binary');
            res.end();
        }
    });
}
exports.start = start;
exports.upload = upload;
exports.show = show;

完整项目源码地址