结合之前项目,把这本书剩下部分看完了,所以整理出以下内容和思考:
- Node与Express开发(二)
- 一、模板引擎
- 二、表单处理
- 三、Cookie 与会话
- 四、中间件
- 五、持久化
- 六、路由
- 七、静态内容
- 八、在Express中实现MVC
- 九、安全
- 十、域名注册和托管服务
- 参考博客
一、模板引擎
我们已经看到了模板是如何让你的代码易写、易读、易维护的。因为模板,我们不需要在
JavaScript 中痛苦地拼凑 HTML 字符串了。
我们既可以将模板引擎文件放在静态资源中引入,也可以使用一个 CDN 链接引入,但是在 Node 的世界里,有许多模板引擎可供选择,那么如何挑选呢?
- 性能:模板引擎尽可能地快
- 选择那些在两端都表现优秀的模板引擎
- 抽象:你想让代码更可读(例如,在普通 HTML 文本中使用大括号),或者你私下里厌恶
HTML 已久,希望有什么东西能把你从那些尖括号中拯救出来?
二、表单处理
向服务器发送客户端数据
大体上讲,向服务器发送客户端数据有两种方式:查询字符串和请求正文。通常,如果是
使用查询字符串,就发起了一个 GET 请求;如果是使用请求正文,就发起了一个 POST 请求
(如果你反过来做,HTTP 协议并不会阻止你,但这是没有必要的:最好在这里坚持标准
实践)。
HTML 表单
<form action="/process" method="POST">
<input type="hidden" name="hush" val="hidden, but not secret!" />
<div>
<label for="fieldColor">Your favorite color: </label>
<input type="text" id="fieldColor" name="color" />
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
- 默认进行 GET 提交。 action 的值被指定为用于接收表单数据的 URL
- 建议你始终都为 action 提供一个有效值,即使是使用 AJAX 提交
编码
- 默认为
application/x-wwwform-urlencoded
(这只是一个冗长的用于“URL 编码”
的媒体类型)。它是受 Express 支持的基本、易用的编码 - 上传文件,需要使用 multipart/form-data 编码类型
处理表单的不同方式
- 如果不使用 AJAX,你唯一的选择是用浏览器提交表单,这会重新加载页面。然而,如何
重新加载页面由你来决定。处理表单时有两件事需要考虑:处理表单是哪个路径(action),
以及向浏览器发出怎样的响应。 - 如果你的表单使用的是 method="POST" (推荐使用),那么展现表单和处理表单通常使用相
同的路径:这样可以区分开来,因为前者是一个 GET 请求,而后者是一个 POST 请求。如果
采用这种方法,就可以省略表单上的 action 属性。
无论使用什么路径来处理表单,必须决定如何响应浏览器。下面是你的选项。
- 直接响应 HTML (不推荐)
处理表单之后,可以直接向浏览器返回 HTML(例如,一个视图)。如果用户尝试重新
加载页面,这种方法就会产生警告,并且会影响书签和后退按钮 - 302 重定向 (不推荐)
HTTP 1.1 增加了响应代码 303(请参阅其他),一种更合适的代码。除非你有理由让浏览器回到 1996 年,否则你应该改用 303。
- 303 重定向
HTTP 规范明确地表明浏览器 303 重定向后,无论之前是什么方法,都应该使用 GET 请求。这是用于响应表单提交请求的推荐方法
重定向指向哪里? 1. 重定向到专用的成功/失败页面 2. 运用flash消息重定向到原位置 ,由于有许多小表单分散在整个站点中(例如,电子邮件登录),最好的用户体验是不干扰用户的导航流 3. 运用flash消息重定向到新位置,大型表单通常都会有自己的页面,一旦提交就没有必要停留在这个页面上了
Express 表单处理
- GET 表单请求方式:表单域在
req.query
对象中,如果有一个名称属性为 email 的 HTML 输入字段,它的值会以 req.query.email 的形式传递到处理程序 - 如果使用 POST (推荐使用的),需要引入中间件来解析 URL 编码体。首先,安装 body-parser
中间件( npm install --save body-parser ),然后引入:app.use(require('body-parser')());
这个问题在 Epress 4.0 中消失了, body-parser 中间件是安全的并且推荐使用。一旦引入了 body-parser ,你会发现 req.body 变为可用,这样所有的表单字段将可用
<form
class="form-horizontal"
role="form"
action="/process?form=newsletter"
method="POST"
></form>
app.use(require("body-parser")());
app.get("/newsletter", function(req, res) {
// 我们会在后面学到 CSRF……目前,只提供一个虚拟值
res.render("newsletter", { csrf: "CSRF token goes here" });
});
app.post("/process", function(req, res) {
console.log("Form (from querystring): " + req.query.form);
console.log("CSRF token (from hidden form field): " + req.body._csrf);
console.log("Name (from visible form field): " + req.body.name);
console.log("Email (from visible form field): " + req.body.email);
res.redirect(303, "/thank-you");
});
处理 AJAX 表单
<body>
<form
class="form-horizontal newsletterForm"
role="form"
action="/process?form=newsletter"
method="POST"
></form>
</body>
<script>
$(document).ready(function() {
$(".newsletterForm").on("submit", function(evt) {
evt.preventDefault();
var action = $(this).attr("action");
var $container = $(this).closest(".formContainer");
$.ajax({
url: action,
type: "POST",
success: function(data) {
if (data.success) {
$container.html("<h2>Thank you!</h2>");
} else {
$container.html("There was a problem.");
}
},
error: function() {
$container.html("There was a problem.");
}
});
});
});
</script>
Express 代码,两个方便的属性: req.xhr 和 req.accepts
- 如果是 AJAX 请求(XHR 是 XML HTTP 请求的简称,AJAX 依赖于 XHR), req.xhr 值为 true
- ** req.accepts 试图确定返回的最合适的响应类型**
app.post("/process", function(req, res) {
if (req.xhr || req.accepts("json,html") === "json") {
// 如果发生错误,应该发送 { error: 'error description' }
res.send({ success: true });
} else {
// 如果发生错误,应该重定向到错误页面
res.redirect(303, "/thank-you");
}
});
文件上传
一般,文件上传可以使用 Connect 的内置中间件 multipart 来处理。但是,这个中间件已
经从 Connect 中移除了,一旦 Express 更新了对 Connect 的依赖项,它也将从 Express 中消
失,所以我强烈建议你不要使用这个中间件,对于复合表单处理,目前有两种流行而健壮的选择:Busboy 和 Formidable。
文件上传必须指定 enctype="multipart/form-data" 来启用文件上传。我们也可以通过 accept 属性来限制上传文件的类型(这是可选的)。
<form
class="form-horizontal"
role="form"
enctype="multipart/form-data"
method="POST"
action="/contest/vacation-photo/{year}/{month}"
>
<div class="form-group">
<label for="fieldName" class="col-sm-2 control-label">Name</label>
<div class="col-sm-4">
<input type="text" class="form-control" id="fieldName" name="name" />
</div>
</div>
</form>
对于每一个上传的文件,你会看到属性有文件大小、上传路径(通常是在临时目录中的一个随机名字),还有用户上传此文件的原始名字(文件名,而不是整表单处理个路径,出于安全隐私考虑)接下来如何处理这个文件就取决于你了:可以将它保存到数据库,将其复制到更持久的位置,或者上传到云端文件存储系统。记住,如果你基于本地存储保存文件,应用程序不能很好地扩展,基于云端存储是一个更好的选择。
jQuery 文件上传
如果你想为用户提供真正别出心裁的文件上传,可拖拽,可以看到上传文件缩略图,并查看进度条,那我向你推荐 Sebastian Tschan 的 jQuery File Upload(http://blueimp.github.io/jQuery-File-Upload)。
三、Cookie 与会话
网站不能记忆你从一个页面到下一个页面的喜好。所以我们需要用某种办法在 HTTP 上建立状态,于是便有了 cookie 和会话,不幸的是,cookie 的名声并不好,因为人们用它做了些邪恶的事情。(尽管 HTML5 已经引入了一些新特性,比如本地存储,它可以发挥相同的作用)
cookie 的想法很简单:服务器发送一点信息,浏览器在一段可配置的时期内保存它。发送哪些信息确实是由服务器来决定:通常只是一个唯一 ID 号,标识特定浏览器,从而维持一个有状态的假象。
关于 cookie,有些重要的事情需要你了解:
- cookie 对用户来说不是加密的: 服务器向客户端发送的所有 cookie 都能被客户端查看
- 用户可以删除或禁用 cookie: 用户对 cookie 有绝对的控制权,并且浏览器支持批量或单个删除 cookie
- 一般的 cookie 可以被篡改,比如说,有些极其愚蠢的人会执行 cookie 中的代码。要确保 cookie 不被篡改,请使用签名 cookie。
- cookie 可以用于攻击: 一种叫作跨站脚本攻击 (XSS)的攻击方式。XSS 攻击中有一种技术就涉及用恶意的 JavaScript 修改 cookie 中的内容。所以不要轻易相信返回到你的服务器的 cookie 内容
- 如果你滥用 cookie,用户会注意到:尽量把对 cookie 的使用限制在最小范围内。
- 如果可以选择,会话要优于 cookie:大多数情况下,你可以用会话维持状态,一般来说这样做是明智的,你不用担心会滥用用户的存储,而且也更安全
凭证的外化
外化第三方凭证是一种常见的做法,比如 cookie 秘钥、数据库密码和 API 令牌
凭证或者 token,是一种认证机制,相当于一把钥匙,有了它,才能打开特定的锁进入对应的门,这相当于增加了一层保护机制,过滤掉那些不合理的操作。
我们后面还会用这个文件存放其他凭证,但现在只需要 cookie 秘钥
module.exports = {
cookieSecret: " 把你的 cookie 秘钥放在这里 "
};
// 引入
var credentials = require("./credentials.js");
Express 中的 Cookie
1.在程序中开始设置和访问 cookie 之前,需要先引入中间件 cookie-parser npm install cookie=parser --save
app.use(require("cookie-parser")(credentials.cookieSecret));
2.完成这个之后,你就可以在任何能访问到响应对象的地方设置 cookie 或签名 cookie:
res.cookie("monster", "nom nom");
res.cookie("signed_monster", "nom nom", { signed: true });
3.要获取客户端发送过来的 cookie 的值(如果有的话),只需访问请求对象的 cookie 或 signedCookie 属性:
var monster = req.cookies.monster;
var signedMonster = req.signedCookies.monster;
4.要删除 cookie,请用 res.clearCookie
res.clearCookie("monster");
5.设置 cookie 时可以使用如下这些选项:
https://www.jianshu.com/p/7fc30d77cc5c
会话
从广义上来说,有两种实现会话的方法:把所有东西都存在 cookie 里,或者只在 cookie 里存一个唯一标识,其他东西都存在服务器上
第一种方式被称为“基于 cookie 的会话”,并且仅仅表示比使用 cookie 便利。然而,它还意味着要把你添加到 cookie 中的所有东西都存在客户端浏览器中,所以我不推荐用这种方式
如果你更愿意把会话信息存在服务器上,这也是我推荐的方式,那么你必须找个地方存储它。入门级的选择是内存会话。但也有个巨大的缺陷:重启服务器(你在本书中会做很多次)后会话信息就消失了。更糟的是,如果你扩展了多台服务器(参见第 12 章),那么每次请求可能是由不同的服务器处理的,所以会话数据有时在那里,有时不在。
会话是大家开发 Web 应用的常用技术,那么会话是什么,会话的用途还有工作原理又是什么?
在 web 应用中,作为客户端的浏览器,通过请求/响应这种模式访问同一个 web 网站的各种 web 页面,从开始访问这个服务器直到结束整个过程称为一次会话。
那么就有一个问题,同时访问这个服务器的用户请求有很多。但是无状态协议 HTTP,导致服务器连鱼的记忆都没有,把一个行为属于用户 A 还是用户 B 忘得一干二净。
解决上面的问题,就需要在客户端第一次请求 web 服务器的时候,web 服务器生成一种称为“会话标识符”的 ID,然后伴随响应发送给客户端,之后的通信中客户端发来的请求只需要带着这个特定的 ID,服务器就可以知道这是哪个客户端了。Cookie 就是这样的一种机制,它可以弥补 HTTP 协议无状态的不足。在 Session 出现之前,基本上所有的网站都采用 Cookie 来跟踪会话。客户端请求服务器,如果服务器需要记录该用户状态,就使用 response 向客户端浏览器颁发一个 Cookie。
Web 程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是 Cookie 与 Session。Cookie 通过在客户端记录信息确定用户身份,Session 通过在服务器端记录信息确定用户身份。
会话的用途
当你想跨页保存用户的偏好时,可以用会话。会话最常见的用法是提供用户验证信息,你
登录后就会创建一个会话。之后你就不用在每次重新加载页面时再登录一次。即便没有用
户账号,会话也有用。网站一般都要记住你喜欢如何排列东西,或者你喜欢哪种日期格
式,这些都不需要登录。
尽管我建议你优先选择会话而不是 cookie,但理解 cookie 的工作机制也很重要(特别是
因为有 cookie 才能用会话)。它对于你在应用中诊断问题、理解安全性及隐私问题都有帮助。
四、中间件
从概念上讲,中间件是一种功能的封装方式,具体来说就是封装在程序中处理 HTTP 请求的功能。从实战上讲,中间件只是一个有 3 个参数的函数:一个请求对象、一个响应对象和一个 next 函数,
中间件是在管道中执行的。你可以想象一个送水的真实管道。水从一端泵入,然后在到达
目的地之前还会经过各种仪表和阀门。这个比喻中很重要的一部分是顺序问题,你把压力
表放在阀门之前和之后的效果是不同的。同样,如果你有个向水中注入什么东西的阀门,
这个阀门“下游”的所有东西都会含有这个新添加的原料。在 Express 程序中,通过调用
app.use 向管道中插入中间件。
在 Express 4.0 之前,这个管道有些复杂,因为必须显式地把路由器连进来;在 Express 4.0 中,中间件和路由处理器是按它们的连入顺序调用的,顺序更清晰
那么请求在管道中如何“终止”呢?这是由传给每个中间件的 next 函数来实现的。如果不调用 next() ,请求就在那个中间件中终止了。
这里有三个中间件。第一个只是在将请求传给下一个中间件之前记录一条消息。然后下一
个中间件会真正地处理请求。注意,如果我们忽略了 res.send ,则不会有响应返回到客户
端,最终会导致客户端超时。最后一个中间件永远也不会执行,因为所有请求都在前一个
中间件中终止了。
app.use(function(req, res, next) {
console.log('processing request for "' + req.url + '"....');
next();
});
app.use(function(req, res, next) {
console.log("terminating request");
res.send("thanks for playing!");
// 注意,我们没有调用 next()……这样请求处理就终止了
});
app.use(function(req, res, next) {
console.log("whoops, i'll never get called!");
});
五、持久化
所有网站和 Web 应用程序(除了最简单的)都需要某种持久化方式,即某种比易失性内存更持久的数据存储方式,这样当遇到服务器宕机、断电、升级和迁移等情况时数据才能保存下来
1. 文件系统持久化
将数据存到扁平文件中(“扁平”的意思是文件没有内在结构,只是一串字节)。Node 通过 fs (文件系统)模块实现文件系统持久化(除非所有服务器都能访问一个共享的文件系统,否则就会遇到文件系统持久化的问题)
2. 云持久化
3. 数据库持久化
所有网站和 Web 应用程序(除了最简单的)都需要数据库。即便你的数据是二进制的,并且你用共享的文件系统或云存储,你也很有可能需要一个数据库来做那些二进制数据的目录
六、路由
认真思考你的 URL:它们在 20 年后还有意义吗?(200 年可能有点长:谁知道我们那时候还用不用 URL 呢。不过我佩服考虑得那么长远的精神。)认真考虑内容的分解。按逻辑归类,尽量别把自己逼入死角。这是科学,但也是艺术。
这里有些建议能帮你实现持久的 IA:
- 不在 URL 中暴露技术细节
- 避免在 URL 中出现无意义的信息
- 避免无谓的长 URL
- 单词分隔符要保持一致
- 绝不要用空格或不可录入的字符
- 在 URL 中用小写字母
路由路径和正则表达式
路由中指定的路径(比如 /foo)最终会被 Express 转换成一个正则表达式。某些正则表达式中的元字符可以用在路由路径中: + 、 ? 、 * 、 ( 和 ) 。我们看两个例子。比如你想用同一
个路由处理 /user 和 /username 两个 URL:
app.get("/user(name)?", function(req, res) {
res.render("user");
});
路由参数
日常使用的 Expression 工具箱中可能很少发现正则路由,但路由参数很可能要经常用。简而言之,这是一种把变量参数放到路由中成为其一部分的办法。比如我们想给每位职员一个页面。我们的数据库中有职员的简介和图片。随着公司规模的增长,给每位职员添加新的路由变得越来越不现实。我们看一下路由参数是怎么帮我们的
var staff = {
mitch: { bio: "Mitch is the man to have at your back in a bar fight." },
madeline: { bio: "Madeline is our Oregon expert." },
walt: { bio: "Walt is our Oregon Coast expert." }
};
app.get("/staff/:name", function(req, res) {
var info = staff[req.params.name];
if (!info) return next(); // 最终将会落入 404
res.render("staffer", info);
});
路由中可以有多个参数。比如说,如果我们想按城市分解职员列表:
var staff = {
portland: {
mitch: { bio: "Mitch is the man to have at your back." },
madeline: { bio: "Madeline is our Oregon expert." }
},
bend: {
walt: { bio: "Walt is our Oregon Coast expert." }
}
};
app.get("/staff/:city/:name", function(req, res) {
var info = staff[req.params.city][req.params.name];
if (!info) return next(); // 最终将会落入 404
res.render("staffer", info);
});
组织路由
在主应用程序文件中定义所有路由太笨重了。那样不仅会导致那个文件一直增长,还不利于功能的分离,因为那个文件里已经有很多东西了。一个简单的网站可能只有十几个路由,甚至更少,但比较大的网站可能有上百个路由。
那么如何组织路由呢?你想怎么组织自己的路由?
Express 对于你如何组织路由没有意见,所以怎么做完全是你的事情。我会在下一节谈到处理路由的流行做法,但现在我要先推荐下面这四条组织路由的指导原则
- 给路由处理器用命名函数
-
路由不应该神秘
因为大型的复杂网站可能比只有 10 个页面的网站需要更加复杂的组织方案。一种极端的做法是简单地把网站的所有路由都放到一个文件中,好知道它们在哪。对于大型网站来说,你可能不想这样,那就根据功能区域把路由分开。然而,即便如此,也应该清楚该到哪里找给定的路由。当你需要修订错误时,肯定不想花上一个小时来确定那个路由是在哪里处理的。 -
路由组织应该是可扩展的
如果你现在有 20 或 30 个路由,把它们都放在一个文件中可能没问题。如果在 3 年内你有了 200 个路由呢?这是有可能的。不管你选择用什么办法,都应该确保有增长的空间 -
不要忽视自动化的基于视图的路由处理器
如果你的网站由很多静态和固定 URL 的页面组成,你的所有路由最终看起来将像是:app.get('/static/thing', function(req, res){ res.render('static/thing'); }
。要减少不必要的重复代码,可以考虑使用自动化的基于视图的路由处理器。后面介绍了这种方式,并且它可以跟定制路由一起用。
在模块中声明路由
- 模块做成一个函数,让它返回包含“方法”和“处理器”属性的对象数组。然后你可以这样在应用程序文件中定义路由:
var routes = require("./routes.js")();
routes.forEach(function(route) {
app[route.method](route.handler);
});
这种方式有它的优势,并且可能非常适合动态地存储路由,比如在数据库或 JSON 文件中。
然而,如果你不需要那样的功能,我建议将 app 实例传给模块,然后让它添加路由。我们
的例子中用的就是这种方式。创建文件 routes.js,将所有路由都放进去:
module.exports = function(app){
app.get('/', function(req,res){
app.render('home');
}))
//...
};
然后连入路由require('./routes.js')(app);
按逻辑对处理器分组
给路由处理器用命名函数(要满足上面第一条指导原则),我们需要找地方放那些处理器。更极端的做法是给每个处理器建一个 JavaScript 文件。以某种方式将相关功能分组更好。那样不仅更容易利用共享的功能,并且更容易修改相关的方法,
现在我们先把功能分组到各自的文件中:handlers/main.js 中放首页处理器、 /about 处理
器,以及所有不属于任何其他逻辑分组的处理器,handlers/vacations.js 中放跟度假相关的
处理器,以此类推
// handlers/main.js:
var fortune = require("../lib/fortune.js");
exports.home = function(req, res) {
res.render("home");
};
exports.about = function(req, res) {
res.render("about", {
fortune: fortune.getFortune(),
pageTestScript: "/qa/tests-about.js"
});
};
//...
接下来修改 routes.js 以使用它:
var main = require("./handlers/main.js");
module.exports = function(app) {
app.get("/", main.home);
app.get("/about", main.about);
//...
};
这满足了所有的指导原则。/routes.js 非常直白。一眼就能看出来网站里有哪些路由,以及它们是在哪里处理的。我们还预留了充足的增长空间。我们可以把相关功能放到很多不同的文件中。如果 routes.js 变得笨重了,我们可以再用相同的技术,把 app 传给另一个模块,再注册更多路由
自动化渲染视图
如果你希望回到以前,只要把 HTML 文件放到一个目录中,然后很快你的网站就能提供它的旧时光,那么有这样想法的人不止你一个。如果你的网站有很多内容,但功能不多,你可能发现给每个视图添加一个路由是不必要的麻烦。好在我们可以解决这个问题
比如说你想添加文件 views/foo.handlebars,然后它就神奇地可以通过路由 /foo 访问了。我们看看怎么做。在我们的应用程序文件中,就在 404 处理器之前,添加下面的中间件:
var autoViews = {};
var fs = require("fs");
app.use(function(req, res, next) {
var path = req.path.toLowerCase();
// 检查缓存;如果它在那里,渲染这个视图
if (autoViews[path]) return res.render(autoViews[path]);
// 如果它不在缓存里,那就看看有没有 .handlebars 文件能匹配
if (fs.existsSync(__dirname + "/views" + path + ".handlebars")) {
autoViews[path] = path.replace(/^\//, "");
return res.render(autoViews[path]);
}
// 没发现视图;转到 404 处理器
next();
});
现在我们只要添加个 .handlebars 文件到 view 目录下,它就神奇地渲染在相应的路径上了。注意,常规路由会避开这一机制(因为我们把自动视图处理器放在了其他所有路由后面),所以如果你有个路由为 /foo 渲染了不同的视图,那它会取得优先权
其他的路由组织方式
最流行的两种路由组织方式是命名空间路由(namespaced routing)和随机应变路由(resourceful routing)
路由在项目中很重要,如果我在本章中介绍的基于模块的路由技术看起来不适合你,我建议你看看 express-namespace 或 express-resource 的文档。
七、静态内容
静态内容是指应用程序不会基于每个请求而去改变的资源。下面这些一般都应该是静态内容
- 多媒体
图片、视频和音频文件。当然,图片很有可能是即时生成的(尽管不太常见,但视频和音频也有可能如此),但大多数多媒体资源都是静态的。 - CSS
即便使用 LESS、Sass 或 Stylus 这样的抽象 CSS 语言,最后浏览器需要的还是普通CSS。 普通 CSS 是静态资源。 - JavaScript
服务器端运行的是 JavaScript 并不意味着没有客户端 JavaScript。客户端 JavaScript 是静态资源。当然,现在界限开始变得有点儿模糊了:我们既想在后台使用,又想在客户端使用的通用代码算什么呢?这个问题有解决办法,但最终送到客户端的 JavaScript 通常是静态的。 - 二进制下载文件
这包含所有种类:PDF、压缩文件、安装文件等类似的东西。
注意,如果你只是要搭建 API,可能没有静态内容html。
性能方面的考虑(重点)
如何处理静态资源对网站的性能有很大影响,特别是网站有很多多媒体内容时。在性能上主要考虑两点:减少请求次数和缩减内容的大小。
其中减少(HTTP)请求的次数更关键,特别是对移动端来说(通过蜂窝网络发起一次HTTP 请求的开销要高很多)。有两种办法可以减少请求的次数:合并资源和浏览器缓存
- 合并资源主要是架构和前端问题:要尽可能多地将小图片合并到一个子画面中。然后用CSS 设定偏移量和尺寸只显示图片中需要展示的部分。
- 浏览器缓存会在客户端浏览器中存储通用的静态资源,这对减少 HTTP 请求有帮助。尽管浏览器做了很大努力让缓存尽可能自动化,但它也不是完美的:在让浏览器缓存静态资源方面,还有很多你能做也应该做的工作。
- 最后,我们可以通过缩减静态资源的大小来提升性能。有些技术是无损的(不丢失任何数据就可以实现资源大小的缩减),有些技术是有损的(通过降低静态资源的品质实现资源大小的缩减)。无损技术包括 JavaScript 和 CSS 的缩小化,以及 PNG 图片的优化。有损技术包括增加 JPEG 和视频的压缩等级。我们会在本章中讨论缩小和打包(也可以减少HTTP 请求的次数)。
八、在Express中实现MVC
模型 - 视图 - 控制器(MVC)模式。这个概念相当古老了,实际上可以追溯到 20 世纪 70 年代。它的复兴要归功于它在Web 开发领域中的适用性。
- 建议你在项目中创建一个叫 models 的子目录来存放模型。只要你有要实现的逻辑,或要存储的数据,都应该在 models 目录下的文件里完成
MVC (Model View Controler)
MVC本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,C则是控制器。使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据你可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。
视图
视图是用户看到并与之交互的界面(它可以包括一些可以显示数据信息的页面,或者展示形式。例如jsp,html,asp,php。
模型
模型表示企业数据和业务规则(可以说就是后端接口,用于业务处理)。在MVC的三个部件中,模型拥有最多的处理任务。例如它可能用象EJBs和ColdFusion Components这样的构件对象来处理数据库。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
控制器
控制器接受用户的输入并调用模型和视图去完成用户的需求(接受客户发送的请求,根据请求调用所对应的接口,然后模型业务处理后返回的数据,由控制器决定调用那个View展示)
现在我们总结MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。
MVC优点
首先,最重要的一点是多个视图能共享一个模型
由于运用MVC的应用程序的三个部件是相互对立,改变其中一个不会影响其它两个,所以依据这种设计思想你能构造良好的松偶合的构件。
控制器的也提供了一个好处,就是可以使用控制器来联接不同的模型和视图去完成用户的需求,这样控制器可以为构造应用程序提供强有力的手段。给定一些可重用的模型和视图,控制器可以根据用户的需求选择模型进行处理,然后选择视图将处理结果显示给用户。
MVC缺点
MVC的缺点是由于它没有明确的定义,所以完全理解MVC并不是很容易。使用MVC需要精心的计划,由于它的内部原理比较复杂,所以需要花费一些时间去思考。
你将不得不花费相当可观的时间去考虑如何将MVC运用到你的应用程序,同时由于模型和视图要严格的分离,这样也给调试应用程序到来了一定的困难。每个构件在使用之前都需要经过彻底的测试。一旦你的构件经过了测试,你就可以毫无顾忌的重用它们了。
在 Node 中,MVC 架构下处理请求的过程如下:
- 请求抵达服务端
- 服务端将请求交由路由处理
- 路由通过路径匹配,将请求导向对应的 controller
- controller 收到请求,向 model 索要数据
- model 给 controller 返回其所需数据
- controller 可能需要对收到的数据做一些再加工
- controller 将处理好的数据交给 view
- view 根据数据和模板生成响应内容
- 服务端将此内容返回客户端
九、安全
1.HTTPS
使用 HTTPS 是提供安全服务的第一步
2.防止跨站请求伪造
跨站请求伪造(CSRF)攻击利用了用户一般都会相信浏览器并且在同一个会话中访问多个网站这样的事实。在 CSRF 攻击中,恶意站点上的脚本会请求另外一个网站:如果你在另一个网站上登录过,恶意网站可以成功访问那个网站上的安全数据。要防范 CSRF 攻击,你必须想办法确保请求合法地来自你的网站。我们的做法是给浏览器传一个唯一的令牌。当浏览器提交表单时,服务器会进行检查,以确保令牌是匹配的。
3.认证与授权
尽管这两个词经常交叉使用,但其实它们之间有些细微的差别。认证是指验证用户的身份,即他们是自己所宣称的人。授权是指确定用户有哪些权力,可以访问、修改或查看什么
十、域名注册和托管服务
如果要用现实世界的事物类比,它们之间的差别就好像公司名称和物理地址一样。域名就像公司名称(苹果),而 IP 地址就像物理地址(1 Infinite Loop, Cupertino, CA 95014)。如果你要开车去苹果总部,需要知道物理地址。好在如果你知道公司名称,很可能也能找到物理地址。采用这种抽象的另一个原因是这有助于组织迁移(搬到一个新的物理地址去),即便它搬家了,人们仍然可以找到它。而托管服务器描述的是运行网站的真实计算机。我们继续用类比来解释这个概念,托管服务器相当于你到达物理地址时见到的真实建筑
- 域名将一个人类容易记住的名字(比如 google.com)和一个 IP 地址(74.125.239.13)映射起来
- 托管服务器描述的是运行网站的真实计算机
一旦有了域名,并且正式上线后,你可以通过多个 URL 访问网站。
域名系统
域名系统(DNS)负责将域名映射到 IP 地址。这个系统相当复杂,但作为站长,有些跟DNS 有关的知识你应该掌握。
顶级域名
域名的结尾部分(比如 .com 或 .net)叫作顶级域名(TLD)。
子域名
TLD 在域名后面,子域名在域名前面。目前最常见的子域名是 www
- 建议用子域名给网站或服务有显著区别的部分分区。比如说,我认为用 api.meadowlarktravel.com 提供 API 是子域名的良好用法
- 子域名的另一个明智用途是将管理界面跟公众界面分开(admin.meadowlarktravel.com,只供员工使用)。
托管
在考察云托管时,你会遇到 SaaS、PaaS、IaaS 这几个缩写:
- 软件即服务(SaaS)
SaaS 一般用来描述提供给你的软件(网站、应用):你只是使用它们。谷歌文档和Dropbox 就是这样的软件
- 平台即服务(PaaS)
PaaS 为你提供了所有的基础设施(操作系统、网络,所有都弄好了)。你只需要编写应用程序。尽管 PaaS 和 IaaS 之间的界限比较模糊(作为开发者,你会发现自己经常会跨过这条线),这一般是我们在本书中讨论的服务模型。如果你运营着一个网站或网络服务,PaaS 可能就是你要找的东西
- 架构即服务(IaaS)
IaaS 最灵活,但也是有代价的。它只是提供虚拟机和基本的网络连接。然后你负责安装和维护操作系统、数据库和网络策略。除非你需要对环境做这种层面的控制,否则一般还是会选 PaaS。(注意,PaaS 确实允许你控制操作系统和网络配置的选择,只是你不必亲自动手实现。)
部署
最起码你也应该用 SFTP
或 FTPS
(别搞混了),但还有更好的办法:基于 Git 的部署
参考博客
web 开发(三) 会话机制,Cookie 和 Session 详解
https://www.cnblogs.com/hzb462606/p/8977444.html
浅谈 Web 会话
https://www.jianshu.com/p/52d04a6f6fe5
ps:这本书可能翻译有些小问题,废话稍多,但是小而全,还是能学到东西的