Express
最近一个多月在用Express+MySQL
写公司内部业务(商品订单数据分析)的一些简单的逻辑,天天整SQL
,搞定时任务,搞日志,搞AntV F2
+Vue
,结果年前要重构项目,全部从0开始,包括后台数据库建表.....
最近想抽空看了下Node与Express开发,记录一点笔记,个人感觉还是看文档比较好
image.png招个前端,让他负责点后端,维护哈服务器,
然后拍摄一下产品,拍个产品视频,做一下图,
再帮忙做下销售,
随便给公司员工炒几个菜,
再刷两间厕所,
就在那儿喊苦了,哎,现在的年轻人啊
微信图片_20191218185718.jpg
就当巩固下知识吧,接触Express蛮久了,年中搭了一个小站点,采用Express
+art-template
做页面渲染,客户端部分功能使用vue.js
库(jQuery 写逻辑的代码量太大了),中转后台接口渲染模板页,坑就是 vue 和 art-template 的两种模板语法之一{{}}
会冲突,需要单独修改源码设置
你可能会问,为啥不用Koa2
,Egg.js
,ThinkJS
,sails........
,我是挺想用Koa或者egg
的,想了一番,我还是先把ES6搞明白吧....(滑稽脸) 年底还要整支付宝和微信小程序...
官网是这么介绍的,精简的、灵活的 Node.js Web 程序框架,为构建单页、多页及混合的 Web 程序提供了一系列健壮的功能特性
- 精简: Express 的哲学是在你的想法和服务器之间充当薄薄的一层,这并不意味着它不够健壮,或者没有足够的有用特性,而是尽量少干预你,让你充分表达自己的思想,同时提供一些有用的东西
- 灵活: Express 哲学中的另一个关键点是可扩展
- Web 程序框架: 网站是 Web 程序,网页也是 Web 程序
- 单页 Web 程序
- 多页和混合的 Web 程序:多页 Web 程序是更传统的方式。网站上的每个页面都是通过向服务器发起单独的请求得到的
Express4.0
- Connect • 已经从 Express 中去掉了,所以除了 static 中间件,你需要自己安装相应的开发包(即 connect ),与此同时,Connect 将一些中间件移到了它自己的包内
- body-parser • 现在有自己的包了,它不再包含 multipart 中间件,因而也关闭了一个重大的安全漏洞。现在可以放心使用 body-parser 中间件了
- 不必再将 Express router 链接到程序里。所以应该从已有的 Express 3.0 中去掉 • app.use(app.router)
- app.configure • 被去掉了,只要检查 app.get(env) (用 switch 或 if 语句)就可以取代该方法。
使用终端
很多工具都有 GUI 界面,所以如果你确实不想使用终端,你有自己的选择权,但你就只能靠自己去学习了
Console2
(http://sourceforge.net/projects/console/)或
ConEmu
(https://github.com/Maximus5/ConEmu)
这些更精致的控制台,以及微软自己的 PowerShell
,技艺娴熟的 PowerShell
用户跟 Unix
命令行大师旗鼓相当,不管你用什么系统,都可以使用优秀的 Codio
(https://codio.com/)
-
至少你应该知道如何切换目录,如何复制、移动和删除文件,以及如何中断一个命令行程序(通常 Ctrl-C)。如果你想变成终端高手,我建议你学一学如何在文件中搜索文本,如何搜索文件和目录,如何把
命令链在一起(老式的“Unix 理念”),以及如何重定向输出 -
解冻终端是用 Ctrl-Q,所以如果你忽然发觉终端看起来被冻结了,试一下 Ctrl-Q,看能不能释放它
Node 实现简单的 web 服务器
如果你之前曾经做过静态的 HTML 网站,或者有 PHP 或 ASP 背景,可能习惯用 Web 服务
器(比如 Apache 或 IIS)提供静态文件服务,以便使用浏览器通过网络查看这些文件。比
如说,如果你创建了一个名为 about.html 的文件,并把它放到了恰当的目录下,然后就可
以访问 http://localhost/about.html 查看这个文件。根据 Web 服务器的配置,你甚至可以省
略 .html,但 URL 和文件名之间的关系很清晰:Web 服务器知道文件在机器的哪个地方,
并能把它返回给浏览器。
Node 所提供的范式跟传统的 Web 服务器不同:你写的程序就是 Web 服务器。Node 只是
给你提供了一个构建 Web 服务器的框架。
创建
var http = require("http");
http
.createServer(function(req, res) {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello world!");
})
.listen(3000);
console.log("Server started on localhost:3000; press Ctrl-C to terminate....");
事件驱动编程
Node 的核心理念是事件驱动编程,很多人接触事件驱动编程是从用户界面开始的:用户点击了什么,
然后你处理“点击事件”。这个类比很好,因为程序员不能控制用户什么时间点击或者是
否会点击,所以事件驱动编程真的很直观
路由
路由是指向客户端提供它所发出的请求内容的机制。对基于 Web 的客户端 / 服务器端程序而言,客户端在 URL 中指明它想要的内容,具体来说就是路径和查询字符串
静态资源服务
用 Node 提供静态资源只适用于初期的小型项目,对于比较大的项目,你应该会想用 Nginx 或 CDN 之类的代理服务器来提供静态资源
我们可将它作为跳板去完成更加复杂的事情。如果我们沿着这条路走下去,写出越来越复杂的 Node 程序,最后你可能会得到一个类似于 Express 的东西……幸运的是,我们没必要这样做,因为 Express 已经存在了,你不用自己花那么多时间去写基础设施类的代码
脚手架
这个想法很简单:大多数项目都需要一定数量的“套路化”代码,谁会想每次开始新项目时都重新写一次这些代码呢?对此有个简单的方法,那就是创建一个通用的项目骨架,每次开始新项目时,只需复制这个骨架,或者说是模板。
案例:HTML5 Boilerplate,它能生成一个很不错的空白 HTML5 网站
初始化项目
-
npm init --yes
生成一个 package.json 文件,注意 main 的入口文件,默认 index.js npm install express --save
- 编写
index.js
入口文件内容
var express = require("express");
var app = express();
app.set("port", process.env.PORT || 3000);
// 定制 404 页面
app.use(function(req, res) {
res.type("text/plain");
res.status(404);
res.send("404 - Not Found");
});
// 定制 500 页面
app.use(function(err, req, res, next) {
console.error(err.stack);
res.type("text/plain");
res.status(500);
res.send("500 - Server Error");
});
app.listen(app.get("port"), function() {
console.log(
"Express started on http://localhost:" +
app.get("port") +
"; press Ctrl-C to terminate."
);
});
-
app.use 是 Express 添加中间件的一种方法
-
视图和布局
Express 支持多种不同的视图引擎,它们有不同层次的抽象。Express 比较偏好的视图引擎是 Jade(因为它也是 TJ Holowaychuk 开发的),Jade 所采用的方式非常精简:你写的根本不像是 HTML,因为没有尖括号和结束标签,这样可以少敲好多次键盘。然后,Jade 引擎会将其转换成 HTML Jade 是非常吸引人的,但这种程度的抽象也是有代价的。如果你是一名前端开发人员,即便你实际上是用 Jade 编写视图,也必须理解 HTML,大多数前端开发人员都不喜欢他们主要的标记语言被抽象化处理
Handlebars
推荐使用另外一个抽象程度较低的模板框架 Handlebars。Handlebars(基于与语言无关的流行模板语言 Mustache)不会试图对 HTML 进行抽象:你编写的是带特殊标签的 HTML,Handlebars 可以借此插入内容
- 视图和静态文件
static 中间件可以将一个或多个目录指派为包含静态资源的目录,其中的资源不经过任何特殊处理直接发送到客户端。你可以在其中放图片、CSS 文件、客户端 JavaScript 文件之类的资源
app.use(express.static(path.join(__dirname, "public")));
- 视图中的动态内容
视图真正的强大之处在于它可以包含动态信息
工欲善其事,必先利其器
git
项目目录中创建一个文本文件 .gitignore
node_modules
*~
.DS_Store
npm 包
不管什么时候在项目中使用了 Node 模块,你都要确保它作为依赖项出现在 package.json 文
件中。如果你没能做到这一点,npm 将无法构建出恰当的依赖项,而当其他开发人员检出
项目时(或者当你换了一台机器时),就无法安装正确的依赖项,包管理器的价值也不能
得到有效发挥
项目元数据
package.json 文件的另一个作用便是存放项目的元数据,比如项目名称、作者、授权信息等。如果你用 npm init 来初始化创建 package.json 文件,它会为你生成必需的域,然后你可以随时修改它们。如果你想把项目放到 npm 或 Github 上,则对元数据的要求会比较严格
Node 模块
Node 模块和 npm 包是两个相互关联但又彼此不同的概念。Node 模块,就像
它的名字一样,提供了一个模块化和封装的机制。npm 包则提供了一种存储、版本化和引
用项目(不限于模块)的标准范式
质量保证
在 Web 开发中,质量可以分解为四个维度:
- 到达率是指产品的市场普及程度,即查看网站或使用服务的人数。到达率和盈利能力是
正相关关系:访问网站的人越多,购买产品或服务的人就越多。从开发的角度来看,搜
索引擎优化(SEO)对到达率的影响最大,所以我们会在 QA 方案里包含 SEO - 功能,否把用户留下很大程度上取决于网站功能的质量
- 功能关心的是功能的正确性,而可用性评估的是人机交互(HCI),程序员眼中的“容易”可能跟不懂技术的用户眼中的“容易”不一样
- 审美是四个维度中最主观的,因此也是跟开发最不相关的一个维度,审美具有时间敏感性(审美标准会随着时间而发生变化),并且因人而异(受到某一受众喜爱的东西可能完全激不起其他受众的兴趣)
测试的类型
单元测试和集成测试,一般而言,单元测试在测试逻辑时更实用,也更恰当
QA 技术概览
- 页面测试,用来测试页面的表示和前端功能。这同时涉及单元测试和集成测
试。我们会用 Mocha 进行页面测试 - 跨页测试是对从一个页面转到另一个页面的功能的测试,比如电子商务网站上的结账功
能,通常要跨越多个页面。因为这种测试会涉及多个组件,所以一般被当作集成测试。
这个测试用的是 Zombie.js。 - 逻辑测试会对逻辑域进行单元和集成测试。它只会测试 JavaScript,跟所有表示功能
分开 - 去毛的一般概念是找出可能有错误的区域,SHint
- 链接检查(确保你的网站上没有破损的链接)属于“唾手可得”的那一类测试
请求和响应对象
URL 组成部分
- 协议:确定如何传输请求。我们主要是处理 http 和 https。其他常见的协议还有 file 和 ftp。
- 主机名标识服务器,还会有子域名作为主机名的前缀,子域名可以是任何形式的,其中 www 最为常见。子域名通常是可选的。
- 端口: 每一台服务器都有一系列端口号,如果省略端口值,那么默认 80 端口负责 HTTP 传输,443 端口负责 HTTPS 传输。如果不使用 80 和 443 端口,就需要一个大于 1023 1 的端口号。通常使用容易记忆的端口号,如 3000、8080 或 8088
- 路径: 是应用中的页面或其他资源的唯一标识
- 查询字符串: 是一种键值对集合,是可选的。它以问号(?)开头,键值对则以与号(&)分隔开。所有的名称和值都必须是 URL 编码的。对此,JavaScript 提供了一个嵌入式的函数 encodeURIComponent 来处理。例如,空格被加号(+)替换。其他特殊字符被数字型字符替换。
-
信息片段(或散列)被严格限制在浏览器中使用,不会传递到服务器。用它控制单页应
用或 AJAX 富应用越来越普遍。最初,信息片段只是用来让浏览器展现文档中通过锚点
标记(<a id="chapter06">
)指定的部分
HTTP 请求方法
HTTP 协议确定了客户端与服务器通信的请求方法集合(通常称为 HTTP verbs)。很显然,
GET 和 POST 最为常见
请求报头
我们浏览网页时,发送到服务器的并不只是 URL,会发送“用户代理”信息(浏览器、操作系统和硬件设备)和其他一些信息。所有能够确保你了解请求对象头文件属性的信息都将会作为请求报头发送。如果想查看浏览器发送的信息,可以创建一个非常简单的 Express 路由来展示一下:
app.get("/headers", function(req, res) {
res.set("Content-Type", "text/plain");
var s = "";
for (var name in req.headers) s += name + ": " + req.headers[name] + "\n";
res.send(s);
});
响应报头
正如浏览器以请求报头的形式发送隐藏信息到服务器,当服务器响应时,同样会回传一些浏览器没必要渲染和显示的信息,通常是元数据和服务器信息
它告诉浏览器正在被传输的内容类型(网页、图片、样式表、客户端脚本等)。特别要注意的是,不管 URL 路径是什么,浏览器都根据内容类型报头处理信息
浏览器只根据内容类型来决定内容该如何渲染。)除了内容类型之外,报头还会指出响应信息是否被压缩,以及使用的是哪种编码。响应报头还可以包含关于浏览器对资源缓存时长的提示。优化网站时需要着重考虑这一点
响应报头还经常会包含一些关于服务器的信息,一般会指出服务器的类型,有时甚至会包含操作系统的详细信息。返回服务器信息存在一个问题,那就是它会给黑客一个可乘之机,从而使站点陷入危险。非常重视安全的服务器经常忽略此信息,甚至提供虚假信息。禁用 Express 的 X-Powered-By 头信息很简单
app.disable("x-powered-by");
请求体
除请求报头外,请求还有一个主体(就像作为实际内容返回的响应主体一样)
- 一般 GET 请求没有主体内
- POST 请求体最常见的媒体类型是 application/x-www-form-urlendcoded,是键值对集合的简单编码,用 & 分隔(基本上和查询字符串的格式一样)
- POST 请求需要支持文件上传,则媒体类型是 multipart/form-data ,它是一种更为复杂的格式
- AJAX 请求,它可以使用 application/json
请求对象
请求对象的生命周期始于 Node 的一个核心对象 http.IncomingMessage 的实例。
Express 添加了一些附加功能,除了来自 Node 的req.headers
和req.url
,所有这些方法都由 Express 添加
方法 | 说明 |
---|---|
req.params |
一个数组,包含命名过的路由参数 |
req.param(name) |
返回命名的路由参数,或者 GET 请求或 POST 请求参数 |
req.query |
一个对象,包含以键值对存放的查询字符串参数(通常称为 GET 请求参数) |
req.body |
一个对象,包含 POST 请求参数,需要中间件能够解析请求正文内容类型 |
req.route |
关于当前匹配路由的信息。主要用于路由调试 |
req.cookies/req.singnedCookies |
一个对象,包含从客户端传递过来的 cookies 值 |
req.headers |
从客户端接收到的请求报头。 |
req.accepts([types]) |
用来确定客户端是否接受一个或一组指定的类型(可选类型可以是单个的 MIME 类型,如 application/json 、一个逗号分隔集合或是一个数组) |
req.ip |
客户端的 IP 地址。 |
req.path |
请求路径(不包含协议、主机、端口或查询字符串) |
req.host |
一个简便的方法,用来返回客户端所报告的主机名。这些信息可以伪造,所以不应该用于安全目的 |
req.xhr |
一个简便属性,如果请求由 Ajax 发起将会返回 true |
req.protocol |
用于标识请求的协议( http 或 https ) |
req.secure |
一个简便属性,如果连接是安全的,将返回 true 。等同于 req.protocol==='https'
|
req.url/req.originalUrl |
这些属性返回了路径和查询字符串(它们不包含协议、主机或端口) |
req.acceptedLanguages |
用来返回客户端首选的一组(人类的)语言。这些信息是从请求报头中解析而来的 |
响应对象
响应对象(通常传递到回调方法,这意味着你可以随意命名它,通常命名为 res 、 resp 或
response )的生命周期始于 Node 核心对象 http.ServerResponse 的实例.Express 添加了一
些附加功能。我们来看看响应对象中最有用的属性和方法(所有这些方法都是由 Express
添加的)
方法 | 说明 |
---|---|
res.status(code) |
设置 HTTP 状态代码。Express 默认为 200(成功) |
res.set(name,value) |
设置响应头。这通常不需要手动设置 |
res.cookie(name,vaue,[options]), res.clearCookie(name,[options])
|
设置或清除客户端 cookies 值。需要中间件支持 |
res.redirect([status],url) |
重定向浏览器。默认重定向代码是 302(建立) |
res.send(body),res.send(status,body) |
向客户端发送响应及可选的状态码。Express 的默认内容类型是 text/html |
res.json(json),res.json(status,json) |
向客户端发送 JSON 以及可选的状态码。 |
res.jsonp(json),req.jsonp(status,json) |
向客户端发送 JSONP 及可选的状态码。 |
res.type(type) |
一个简便的方法,用于设置 Content-Type 头信息。基本上相当于 res.set('Content-Type','type')
|
res.format(object) |
允许你根据接收请求报头发送不同的内容 |
res.sendFile(path,[option],[callback]) |
根据路径读取指定文件并将内容发送到客户端 |
更多信息
如果你需要的信息没在文档中,有时就不得不深入研究 Express 源码
-
源码的路径说明和说明
lib/application.js
是 Express 主接口。如果想了解中间件是如何接入的,或视图是如何被渲染的,可以看这里。 -
lib/express.js
这是一个相对较短的 shell,是 lib/application.js 中 Connect 的功能性扩展,它返回一个函数,可以用 http.createServer 运行 Express 应用 -
lib/request.js
扩展了 Node 的 http.IncomingMessage 对象,提供了一个稳健的请求对象。关于请求对象属性和方法的所有信息都在这个文件里。 -
lib/response.js
扩展了 Node 的 http.ServerReponse 对象,提供响应对象。关于响应对象的所有属性和方法都在这个文件里 -
lib/router/route.js
提供基础路由支持。尽管路由是应用的核心,但这个文件只有不到 200 行,你会发现它非常地简单优雅
内容渲染(掌握)
渲染内容用 res.render ,它最大程度地根据布局渲染视图。如果想写一个快速测试页,也许会用到 res.send,你可以使用 req.query 得到查询字符串的值,使用 req.session 得到会话值,或使用 req.cookie/req.singedCookies 得到 cookies 值
- 基本用法
app.get("/about", function(req, res) {
res.render("about");
});
- 200 以外的响应代码
app.get("/error", function(req, res) {
res.status(500);
res.render("error");
});
//或者是一行代码
app.get("/error", function(req, res) {
res.status(500).render("error");
});
- 将上下文传递给视图,包括查询字符串、cookie 和 session 值
app.get("/greeting", function(req, res) {
res.render("about", {
message: "welcome",
style: req.query.style,
userid: req.cookie.userid,
username: req.session.username
});
});
- 没有布局的视图渲染
// 下面的 layout 没有布局文件,即 views/no-layout.handlebars
// 必须包含必要的 HTML
app.get("/no-layout", function(req, res) {
res.render("no-layout", { layout: null });
});
- 使用定制布局渲染视图
// 使用布局文件 views/layouts/custom.handlebars
app.get("/custom-layout", function(req, res) {
res.render("custom-layout", { layout: "custom" });
});
- 渲染纯文本输出
app.get("/test", function(req, res) {
res.type("text/plain");
res.send("this is a test");
});
- 添加错误处理程序
// 这应该出现在所有路由方法的结尾
// 需要注意的是,即使你不需要一个 " 下一步 " 方法
// 它也必须包含,以便 Express 将它识别为一个错误处理程序
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).render("error");
});
- 添加一个 404 处理程序
// 这应该出现在所有路由方法的结尾
app.use(function(req, res) {
res.status(404).render("not-found");
});
表单处理(掌握)
当你处理表单时,表单信息一般在 req.body 中(或者偶尔在 req.query 中)。你可以使用 req.xhr 来判断是 AJAX 请求还是浏览请求
- 基本表单处理
// 必须引入中间件 body-parser
app.post("/process-contact", function(req, res) {
console.log(
"Received contact from " + req.body.name + " <" + req.body.email + ">"
);
// 保存到数据库……
res.redirect(303, "/thank-you");
});
- 更强大的表单处理
// 必须引入中间件 body-parser
app.post("/process-contact", function(req, res) {
console.log(
"Received contact from " + req.body.name + " <" + req.body.email + ">"
);
try {
// 保存到数据库……
return res.xhr
? res.render({ success: true })
: res.redirect(303, "/thank-you");
} catch (ex) {
return res.xhr
? res.json({ error: "Database error." })
: res.redirect(303, "/database-error");
}
});
Handlebars 模板引擎
模板解决了在目标语言中编写代码的问题,同时也让插入动态数据成为了可能
Node 的世界里,有许多模板引擎可供选择,那么如何挑选呢?
- 性能
- 客户端、服务端或兼而有之?-选择那些在两端都表现优秀的模板引擎
- 抽象-你私下里厌恶 HTML 已久,希望有什么东西能把你从那些尖括号中拯救出来但是,作为一个 Web 开发者,HTML 是核心
Handlebars 是另一个流行的模板引擎 Mustache 的扩展
理解模板引擎的关键在于 context(上下文环境)。当你渲染一个模板时,便会传递给模板引擎一个对象,叫作上下文对象,它能让替换标识运行
注释
Handlebars 的注释看起来像 {{! comment goes here }}
服务器端模板
服务器端模板除了隐藏实现细节,还支持模板缓存,这对性能很重要。模板引擎会缓存已编译的模板(只有在模板发生改变的时候才会重新编译和重新缓存),这会改进模板视图的性能,默认情况下,视图缓存会在开发模式下禁用,在生产模式下启用。如果想显式地启用视图缓存,可以这样做:
app.set('view cache', true)
Express 中使用布局
//我们创建视图引擎时,会指定一个默认的布局
var handlebars = require('express3-handlebars').create({ defaultLayout: 'main' });
//Express 会在 views 子目录中查找视图,在 views/layouts 下查找布局。所以如
//果有一个叫作 views/foo.handlebars 的视图,可以这样渲染它
app.get('/foo', function(req, res){
res.render('foo');
});
你拥有的模板越多,需要维护的基础 HTML 布局就越多
客户端 Handlebars
我们既可以将 Handlebars
放在静态资源中引入,也可以使用一个 CDN
阅读记录中...持续更新
网友评论