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
脚本,实现的不够优美,启动脚本与服务器在一个文件里,偶合度太高, 更好的做法是封装服务器的创建与启动功能,分离出专门的脚本去调用。
- 首先将
http.createServer
里的回调函数提取出来成onRequest
函数 - 将
http
模块创建的server
导出成单独的模块 - 将服务器启动过程封装到一个
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.js
和 requestHandler.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.js
里 start
方法添加两个参数(route, handler)
,然后在onRequest
里 添加调用
var pathname = url.parse(req.url).pathname;
route(pathname, handler);
需要添加 url
依赖:
var url = require('url');
使用 node index.js
启动服务器后访问
http://localhost:3000/start 与 http://loalhost:3000/upload 可以在命令端看到处理方法被调用的log输出
传递 request, response
所谓request是http对请求的抽象, request 是对回复的抽象,这两个是创建server时回调方法onRequest
可以获得的。
之前的路由其实只完成了一半,我们可以在命令端发现处理方法被调用了,但是实际上浏览器上看不出差别的。要做到浏览器获取不同数据,需要将对应请求的response
从 server
传递给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.js
将 request
传递给路由
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;
完整项目源码地址。