本文章主要是讲述了 node.js 中 web 开发的基础知识,这里主要讲述如何使用 express 框架的基础使用
1 简单的 http 服务
引入 required 模块
使用 require 指令来载入 Node.js 模块。
var http = require('http');
创建服务器:服务器可以监听客户端的请求,
类似于 Apache 、Nginx 等 HTTP 服务器。具体的细节参加 API
// 使用 http.createServer() 方法创建一个 Web 服务器, 返回一个 Server 实例 var server = http.createServer()
接收请求与响应请求
服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。
// 注册 request 请求事件 // 当客户端请求过来,就会自动触发服务器的 request 请求事件,然后执行第二个参数:回调处理函数 server.on("request", function(request, response) { console.log("收到客户端的请求了,请求的url为 " + request.url); });
绑定端口号,启动服务器
server.listen(8888, function () { console.log('服务器启动成功了,可以通过 http://127.0.0.1:8888/ 来进行访问') })
注意 : 当且仅当在第一次调用 server.listen() 或调用 server.close() 期间出现错误时,才能再次调用 server.listen() 方法。 否则,将抛出 ERR_SERVER_ALREADY_LISTEN 错误。参见
此时,若用户访问http://127.0.0.1:8888/则浏览器会一直在响应中,因为服务器没有返回信息。显示
该网页无法正常运作 127.0.0.1 未发送任何数据。 ERR_EMPTY_RESPONSE
因此,可以将步骤三中的代码更改为下面的示例,增加响应。
server.on('request', function (request, response) { console.log('收到客户端的请求了,请求路径是:' + request.url) // 发送 HTTP 头部 ,可选操作 // HTTP 状态值: 200 : OK // 内容类型: text/plain response.writeHead(200, {'Content-Type': 'text/plain'}); // response 对象有一个方法:write 可以用来给客户端发送响应数据 // write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待 response.write('hello') response.write(' nodejs') // 告诉客户端,我的话说完了,你可以呈递给用户了 response.end() })
向请求发送响应头。 状态码是一个 3 位的 HTTP 状态码,如 404。 最后一个参数 headers 是响应头。 可以可选地将用户可读的 statusMessage 作为第二个参数。
const body = 'hello world'; response.writeHead(200, { 'Content-Length': Buffer.byteLength(body), 'Content-Type': 'text/plain' });
此方法只能在消息上调用一次,并且必须在调用 response.end() 之前调用。
如果在调用此方法之前调用了 response.write() 或 response.end(),则将计算隐式或可变的响应头并调用此函数。
当使用 response.setHeader() 设置响应头时,则与传给 response.writeHead() 的任何响应头合并,且 response.writeHead() 的优先。
如果调用此方法并且尚未调用 response.setHeader(),则直接将提供的响应头值写入网络通道而不在内部进行缓存,响应头上的 response.getHeader() 将不会产生预期的结果。 如果需要渐进的响应头填充以及将来可能的检索和修改,则改用 response.setHeader()。
// 返回 content-type = text/plain const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'text/html'); res.setHeader('X-Foo', 'bar'); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('ok'); });
注意,Content-Length 以字节而非字符为单位。 上面的示例可行是因为字符串 ‘hello world’ 仅包含单字节字符。 如果主体包含更高编码的字符,则应使用 Buffer.byteLength() 来判断给定编码中的字节数。 Node.js 不检查 Content-Length 和已传输的主体的长度是否相等。
尝试设置包含无效字符的响应头字段名称或值将导致抛出 TypeError。
另一个完整的 demo 示例如下:
var http = require('http'); http.createServer(function (request, response) { // 发送 HTTP 头部 // HTTP 状态值: 200 : OK // 内容类型: text/plain response.writeHead(200, {'Content-Type': 'text/plain'}); // 发送响应数据 "Hello World" response.end('Hello World\n'); }).listen(8888); // 终端打印如下信息 console.log('Server running at http://127.0.0.1:8888/');
上述 demo 使用的是
http.createServer([options][, requestlistener])
这个接口 ,其中的 requestListener 是一个自动添加到 ‘request’ 事件的函数。
在上述响应中,如果有中文会出现乱码,可以增加响应头有进行控制,完整的 content-type 列表参见
response.setHeader("Content-Type", "text/html;charset=utf-8");
2 express 框架 2.1 安装 Express cnpm install express --save
以上命令会将 Express 框架安装在当前目录的 node_modules 目录中, node_modules 目录下会自动创建 express 目录。以下几个重要的模块是需要与 express 框架一起安装的
cnpm install body-parser --save cnpm install cookie-parser --save cnpm install multer --save
这些附带安装的插件的作用如下:
body-parser - node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。
cookie-parser - 这就是一个解析 Cookie 的工具。通过 req.cookies 可以取到传过来的 cookie,并把它们转成对象。
multer - node.js 中间件,用于处理 enctype=”multipart/form-data”(设置表单的 MIME 编码)的表单数据。
安装完后,我们可以查看下 express 使用的版本号
$ cnpm list express /data/www/node └── express@4.15.2 -> /Users/tianqixin/www/node/node_modules/.4.15.2@express
2.2 快速入门 //express_demo.js 文件 var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World'); }) var server = app.listen(8081, function () { var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) })
上述实例中我们引入了 express 模块,并在客户端发起请求后,响应 “Hello World” 字符串。
$ node express_demo.js 应用实例,访问地址为 http://0.0.0.0:8081
2.3 使用静态资源 项目的结构如上图所示,此时 public 为静态资源目录,无法直接访问,若需要将其当做静态资源访问,需要加上如下代码:
//express_demo.js 文件 var express = require("express"); const path = require("path"); var app = express(); //path.join([path1][, path2][, ...]) //用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是"\"。 app.use(express.static(path.join(__dirname, "public"))); app.get("/", function(req, res) { res.send("Hello World"); }); app.get("/hello", function(req, res) { res.send("Hello World index"); }); //这里也可以链式调用 //app.get("/aa",function(){}).post("/bb",function(){}); var server = app.listen(8081, function() { var host = server.address().address; var port = server.address().port; console.log("应用实例,访问地址为 http://%s:%s", host, port); });
注意 : 若使用路径出现异常时,访问路径里的资源会无法访问 出现问题的代码:
app.use(express.static("/public/"));
此时会出现如下问题:
完成正确的设置后,可以通过 http://localhost:8081/js/demo.js 访问静态资源问题。
express 官方文档参见 http://www.expressjs.com.cn/starter/static-files.html
注意
上面的配置方式中,放置资源的目录不在访问路径中。
另一种正确的配置方式如下:
app.use("/public/", express.static("./public/"));
此时,访问静态资源需要带上路径 public ,访问路径为:
http://localhost:8081/public/js/demo.js
// public 资源 app.use(express.static("public")); // static 资源 app.use(express.static("static")); // /public/xx app.use('/public',express.static("public")); // /static/xx app.use('/static',express.static("public")); // /static/xx app.use('/static',express.static(path.join(__dirname, "public")));
2.4 使用 art-template 安装命令如下:
npm install art-template --sav npm install express-art-template --sav
安装完成之后,即可在 node.js 里使用 art-template 了, 在 node 里增加以下代码
// 配置使用 art-template 模板引擎 // 第一个参数,表示,当渲染以 .art 结尾的文件的时候,使用 art-template 模板引擎 // express-art-template 是专门用来在 Express 中把 art-template 整合到 Express 中 // 虽然外面这里不需要记载 art-template 但是也必须安装 // 原因就在于 express-art-template 依赖了 art-template app.engine('art', require('express-art-template')) // Express 为 Response 相应对象提供了一个方法:render // render 方法默认是不可以使用,但是如果配置了模板引擎就可以使用了 // res.render('html模板名', {模板数据}) // 第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件 // 也就是说 Express 有一个约定:开发人员把所有的视图文件都放到 views 目录中 //若上面配置的为art,则这里后缀名不能为 html,否则会报错 app.get('/post', function (req, res) { res.render('post.html') })
具体的 art-template 用法参见官方文档
注意:
默认的模版文件的目录为 views
如果想修改默认的模板文件夹路径,可以通过如下方法进行设置
app.set('views', __dirname + '/views'); app.set('view engine', 'jade');
上面两行是设置 views 文件夹,即模板文件夹,dirname 是 node.js 里面的全局变量,即取得执行的 js 所在的路径,另外 dirname 是目前执行的 js 文件名。 所以,
app.set(‘views’, __dirname + ‘/views’);
是设置 views 的文件夹。 而
app.set(‘view engine’, ‘jade’);
是设置 express.js 所使用的 render engine。除了 Jade 之外 express.js 还支持 EJS(embedded javascript)、Haml、CoffeScript 和 jQuerytemplate 等 js 模板
2.5 express 处理 post 请求
先安装 body-parser ,安装命令如下:
cnpm install body-parser --save
配置 body-parser
//引入 body-parser var bodyParser = require('body-parser') // 配置 body-parser 中间件(插件,专门用来解析表单 POST 请求体) // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json())
只要配置了这个属性,在 req 请求对象上就会增加一个属性 body
安装成功之后,就可以使用次插件了,使用的方法如下:
/** * app.js 入门模块 * 职责: * 创建服务 * 做一些服务相关配置 * 模板引擎 * body-parser 解析表单 post 请求体 * 提供静态资源服务 * 挂载路由 * 监听端口启动服务 */ var express = require('express') var router = require('./router') var bodyParser = require('body-parser') var app = express() app.use('/node_modules/', express.static('./node_modules/')) app.use('/public/', express.static('./public/')) // 配置使用 art-template 模板引擎 // 第一个参数,表示,当渲染以 .art 结尾的文件的时候,使用 art-template 模板引擎 // express-art-template 是专门用来在 Express 中把 art-template 整合到 Express 中 // 虽然外面这里不需要记载 art-template 但是也必须安装 // 原因就在于 express-art-template 依赖了 art-template app.engine('html', require('express-art-template')) // parse application/x-www-form-urlencoded // Express 为 Response 相应对象提供了一个方法:render // render 方法默认是不可以使用,但是如果配置了模板引擎就可以使用了 // res.render('html模板名', {模板数据}) // 第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件 // 也就是说 Express 有一个约定:开发人员把所有的视图文件都放到 views 目录中 // 如果想要修改默认的 views 目录,则可以 // app.set('views', render函数的默认路径) // 配置 body-parser 中间件(插件,专门用来解析表单 POST 请求体) // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json()) // 当以 POST 请求 /post 的时候,执行指定的处理函数 // 这样的话我们就可以利用不同的请求方法让一个请求路径使用多次 app.post('/post', function (req, res) { // 1. 获取表单 POST 请求体数据 // 2. 处理 // 3. 发送响应 // req.query 只能拿 get 请求参数 // console.log(req.query) var comment = req.body comment.dateTime = '2017-11-5 10:58:51' comments.unshift(comment) // res.send // res.redirect // 这些方法 Express 会自动结束响应 res.redirect('/') // res.statusCode = 302 // res.setHeader('Location', '/') }) // 配置模板引擎和 body-parser 一定要在 app.use(router) 挂载路由之前 // 把路由容器挂载到 app 服务中 app.use(router) app.listen(3000, function () { console.log('running 3000...') }) module.exports = app
2.6 node.js 路由设置 假设项目的路由的路径作用如下:
请求方法
请求路径
get 参数
post 参数
备注
GET
/studens
渲染首页
GET
/students/new
渲染添加学生页面
POST
/studens/new
name、age、gender、hobbies
处理添加学生请求
GET
/students/edit
id
渲染编辑页面
POST
/studens/edit
id、name、age、gender、hobbies
处理编辑请求
GET
/students/delete
id
处理删除请求
1.在项目中创建一个名为 router.js 的文件
文件的内容如下:
/** * router.js 路由模块 * 职责: * 处理路由 * 根据不同的请求方法+请求路径设置具体的请求处理函数 * 模块职责要单一,不要乱写 * 我们划分模块的目的就是为了增强项目代码的可维护性 * 提升开发效率 */ var fs = require('fs') var Student = require('./student') // Express 提供了一种更好的方式 // 专门用来包装路由的 var express = require('express') // 1. 创建一个路由容器 var router = express.Router() // 2. 把路由都挂载到 router 路由容器中 /* * 渲染学生列表页面 */ router.get('/students', function (req, res) { Student.find(function (err, students) { if (err) { return res.status(500).send('Server error.') } res.render('index.html', { fruits: [ '苹果', '香蕉', '橘子' ], students: students }) }) }) /* * 渲染添加学生页面 */ router.get('/students/new', function (req, res) { res.render('new.html') }) /* * 处理添加学生 */ router.post('/students/new', function (req, res) { // 1. 获取表单数据 // 2. 处理 // 将数据保存到 db.json 文件中用以持久化 // 3. 发送响应 Student.save(req.body, function (err) { if (err) { return res.status(500).send('Server error.') } res.redirect('/students') }) }) /* * 渲染编辑学生页面 */ router.get('/students/edit', function (req, res) { // 1. 在客户端的列表页中处理链接问题(需要有 id 参数) // 2. 获取要编辑的学生 id // // 3. 渲染编辑页面 // 根据 id 把学生信息查出来 // 使用模板引擎渲染页面 Student.findById(parseInt(req.query.id), function (err, student) { if (err) { return res.status(500).send('Server error.') } res.render('edit.html', { student: student }) }) }) /* * 处理编辑学生 */ router.post('/students/edit', function (req, res) { // 1. 获取表单数据 // req.body // 2. 更新 // Student.updateById() // 3. 发送响应 Student.updateById(req.body, function (err) { if (err) { return res.status(500).send('Server error.') } res.redirect('/students') }) }) /* * 处理删除学生 */ router.get('/students/delete', function (req, res) { // 1. 获取要删除的 id // 2. 根据 id 执行删除操作 // 3. 根据操作结果发送响应数据 Student.deleteById(req.query.id, function (err) { if (err) { return res.status(500).send('Server error.') } res.redirect('/students') }) }) // 3. 把 router 导出 module.exports = router // 这样也不方便 // module.exports = function (app) { // app.get('/students', function (req, res) { // // readFile 的第二个参数是可选的,传入 utf8 就是告诉它把读取到的文件直接按照 utf8 编码转成我们能认识的字符 // // 除了这样来转换之外,也可以通过 data.toString() 的方式 // fs.readFile('./db.json', 'utf8', function (err, data) { // if (err) { // return res.status(500).send('Server error.') // } // // 从文件中读取到的数据一定是字符串 // // 所以这里一定要手动转成对象 // var students = JSON.parse(data).students // res.render('index.html', { // fruits: [ // '苹果', // '香蕉', // '橘子' // ], // students: students // }) // }) // }) // }
将 router.js 挂在到服务中
/** * app.js 入门模块 * 职责: * 创建服务 * 做一些服务相关配置 * 模板引擎 * body-parser 解析表单 post 请求体 * 提供静态资源服务 * 挂载路由 * 监听端口启动服务 */ var express = require('express') var router = require('./router') var bodyParser = require('body-parser') var app = express() app.use('/node_modules/', express.static('./node_modules/')) app.use('/public/', express.static('./public/')) app.engine('html', require('express-art-template')) // 配置模板引擎和 body-parser 一定要在 app.use(router) 挂载路由之前 // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json()) // 把路由容器挂载到 app 服务中 app.use(router) app.listen(3000, function () { console.log('running 3000...') }) module.exports = app
express 具体的路由使用方法参见 http://www.expressjs.com.cn/4x/api.html#router
3 nodemon 自动重启服务
全局安装 nodemon 包,这样新创建的 Node.js 应用都能使用 Nodemon 运行起来了
安装完成之后,Nodemon 就可以启动 Express 应用了,先关闭当前正在执行的应用程序,然后再执行命令
3.1 nodemon 配置文件 Nodemon 默认会监听当前目录下(也就是执行 nodemon 命令所在的目录)的所有文件,不过有些情况下,虽然项目文件发生了改动,但是不需要 Nodemon 重启应用,那如何让文件不被 Nodemon 监听呢?不需要监听的文件,可以通过设置 Nodemon 的配置文件排除掉,新建文件 server/nodemon.json,添加代码
{ "ignore": [ "config.default.js" ] }
Nodemon 配置文件是 JSON 文件,通过设置 ignore 属性值,一个由文件名组成的字符串数组,指定不需要监听的文件
3.2 nodemon 手动重启 有时候可能 Nodemon 还在运行的时候,需要手动重启它,在这种情况下不需要关闭正在运行的 Nodemon 进程然后再重启 Nodemon,只要在 Nodemon 命令运行的终端 窗口中输入 rs 两个字符,然后再按下回车键,就能重启 Nodemon 了
4 session 处理 在 express 框架中,默认不支持 session,可以使用 express-session 来支持 session
4.1 安装 express-session
4.2 配置 express-session var session = require('express-session') var app = express() app.set('trust proxy', 1) // trust first proxy app.use(session({ secret: 'keyboard cat', //加密的key,提高安全性 resave: false, saveUninitialized: true, cookie: { secure: true } }))
express-session 一定要在路由之前配置
express-session 是针对 nodejs express 框架提供的一套 session 扩展,主要参数有 secret,sesave,saveUninitialized,cookie
cookie 主要属性如下
{ path: ‘/‘, httpOnly: true, secure: false, maxAge: null }
domain:设置 cookie 可以设置的域名,如果没有设置则 cookie 默认在当前域可以使用
expires:cookie 失效时间,可以设置时间,不建议给固定时间,设置 maxAge 之后自动会生成这个值
//获取当前时间 var date=new Date(); var expireDays=10; //将date设置为10天以后的时间 date.setTime(date.getTime()+expireDays*24*3600*1000); //将userId和userName两个Cookie设置为10天后过期 expires:date.toGMTString();
httpOnly:属性禁止客户端 JavaScript 的访问,禁止后不能使用 document.cookie
maxAge:单位毫秒,从设置 cookie 开始多少毫秒失效 ,如果 maxAge 和 expires 都设置了,最后设置的属性生效.
path:路径,默认值为域名的根路径.
sameSite: SameSite-cookies 是一种机制,用于定义 cookie 如何跨域发送。这是谷歌开发的一种安全机制,未来的一种 cookie 跨域授权处理方式,不明白的就不用设置了
(Strict 是最严格的防护,有能力阻止所有 CSRF 攻击。然而,它的用户友好性太差,因为它可能会将所有 GET 请求进行 CSRF 防护处理。
例如:一个用户在 reddit.com 点击了一个链接(GET 请求),这个链接是到 facebook.com 的,而假如 facebook.com 使用了 Samesite-cookies 并且将值设置为了 Strict,那么用户将不能登陆 Facebook.com,因为在 Strict 情况下,浏览器不允许将 cookie 从 A 域发送到B域。
Lax(relax 的缩写?)属性只会在使用危险 HTTP 方法发送跨域 cookie 的时候进行阻止,例如 POST 方式。
例 1:一个用户在 reddit.com 点击了一个链接(GET 请求),这个链接是到 facebook.com 的,而假如 facebook.com 使用了 Samesite-cookies 并且将值设置为了 Lax,那么用户可以正常登录 facebok.com,因为浏览器允许将 cookie 从 A 域发送到 B 域。
例 2:一个用户在 reddit.com 提交了一个表单(POST 请求),这个表单是提交到 facebook.com 的,而假如 facebook.com 使用了 Samesite-cookies 并且将值设置为了 Lax,那么用户将不能正常登陆 Facebook.com,因为浏览器不允许使用 POST 方式将 cookie 从 A 域发送到B域。
)
值 true:sameSite 使用 strict 模式
值 false:不设置 sameSite 属性
值 lax:sameSite 使用 lax 模式
值 strict: sameSite 使用 strict 模式
secure:设置 cookie 的 secure 值,默认是不设置任何值
setSecure(true); 的情况下,只有 https 才传递到服务器端。http 是不会传递的。
genid:设置创建 session id 的自定义函数,默认使用 uid-safe 扩展来生成 id, 自定义 genid 创建函数时一定要保证创建的 id 不要重复。
name :用来设置在 response 中范围 session id 是属性值,reuqest 中可以用默认的 request.session.id 访问。默认值为 connect.sid
proxy:代理,通过设置这个值可以设置 X-Forwarded-Proto 头,
proxy 的值有 true (X-Forwarded-Proto 使用),false (所有头信息忽略,只有 tls/ssl 可以安全连接),undefined(使用 trust proxy 设置) 具体大家研究,因为没有整代码大家继续努力实践
resave:是否允许 session 重新设置,要保证 session 有操作的时候必须设置这个属性为 true
rolling:是否按照原设定的 maxAge 值重设 session 同步到 cookie 中,要保证 session 有操作的时候必须设置这个属性为 true
saveUninitialized:是否设置 session 在存储容器中可以给修改(true 表示无论是否使用 session 都默认分配一个钥匙 )
session 过期 30 分钟,没有人操作时候 session 30 分钟后过期,如果有人操作,每次以当前时间为起点,使用原设定的 maxAge 重设 session 过期时间到 30 分钟只有这种业务场景必须同行设置 resave rolling 为 true.同时 saveUninitialized 要设置为 false 允许修改。
secret:用来注册 session id 到 cookie 中,相当与一个密钥。
store:session 存储的实例子,一般可以用 redis 和 mangodb 来实现
unset:设置 req.session 在什么时候可以设置
值:destory:请求结束时候 session 摧毁,值:keep session 在存储中的值不变,在请求之间的修改将会忽略,而不保存
更多具体的使用参见https://www.npmjs.com/package/express-session
4.3 session 的使用 添加 session 数据
访问 session 中的数据