上一节我们实现了手动控制路由的示例,这节我们来做一个完整的示例
目录结构
基于前面的一些铺垫,这节会做一个完整的示例。目录的文件结构如下 :
- node-express-pug ==>项目目录
- package.json ==>依赖文件
- server.js ==> 入口文件
- start.js ==> 创建服务
- router.js ==> 路由中转
- handlers.js ==> 路由处理
- views/home.html ==>首页
- files/ ==>存放上传文件的目录
- node_modules/ ==>依赖项文件目录
package.json
首先我们安装依赖项,本示例中的package.json
文件如下:
{
"name": "application-name",
"version": "0.0.1",
"dependencies": {
"formidable": "latest",
"mime": "~1.3.4"
}
}
其中,formidable
用于处理form表单数据,mime
是一个互联网标准类型,通过设定它就可以设定文件在浏览器的打开方式。稍后我们会看到如何使用它们。在项目的文件夹下npm install
完成安装依赖项。
server.js
首先我们来实现server.js。在server.js中,我们主要实现服务器的入口功能,调用各个模块组件,由其他模块实现具体的功能。
var server = require('./start');
var router = require('./router');
var handlers = require('./handlers');
var handler = {};
handler[['/','GET']] = handlers.home;
handler[['/show','GET']] = handlers.show;
handler[['/upload','POST']] = handlers.upload;
server.start(router.route,handler);
先导入我们创建的其他的模块文件,然后定义一个handlder对象,这里配置了对应的路由路径和其对应的处理方法,类似handler[['/','GET']] = handlers.home;
,然后又调用了server
模块的start()
方法,并传入两个参数,分别是router.route
方法和handler
对象。下面我们就来实现start.js
文件。
start.js
start.js
的主要功能是负责创建一个server服务,然后对请求的路由进行判断和过滤,交由router
模块进行处理具体的请求路由。完整的代码如下
//1.引用模块
var http = require('http');
var url = require('url');
var formidable = require('formidable');
var querystring = require('querystring');
//2.start(route,handler)方法
//方法的参数是由上层传递过来,分别是router.route方法和handler对象
//其中route负责处理路由
//handler对象里是我们预先定义的可用处理的路由和对应的请求类型
function start(route,handler) {
console.log("Start Begin");
//3.监听1337端口,创建服务器
var port = process.env.port || 1337;
http.createServer(onRequest).listen(port);
//4.创建服务器的回调方法
function onRequest(req, res) {
console.log("Request Begin");
//解析请求的路径名
var pathname = url.parse(req.url).pathname;
var query = url.parse(req.url).query;
//POST方法处理
if (req.method === 'POST'){
//解析form表单POST方式提交数据
var form = new formidable.IncomingForm();
//解析路径的请求参数,包装成data向下传递
//function (err, fields, files)是解析成功的回调方法
form.parse(req, function (err, fields, files) {
if (err){
console.error(err.message);
return;
}
var data = {fields:fields, files:files};
execute(pathname,handler,req, res, data);
});
}
//GET方法处理请求
if (req.method === 'GET'){
var data = {
//解析路径的请求参数,包装成data向下传递
fields: querystring.parse(query)
};
execute(pathname,handler,req, res, data);
}
console.log("Request End");
}
//5.执行处理后的请求
function execute(pathname, handler, req, res, data) {
//route执行返回值,如果发生错误,统一返回400
var content = route(pathname,handler,req,res,data);
if (!content){
res.writeHead('400',{
"Content-Type":"text/plain"
});
console.log(req.url);
res.write("400 Bad Request");
res.end();
}
}
}
//6.导出模块
exports.start = start;
这里注意
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) { });
form.parse
会解析出很多的属性,传给回调参数files
,下面是一个我上传文件后打印的请求参数,参考如下:
{ fields: {},
files:
{ fn:
File {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
size: 4510,
path: 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\upload_5990006963ce4f2
9de485ddadd819355',
name: 'sql.sql',
type: 'application/octet-stream',
hash: null,
lastModifiedDate: 2017-03-10T07:00:14.577Z,
_writeStream: [Object]
}
}
}
router.js
我们看到最后的处理交给了var content = route(pathname,handler,req,res,data);
,那么下面我们就来看router.js
中route()
方法的实现,完整的router.js
如下:
function route(pathname,handler,req,res,data) {
console.log('Route');
var method = req.method;
if (typeof handler[[pathname,method]] === 'function'){
return handler[[pathname,method]](res, data);
}else {
console.log("No Method found for " + pathname);
return null;
}
}
exports.route = route;
还记得我们的handler
对象的格式吗?我们的参数传入了解析后的pathname
,在这里又解析了请求的方式var method = req.method;
。typeof handler[[pathname,method]] === 'function'
,如果请求路由和请求不在我们的handler对象的中,即没有指定的方法调用,那么我们就直接返回null,如果有,就调用这个方法。打个比方handler[['/show','get']](res, data)
等同于handlers.show(res, data)
。
home.html
在home.html中我们定义了简单的form表单,实现一个上传文件的功能。
其中上传文件的input标签的name="fn"
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HOME</title>
</head>
<body>
<p>This is Home</p>
<h1>File Manager</h1>
<hr>
<a href="/show">Show All Files</a>
<form action="/upload" method="post" enctype="multipart/form-data" >
<input type="file" name="fn">
<input type="submit" value="Upload File">
</form>
</body>
</html>
handlers.js
这里才是我们对各个正确路由的响应。以下是完整的代码,我会逐一解释说明。
//1.引入模块
var fs = require('fs');
var path = require('path');
var mime = require('mime');
//2.home方法,即浏览器请求`http://127.0.0.1:1337`的方法
//这里我们直接读取views/home.html文件,返回将页面展现出来
function home(res, data) {
fs.readFile('views/home.html', function (err, data) {
res.writeHead(200, {"Content-Type": "text/html"});
//注意这里的data并不是home的参数,而是读取文件成功后的回调data
res.write(data);
res.end();
});
return true;
}
//3.show方法
//show方法会处理两种类型的请求,两种类型的请求都是get类型
//data.fields && data.fields['fn'] 这里处理的有上传的文件后,点击文件的链接下载文件
function show(res, data) {
//解析参数的data的值,这里用fn是因为后面form表单中定义的值是fn
if (data.fields && data.fields['fn']){
//获取文件名称
var name = data.fields['fn'];
//取得文件完整名称
var file = path.join(__dirname, '/files', name);
//通过文件名指定mime类型
var mimeType = mime.lookup(file);
//设定响应头
res.setHeader('Content-disposition','attachment;filename=' + name);
res.setHeader('Content-Type',mimeType);
//读取文件数据
var fileData = fs.readFileSync(file,'binary');
//响应给给用户
res.end(fileData, 'binary');
//这里会直接返回,并不会走下面的方法
return true;
}
//如果用户不是点击的下载链接进行show方法
//那么就读取文件列表显示在界面上,并且提供下载功能
fs.readdir('files', function (err, list) {
console.log(list);
res.writeHead(200, {"Content-Type": "text/html"});
var html = '<html><head></head>' +
'<body><h1>File Manager</h1>';
//有文件就生成文件列表
if (list.length) {
html += "<ul>";
for (i = 0; i < list.length; i++) {
html += '<li><a href="/show?fn=' +
list[i] + '">' +
list[i] + '</a></li>';
}
html += "</ul>";
} else {
//没有文件
html += '<h2>No files found</h2>';
}
html += '</body></html>';
res.write(html);
res.end();
});
return true;
}
//4.上传文件的方法,方法是响应home.html中的form表单
function upload(res, data) {
var temp = data.files['fn'].path;
var name = data.files['fn'].name;
//调用复制文件的方法
copyFile(temp,path.join('./files',name),function (err) {
if (err){
console.log(err);
return false;
}else {
return true;
}
});
}
//定义复制文件的方法
function copyFile(source, target, callback) {
//读文件流
var rs = fs.createReadStream(source);
rs.on('error',function (err) {
callback(err);
});
//写文件流
var ws = fs.createWriteStream(target);
ws.on('error',function (err) {
callback(err);
});
ws.on('finish',function () {
callback();
});
//写入,并覆盖源文件的内容
rs.pipe(ws);
}
//导出方法
exports.home = home;
exports.show = show;
exports.upload = upload;
小结
在项目文件夹下node server
启动http服务器,在浏览器中输入http://127.0.0.1:1337
在上传文件后点击Show All Files
,可以看到文件列表,点击其中一个,即可下载。
这样我们就完成了一个完整的node处理表单,文件上传和下载的示例。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。