第01节:概述
● 优势:性能高;跟前端JS配合方便;与前端技术栈更近,便于学习。
第02节:创建服务器
● 在NodeJS中自带有HTTP模块,可以直接使用require方法调用模块,然后使用HTTP模块新建一个服务器。例如:
const http = require('http');//调用组件并新建常量接收;
//使用组件创建服务器,服务器的参数是一个回调函数,服务器有访问便会调用回调函数,这个函数有两个参数,request是请求信息(输入),response是响应信息(输出);
var server = http.createServer(function (req, res) {
console.log('有人访问。');//服务器每次响应都会提示;
res.write('abc');//响应信息的输出方法;
res.end();//结束请求的方法;
});
server.listen(8181);//服务器需要监听端口来响应请求;
● 请求(request)参数的属性:req.url,可以获取请求的文件地址。
第03节:文件操作
● 在NodeJS中的文件操作依赖的是fs(File System)模块,下面是它的读和写方法:
1.读取方法fs.readFile(),例如:
const fs = require('fs');//导入文件处理模块;
//readFile(文件名, 回调函数);
//回调函数中的err是返回的错误信息,没有错误便返回null;data是请求到的数据,返回的是文件的二进制源码;
fs.readFile('test.txt', function (err, data) {
if (err) {
console.log('读取失败。')
} else {
console.log(data.toString());
}
});
2.写入方法fs.writeFile(),例如:
const fs = require('fs');
//writeFile(文件名, 内容, 回调函数);
//回调函数参数是错误信息,没有错误便返回null;
fs.writeFile('test.txt', 'test content', function (err) {
console.log(err);
});
3.创建服务器和文件操作结合使用示例:
const http = require('http');//导入HTTP模块;
const fs = require('fs');//导入文件操作模块;
var server = http.createServer(function (req, res) {//创建服务器;
var fileName = './www' + req.url;//处理文件请求的URL,将URL和本地文件夹合并为文件地址;
fs.readFile(fileName, function (err, data) {//读取文件;
if (err) {
res.write('404');//读取失败返回信息;
} else {
res.write(data);//读取成功返回文件;
}
res.end();//要在文件读取的函数内部结束响应;
});
});
server.listen(8181);//监听端口;
第04节:解析GET数据
● 当form表单使用GET方式提交数据时,可以使用request参数的url属性获得数据。
● 使用Query String组件的parse方法可以方便的解析使用GET方式提交的URL地址中的数据,直接将URL中的数据返回为一个JSON对象。例如:
const querysting = require('querystring');
var json = querysting.parse('user=testname&pwd=test1234');
console.log(json);//{ user: 'testname', pwd: 'test1234' }
● 解析GET数据还有更便捷的方式,就是URL组件中的parse方法,它能解析URL地址中包含的所有数据,方法的第二个参数传入true会将Query String数据解析出来。例如:
const urlLib = require('url');
var obj = urlLib.parse('user=testname&pwd=test1234', true);
var json = obj.query;
console.log(json);//{ user: 'testname', pwd: 'test1234' },obj的pathname属性解析出来是请求的文件路径;
第05节:解析POST数据
● 使用GET方式传送数据最大32K,而POST方式可以到1G。
● 使用POST方式传输的数据是在消息的Content部分,而GET数据在消息的Header部分;要解析POST数据需要使用request参数的on方法的data事件(参数)。例如:
const http = require('http');
http.createServer(function (req, res) {
var str = '';//保存接收到的数据,这里使用字符串是为了测试,不是正确用法;
var i = 0;//保存传输次数;
//data代表有一段数据到达,当数据体积比较大时,需要分段多次传输;
req.on('data', function (data) {//参数data代表每次接收到的数据;
console.log('第' + i++ + '次收到数据');//当数据体积较大时就会显示传输的次数;
str += data;//将收到的数据拼接起来;
});
//end代表数据已经全部传输完成;
req.on('end', function () {
console.log(str);
})
}).listen(8181);
● 小复习:
//导入基础模块;
const http = require('http');//HTTP协议;
const fs = require('fs');//文件系统;
const queryString = require('querystring');//QS数据解析;
const urlLib = require('url');//URL数据解析;
//创建服务器;
var server = http.createServer(function (req, res) {//传入req请求参数和res响应参数;
var str = '';//保存请求提交的POST数据;
req.on('data', function (data) {//当有数据传输时的操作;
str += data;//将传输进来的POST数据进行拼接;
});
req.on('end', function () {//当数据请求完成时的操作;
var obj = urlLib.parse(req.url, true);//解析URL数据,并确认解析其中包含的QS数据;
const URL = obj.pathname;//保存需要请求的文件路径;
const GET = obj.query;//保存请求提交的GET数据;
const POST = queryString.parse(str);//解析POST数据并保存;
console.log(url, GET, POST);//展示接收到的数据;
});
//文件处理
var fileName = './www' + url;//拼接本地路径和解析出的请求路径;
fs.readFile(fileName, function (err, data) {//传入err错误信息和data找到的相关数据;
if (err) {
res.write('404');//错误时的返回信息;
} else {
res.write(data);//将找到的数据返回;
}
res.end();//读取结束;
});
});
//服务器监听8181端口;
server.listen(8181);
第06节:接口
● 接口需要在项目开始就定义好,在URL中的接口就是相应的请求地址和保存在Query String数据中的键值对。
● 读取文件和访问接口需要进行区分,接口量不多可以先使用条件语句处理。
● 新练习加小复习:
const http = require('http');
const fs = require('fs');
const queryString = require('querystring');
const urlLib = require('url');
var users = {};//模拟用户数据存储;
var server = http.createServer(function (req, res) {
var str = '';
req.on('data', function (data) {
str += data;
});
req.on('end', function () {
var obj = urlLib.parse(req.url, true);
const URL = obj.pathname;
const GET = obj.query;
const POST = queryString.parse(str);
if (URL === '/user') {//请求接口时访问接口,不是接口便访问数据;
switch (GET.act) {//条件中GET的act属性是定义好的接口,判断它的值是哪个再进行相应的操作;
case 'reg'://自定义的注册接口,这里需要检测诸如用户名是否存在,密码是否过于简单等条件及相应的操作;
if (users[GET.user]) {//检查在模拟数据中是否有请求数据中的对应属性;
res.write('{"ok": false, "msg": "用户名已存在"}');
} else {
users[GET.user] = GET.pass;//没有重名便将请求数据的键值存入模拟数据中;
res.write('{"ok": true, "msg": "注册成功"}');
}
break;
case 'login':
if (users[GET.user] == null) {//检测模拟数据中是否有请求的数据;
res.write('{"ok": false, "msg": "用户名不存在"}');
} else if (users[GET.user] !== GET.pass) {//有数据时检测键值对是否匹配;
res.write('{"ok": false, "msg": "用户名或密码错误"}');
} else {//条件都通过;
res.write('{"ok": true, "msg": "登录成功"}');
}
break;
default:
res.write('{"ok": false, "msg": "未知的请求"}');
break;
}
res.end();
} else {
var fileName = './www' + URL;
fs.readFile(fileName, function (err, data) {
if (err) {
res.write('404');
} else {
res.write(data);
}
res.end();
});
}
});
});
server.listen(8181);
第07节:模块化——系统模块概览
● 除了之前用过的模块,常用系统模块还有:断言(Assert)模块、网络操作(Net)模块、事件(Events)模块、加密(Crypto)模块、操作系统信息(OS)模块等等。
第08节:模块化——自定义模块
● 模块的组成:
1.require:请求,用来引入模块;引入自定义模块需要加路径,除非放到模块文件夹(node_modules)中;后缀名可加可不加。
2.module:模块,可以通过module.exports属性批量输出数据。
3.exports:输出,其实它属于module,如果要对外输出数据,必须将数据做为exports的属性,这样可以控制数据输出。
4.模拟示例:
exports.a = 12;
exports.b = function () {
var res = 0;
for (var i = 0; i < arguments.length; i++) {
res += arguments[i];
}
return res;
};
module.exports = {
x: 55,
y: 66,
z: function (a, b) {
return a - b;
}
};
● 介绍npm:NodeJS Package Manager(NodeJS包管理器),它的作用是:
1.统一下载途径;
2.自动下载依赖;
3.发布自己的模块:需要注册npm账号。
第09节:框架Express——基本用法
● 非破坏式框架,保留原生的功能,但提供了增强的功能。
● 提供了三种方法处理相应的请求,主要使用use方法,因为它能通吃数据。示例:
const express = require('express');
var server = express();//不用创建服务器,直接使用express方法即可;
//第一参数是请求路径或接口,第二参数是回调函数;
server.use('/a.html', function (req, res) {//回调函数中的两个参数不是原生的对象,除了原生的功能外,还有新加的特性;
res.send('test');//虽与write功能类似,但功能更多,例如可以解析JSON数据;
res.end();
});
//专门接收GET数据的方法;
//server.get('/', function (req, res) {});
//专门接收POST数据的方法;
//server.post('/', function (req, res) {});
server.listen(8181);
● 静态文件操作:使用插件express-static在学习中发现这个的插件并不能正常的工作,是因为在NodeJS4.x以上的版本,内置了唯一的中间件,成为一个方法。
● 基本用法实例:
const express = require('express');//导入模块
var server = express();//创建服务器;
server.listen(8181);//监听端口;
//模拟数据
var users = {
"test": 123456,
"zhangsan": 654321,
"lisi": 123123
};
//处理GET数据;
server.get('/login', function (req, res) {
//提取GET数据,并将对应数据保存;
var user = req.query['user'];
var pass = req.query['pass'];
//逻辑判断部分;
if (users[user] == null) {
res.send({ok: false, msg: '用户名不存在'});
} else {
if (users[user] != pass) {
res.send({ok: false, msg: '密码错误'});
} else {
res.send({ok: true, msg: '登陆成功'})
}
}
});
//使用自带的static方法托管静态文件;
server.use(express.static('./www'));
第10节:框架Express——中间件
● 所谓“中间件”其实就是插件,上一节中提到的被内置的express-static就是一个中间件,在这一节中讲的是一个过渡性中间件——body-parser。
● 在express中的req.query解析的是GET数据,使用body-parser插件后,可以用req.body解析POST数据,要使用req.body之前必须先在use中使用urlencoded方法。例如:
//导入等略;
//先将bodyParser对象的urlencoded方法传入,这个方法接收一个JSON参数,里面有两个属性;
server.use(bodyParser.urlencoded({//没有这步解析body数据,就不会有下面的body方法;
extended: false,//扩展模式,使用很少;
limit:2 * 1024 * 1024//体积限制,默认100K,这里所示是2MB;
}));
//解析数据;
server.use('/', function (req, res) {
console.log(req.query);//解析GET数据;
console.log(req.body);//解析POST数据;
});
● 上面的例子中可以看出,所谓“中间件”,就是在使用它之前要use一下,这样才能在随后的代码中使用中间件所提供的方法。
● 使用express的use方法可以实现链式操作,在回调函数参数中有个next,也是一个方法,它会像req和res一样传递给下一个use方法,但链式操作必须都能请求同一个地址。例如:
//导入等略;
server.use('/', function (req, res, next) {//这里的use不给传路径参数也可以传给下面的use方法;
console.log('1');
next();
});
server.use('/', function (req, res) {//这里传next但后面没有操作便无意义;
console.log('2');
});
//控制台依次输出1和2;
● 如果use方法不指定路径,便会对所有请求做出响应。
● 尝试自己写一个类似body-parser具有解析POST数据功能的基础中间件,例如:
1.中间件:
const queryString = require('querystring');
module.exports = function () {
return function (req, res, next) {
var str = '';
req.on('data', function (data) {
str += data;
});
req.on('end', function () {
req.body = queryString.parse(str);
next();//将控制权交给下一个方法;
});
};
};
2.引入使用:
//创建等略;
const myBodyParser = require('./my-body-parser');//导入模块;
server.use('/', myBodyParser());//使用模块;
server.use('/', function (req, res) {
console.log(req.body);
});
第11节:框架Express——cookie和session
● 由于HTTP是无状态的,无法识别用户是否为同一个,所以利用cookie在浏览器保存一些数据,每次请求都会进行识别,来解决此问题;但是cookie有安全隐患,且容量很小(4KB)。
● 与cookie作用类似,session也是用来保存数据的,但它被保存在服务端,安全性相对高,容量也很大;但session是基于cookie(Session ID)实现的,无法独立存在。
● 向浏览器发送cookie使用res参数的cookie方法。示例:
//参数1和2是键值对,参数3是一个JSON数据,path代表cookie的归属目录,当请求这个目录才会写入或读取,maxAge是过期时间,单位是毫秒,例子中设置了30天;
res.cookie('user', 'testName', {path: '/testPath', maxAge: 30 * 24 * 3600 * 1000});
● 为了防止cookie被篡改,有些时候需要给cookie签名,这需要先设置一个签名值,然后再给cookie签名。例如:
//这里的secret就是签名值,值是一个可自定义的字符串,cookie签名必须有签名值才可以;
req.secret = 'testCode';
//参数中JSON对象中的signed就是签名选项,true就代表要签名;
res.cookie('user', 'test', {path: '/aaa', maxAge: 30 * 24 * 3600 * 1000, signed: true});
● 如果要读取浏览器请求所带的cookie数据,可以使用cookie-parser中间件,之后可以在req的cookies属性读取到cookie数据;使用这个中间件还可以解析签名后的cookie数据。例如:
//导入等略;
server.use(cookieParser('testCode'));//使用中间件时将设定的签名值传入中间件参数,这里传入了签名值时,后面就不需要再使用req.secret属性了;
server.use('/', function (req, res) {
console.log('有签名', req.signedCookies);//使用signedCookies解析有签名的cookie数据;
console.log('无签名', req.cookies);//使用cookies解析无签名的cookie数据;
});
● 由于cookie的容量很小,所以要省着用,能短则短,而签名会导致cookie体积增大,并不是每个cookie都需要签名。
● 删除cookie可以将cookie的过期时间直接设置为0,还可以使用res.clearCookie方法,要将准备删除的cookie名和路径传入。例如:
res.clearCookie('user', {path: '/testPath'});
● 要给cookie加密可以使用cookie-encrypter中间件,由于给cookie加密的情况不多,所以这个中间件不常用。
● 解析session数据可以使用cookie-session中间件;为了防止session劫持,使用cookie-session之前需要为它添加密钥,不然没法启动它。方法如下:
//导入等略;
server.use(cookieParser());//使用cookie-session之前要先使用cookie-parser中间件;
server.use(cookieSession({//参数是以JSON的形式传入的;
name: 'sess',//默认是‘session’,如果有这个参数便为设置好的名称;
keys: ['aaa', 'bbb', 'ccc'],//密钥是一个数组,每个数组元素都是一个密钥,系统会循环使用,个数越多安全性越高;
maxAge: 3600 * 1000//过期时间,单位是毫秒;
}));
//也可以使用比较高(wei)级(suo)的方式;
var arr = [];
for (var i = 0; i < 100000; i++) {
arr.push('sig_' + Math.random());
}
//之后将这个数组作为keys的值;
● 给session中间件设置好密钥后,就可以直接使用req.session来进行相应的操作了,由于session属于要写入服务器的数据,所以是在req参数中。例如使用session记录访问次数:
//导入等略;
server.use('/', function (req, res) {
if (req.session['count'] === null || req.session['count'] === undefined) {
req.session['count'] = 1;
} else {
req.session['count']++;
}
console.log(req.session['count']);//每次刷新输出两次是因为favicon的缘故;
res.send('ok');
});
● 要删除session可以使用delete关键字,直接delete req.session即可。
第12节:模板引擎——初识Jade
● 主流的有Jade和Ejs等,Jade属于破坏式(侵入式)的,不能和普通HTML+CSS共存,强依赖型;Ejs比较温和,属于非侵入式,不破坏原有的HTML+CSS,弱依赖型。
● 使用Jade(现已改名为Pug)的基本语法:
1.根据缩进规定层级的;属性使用括号包裹,多属性使用逗号分隔;内容是与代码用一个空格隔开,内容也可以嵌套。
2.使用Jade默认会将内容压缩,没有空格和换行,这在开发阶段不方便,可以在渲染方法中加入参数,参数为JSON格式,其中添加preptty:true就可以美化编译出的代码。
3.设置style和class属性时,都可以使用普通方法写入,style属性还可以使用JSON格式写入,而class属性可以使用数组形式写入,代码复杂时可以处理数组来改变样式。
4.在Jade中,可以在标签名后面使用&attribute来识别JSON格式的属性值。
5.渲染页面的方法由jade.render('String')和jade.renderFile('File Name', Arguments),一般使用后者。
第13节:模板引擎——还是Jade
● 在Jade中可以使用自定义标签,所有自定义标签都是双标签。
● 在Jade中原样输出内容,可以在需要的内容前面加入竖线符号“ | ”,如果这样的内容很多,可以直接在放内容的标签后面直接加“ . ”符号。
● 类似原样输出,内容前面加“ - ”就识别为代码,如果要编译代码可以使用“ #{} ”,在大括号中放入内码,等价于在标签后面加“ = ”号。
● 为了防止注入式攻击,一般在内容里的HTML标签会被转义为实体字符,如要原样输出,要在包含内容的标签等号前加非转义符号“ ! ”,就可以正常输出HTML标签。
● 使用include可以将外部文件的内容引入并写进要编译的jade文件中。
● 在Jade中使用switch和JS中不同,是使用case加when的关键字,例如:
doctype
html
head
body
-var a=3;
case a
when 0
div abc
when 1
div 123
when 2
div abc123
default
div nonono
● 使用Jade的小练习:
1.JS部分,文件名为main.js:
const jade = require('jade');//引入Jade引擎;
const fs = require('fs');//引入文件操作模块;
var str = jade.renderFile('./views/index.jade', {pretty: true});//将目标jade文件渲染,并美化编译后的代码;
fs.writeFile('./build/index.html', str, function (err) {//渲染后的文件存放位置及文件名和包含要渲染文件的变量,最后的回调函数处理错误信息;
if (err) {
console.log('编译失败!');
} else {
console.log('成功!')
}
});
2.Jade部分,文件名为index.jade:
doctype//文档头是一个类似关键字的值;
html
head//依靠缩进来进行层级的区分;
meta(charset="utf-8")//属性写在括号中;
title 测试页面//在标签名后面加个空格后直接写内容;
style(type="text/css").//在.号后面的都是代码原格式;
div {width: 100px; height: 200px; background-color: red; text-align: center; line-height: 200px; float: left; margin: 10px auto;}
div.last {clear: left;}
body
-var a = 0;//如果-号后面都是代码,那么不需要每行前面添加;
while a < 12//在Jade里,JS代码中的括号可以不写;
if a % 4 === 0 && a !== 0
div.last=a++
else
div=a++
3.HTML部分,与输出文件名设定相同,index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试页面</title>
<style type="text/css">
div {width: 100px; height: 200px; background-color: red; text-align: center; line-height: 200px; float: left; margin: 10px auto;}
div.last {clear: left;}
</style>
</head>
<body>
<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div class="last">4</div>
<div>5</div>
<div>6</div>
<div>7</div>
<div class="last">8</div>
<div>9</div>
<div>10</div>
<div>11</div>
</body>
</html>
第14节:模板引擎——初识Ejs
● 使用ejs的方法与jade相仿,下载组件后导入,使用renderFile方法来渲染指定的ejs文件;ejs文件的格式与普通的HTML代码相似,每行代码要用<%%>来包起来。
● 在ejs中使用的JS代码和原生的JS代码是一样的,包括循环语句或判断语句。
● 在ejs中,使用=号是转义输出,使用-号是不转义输出。例如:
<% var str = '<div></div>'; %>
<%= str %>
<!--这里会输出<div></div>-->
<%- str %>
<!--这里会输出<div></div>-->
● 在ejs中同样也可以include将指定文件的内容引入页面中,引入的时候要注意文件的位置。
第15节:期中总结