Node.js(day2)
一、使用Node实现基本Apache的功能
在上一篇笔记中,我们提到如果打开一个文件需要进行一次url判断是繁琐的,我们希望我们的Node具有类似Apache这种web服务器的一个功能:将文件放到www这个文件夹下,我们只要输入对应的文件地址就能访问到相应文件。
之前我们是对每一个文件进行request.url
判断,再处理相应的Content-Type
,这样比较麻烦,现在我们对url进行统一处理即可:
- 要模拟www文件的功能,我们先将我们的项目放到www文件夹下:
- 统一处理访问www文件夹下文件的url
var http = require('http');
var fs = require('fs');
var server = http.createServer();
server.listen(8000);
console.log('server running');
server.on('request',function(request,response){
var url = request.url;
var rootDir = 'D:/Users/Administrator/Desktop/Node/www';//www文件夹路径
var filePath = '/index.html';//文件地址,默认打开index.html
if(url !== '/'){
filePath = url;
}
//根据url打开文件
fs.readFile(rootDir+filePath,function(err,data){
if(err){
return response.end('404,Not Fond!');
}
response.end(data);
});
});
原理是一样的,只不过进行了统一处理;浏览器其实会自动判断data的类型来处理文件,所以很多时候不用设置Content-Type。
二、案例:使用Node实现显示文件目录的效果
在浏览器中输入目录地址我们可以看到这样的效果:
现在使用Node来实现这个效果,我们需要知道fs模块的一个方法:readdir()
即读取目录:
var http = require('http');
var fs = require('fs');
var server = http.createServer();
server.listen(8000);
console.log('server running');
server.on('request',function(request,response){
var url = request.url;
var rootDir = 'D:/Users/Administrator/Desktop/Node/www';//www文件夹路径
//读取目录
fs.readdir(rootDir,function(err,data){
if(err){
return response.end('404,Not Fond!');
}
console.log(data);
});
});
可以发现,读取的数据data是以数组形式显示出对于路径下的文件(夹)名。
现在我们只需要将这些数据显示在页面中即可,怎么将数据变成网页效果呢,一般情况下我们可以使用模块代码来提高效率,类似这样的逻辑:
其实就是想数据填到对应的位置,而不用去关心相应的页面效果,页面效果交给模板代码来处理,我们只需要关注两件事:获取数据data;将data填入模板代码中并使用。
(这里的模板代码我们自己随便写一个好了)
1.编写模板代码
<!DOCTYPE html>
<html>
<head>
<title>DirShow</title>
</head>
<body>
<h1>XXXURL的索引</h1>
<div style="display: block;">
<a href="#">
<span>[上级目录]</span>
</a>
</div>
<table>
<thead>
<tr>
<th>名称</th>
<th class="detailsColumn">
大小
</th>
<th class="detailsColumn">
修改日期
</th>
</tr>
</thead>
<tbody id="tbody">
<tr>
<td><a href="#">css/</a></td>
<td>1k</td>
<td>2018/11/5 上午10:50:48</td>
</tr>
</tbody>
</table>
</body>
</html>
效果如上,我们把框起来的部分使用我们读取的data替换掉就好了:
2.获取数据
上面已经获取过,使用readdir()
方法来获取:
3.将数据填充到模板中并使用
我们发现模板代码其实就是字符串
而已,我们将数据填充进去,其实就是进行简单的字符串替换工作即可:
var http = require('http');
var fs = require('fs');
var server = http.createServer();
server.listen(8000);
console.log('server running');
server.on('request',function(request,response){
var url = request.url;
var rootDir = 'D:/Users/Administrator/Desktop/Node/www'+url;//根据请求路径显示
//创建模板代码
var contentTemplate = `
<tr>
<td><a href="#">filename</a></td>
<td>1k</td>
<td>2018/11/5 上午10:50:48</td>
</tr>
`;
var content = '';
//获取数据
fs.readdir(rootDir,function(err,data){
if(err){
return response.end('404,Not Fond!');
}
//将数据填充到模板代码中---字符串替换操作
data.forEach(function(value,index){
content += contentTemplate.replace('filename',value);
//更新和使用模板代码
});
var templateStr = `
<h1>${rootDir}的索引</h1>
<div style="display: block;">
<a href="#">
<span>[上级目录]</span>
</a>
</div>
<table>
<thead>
<tr>
<th>名称</th>
<th class="detailsColumn">
大小
</th>
<th class="detailsColumn">
修改日期
</th>
</tr>
</thead>
<tbody id="tbody">
${content}
</tbody>
</table>
`;
response.setHeader('Content-Type','text/html;charset=utf-8');
response.end(templateStr);
});
});
效果如下:
三、模板引擎
模板引擎是什么这里就不过多介绍了,从上面的案例中我们已近手动实现了模板引擎要做的事,处理字符串,更准确地说应该是将数据填充到模板代码这一字符串操作过程我们可以通过模板引擎来完成。
模板引擎有诸多优点,这里不再赘述,我们在使用中感受。比如art-yemplate
模板引擎。官网地址:art-yemplate
1.安装art-template
既然使用了Node.js我们就尽量使用npm来安装,新建一个文件夹code来保存代码:
2.在浏览器中使用art-template
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<mata charset=utf-8>
<script type="text/javascript" src="./code/node_modules/art-template/lib/template-web.js"></script>
</head>
<body>
<!--显示位置-->
<p id="show"></p>
<!--模板代码-->
<script type="text/html" id='demo'>
大家好,我叫{{ name }},
我喜欢{{each hobbies}}{{$value}} {{/each}}
</script>
<script type="text/javascript">
//数据
var data = {
name: '张三',
hobbies: ['看书','打游戏','写代码']
};
//使用template方法将数据填充到模板中
var html = template('demo',data);
//将模板显示到页面上
window.document.getElementById('show').innerHTML = html;
</script>
</body>
</html>
注意几点:
- 记得引用template-web.js文件
- 相关格式要求和api查看官网文档即可
- 模板引擎不关心格式以及内容,只是将{{}}表达式中的值进行数据填充,和我们手动replace处理字符串的操作是一样的。
- 模板引擎还有其他好处,比如根据语句来处理数据,内部引用等。比我们手动处理字符串要方便很多,算是一个介绍代码工作的工具,且运行速度很快,渲染接近原生js。(有的模板引擎稍慢)
3.在Node.js中使用art-template
我们直接在上面的案例上进行改动即可:
var http = require('http');
var fs = require('fs');
var template = require('art-template');//启用art-template模板引擎
var server = http.createServer();
server.listen(8000);
console.log('server running');
server.on('request',function(request,response){
var url = request.url;
var rootDir = 'D:/Users/Administrator/Desktop/Node/www'+url;//根据请求路径显示
//创建模板代码
var templateStr = `
<h1>{{dir}}的索引</h1>
<div style="display: block;">
<a href="#">
<span>[上级目录]</span>
</a>
</div>
<table>
<thead>
<tr>
<th>名称</th>
<th class="detailsColumn">
大小
</th>
<th class="detailsColumn">
修改日期
</th>
</tr>
</thead>
<tbody id="tbody">
{{each data}}
<tr>
<td><a href="#">{{$value}}</a></td>
<td>1k</td>
<td>2018/11/5 上午10:50:48</td>
</tr>
{{/each}}
</tbody>
</table>
`;
//获取数据
fs.readdir(rootDir,function(err,data){
if(err){
return response.end('404,Not Fond!');
}
//使用模板引擎填充数据
var htmStr = template.render(templateStr,{
dir:rootDir,
data:data
});
response.setHeader('Content-Type','text/html;charset=utf-8');
response.end(htmStr);
});
});
值得注意的点:
- 在Node.js中使用
require()
来加载模板引擎 - 使用模板引擎的
render()
方法来处理模板代码(在浏览器在中是template()方法) - 在浏览器中是使用id作为参数,在Node.js中直接使用模板代码作为参数即可。
- 注意模板代码要符合使用的模板引擎的规范。
${value}
是es6中模板字符串的变量表达式,而{{}}
是art-template模板引擎的表达式。
四、案例:留言板
需求:使用Node.js实现留言板功能,即在如下页面输入评论,首页显示评论,相关html和css代码如下:
1.准备工作
目录结构如下:
html代码(views文件夹下)
js/css/img(public文件夹下,其实只用到了bootstrap.css,可自行网上下载引用)
模板引擎代码(node_modules文件夹下)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>留言本</title>
<link rel="stylesheet" href="../public/css/bootstrap.css">
</head>
<body>
<div class="header container">
<div class="page-header">
<h1>Example page header <small>Subtext for header</small></h1>
<a class="btn btn-success" href="/post">发表留言</a>
</div>
</div>
<div class="comments container">
<ul class="list-group">
{{each comments}}
<li class="list-group-item">
<span style="color: green">{{ $value.name }}:</span>{{ $value.message }}
<span class="pull-right">{{ $value.dateTime }}</span>
</li>
{{/each}}
</ul>
</div>
</body>
</html>
post.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="../public/css/bootstrap.css">
</head>
<body>
<div class="header container">
<div class="page-header">
<h1><a href="/">首页</a> <small>发表评论</small></h1>
</div>
</div>
<div class="comments container">
<form action="/addComment" method="post">
<div class="form-group">
<label for="input_name">你的大名</label>
<input type="text" class="form-control" required minlength="2" maxlength="10" id="input_name" name="name" placeholder="请写入你的姓名">
</div>
<div class="form-group">
<label for="textarea_message">留言内容</label>
<textarea class="form-control" name="message" id="textarea_message" cols="30" rows="10" required minlength="1" maxlength="20"></textarea>
</div>
<button type="submit" class="btn btn-default">发表</button>
</form>
</div>
</body>
</html>
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>抱歉! 您访问的页面失联啦...</h1>
</body>
</html>
2.编写app.js文件(post提交)
var http = require('http');
var fs = require('fs');
var template = require('art-template');
var comments = [];
http.createServer(function(req,res){
var url = req.url;
var rootDir = 'D:/Users/Administrator/Desktop/Node/feadback';
var filePath = '/views/index.html';
if(url === '/addComment'){
//request的data事件
req.on('data',function(postdata){
//postdata是二进制数据,postdata.toString()返回的数据编码方式任然有问题,所以需要解码
var decodedata = decodeURIComponent(postdata.toString());//对表单数据进行解码--->name=xxx&massage=xxx
var data = {},key,value,arr;
decodedata.split('&').forEach(function(v){
arr = v.split('=');
key = arr[0];
value = arr[1] || '';
data[key] = value;
});
data['dateTime'] = new Date().toLocaleString();
comments.unshift(data);
});
url = '/';
}
if(url !== '/'){
filePath = url;
}
if(url === '/post'){
filePath = '/views/post.html';
}
if(url !== '/'){
fs.readFile(rootDir+filePath,function(err,data){
if(err){
fs.readFile(rootDir+'/views/404.html',function(err1,data1){
if(err1)
return res.end('404,Not Fond.');
return res.end(data1);
})
return;
}
res.end(data);
});
}else{
//index.html需要进行模板渲染
fs.readFile(rootDir+filePath,function(err,data){
if(err){
fs.readFile(rootDir+'/views/404.html',function(err1,data1){
if(err1)
return res.end('404,Not Fond.');
return res.end(data1);
})
return;
}
var html = template.render(data.toString(),{comments:comments});
res.end(html);
});
}
}).listen(8000);
console.log('server running...');
值得注意的一些细节:
- 注意
fileRead()
读取到的数据是二进制数据,要当做模板代码需要使用toString()
进行处理。 - 我们使用的是post提交,那么表单数据就不会显示在url上,我们需要使用
request的data事件
来获取post提交获得的数据。 - postdata也是二进制数据,且使用toString()方法后编码任然可能不是正常编码,需要使用
decodeURIComponent()
方法进行解码。 - 将表单提交的数据添加到数组中,使用模板引擎渲染到页面即可。
3.get提交的一些区别
首先,request.url会变成这样:
也就是多了后面的参数,那么if(url === '/addComment')就会失效,而request的data事件也不会触发。
所以如果是get提交,我们需要使用另外的处理方式。
- 首先使用url核心模板即:
var url = require('url');
- 使用url核心模板的
parse()
方法来处理request.url。
//将路径解析为一个方便操作的对象,
//第二个参数为 true 表示直接将查询字符串转为一个对象(通过 query 属性来访问)
var parseObj = url.parse(req.url, true);
- true这个参数很重要,否则parseObj.query会转化为字符串。
- 此时parseObj对象就包含了url的相关信息,如
parseObj.pathname
表示当前请求连接(不带hash参数,本例中即为'/addComment')。而parseObj.query
可以获取hash参数转化而成的对象,本例即:{name:xxx,messagexxx},且不用再使用decodeURIComponent()
进行解码也不会乱码。