「LeanCloud Web 应用开发实践」系列直播及文章分享持续进行中。
每周二周四晚上 8 点开始,时长预计 45 分钟。在 “leanCloud通讯” 微信公众号回复 “公开课” 即可获取直播链接。
《LeanCloud Web 应用开发实践公开课》上期回顾和本期主题介绍。
为了理清 currentUser 的状态,需要看下不同类型的 WEB 应用是如何运作的。
使用云引擎 demo 来演示,可以使用 https://todo-demo.leanapp.cn 来做接下来的尝试,或者自己部署该 demo 应用尝试(代码 版本: 1efc44a )。
这个 demo 是一个典型的服务端渲染的应用。所谓的服务端渲染是指浏览器请求服务端的地址或资源时,服务端返回一个 HTML 文档(一个很大的字符串),浏览器收到 HTML 文档之后,进行渲染并呈现页面。通过云引擎的自定义路由很容易实现这样的 WEB 应用。
如果单纯看请求和响应,以登录页面为例:
$ curl -v https://todo-demo.leanapp.cn/users/login
> GET /users/login HTTP/1.1
> Host: todo-demo.leanapp.cn
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
<
<!DOCTYPE html><html><head><title>用户登录</title>...<input type="submit"
value="登录" class="btn btn-default"><a href="/users/register" class="btn btn-default">注册</a></div></form></div></body></html>
先配置云引擎 cookieSession中间件 (代码):
app.use(AV.Cloud.CookieSession({ secret: '05XgTktKPMkU', maxAge: 3600000, fetchUser: true }));
用户登录路由的 代码 如下:
router.post('/login', function(req, res, next) {
var username = req.body.username;
var password = req.body.password;
AV.User.logIn(username, password).then(function(user) {
res.saveCurrentUser(user);
res.redirect('/todos');
}, function(err) {
res.redirect('/users/login?errMsg=' + err.message);
}).catch(next);
});
在云引擎的自定义路由中调用了 AV.User.logIn 的 API,并且调用了 res.saveCurrentUser(user); 来将用户信息写入 cookie。
整个请求和响应的流程:
curl -v 'https://todo-demo.leanapp.cn/users/login' -H 'content-type: application/x-www-form-urlencoded' --data 'username=zhangsan&password=zhangsan'
var username = req.body.username;
var password = req.body.password;
AV.User.logIn(username, password)
res.saveCurrentUser(user);
该操作在最终请求响应时, cookieSession 中间件 会将用户的信息写入 header 的 Set-Cookie 中。
< HTTP/1.1 302 Found
< Content-Type: text/plain; charset=utf-8
< Location: /todos
< Set-Cookie: avos:sess=eyJfdWlkIjoiNTUxZDJkZTZlNGIwYjM2NzFhZWNmZWIyIiwiX3Nlc3Npb25Ub2tlbiI6ImFjajd3eTgwdDhmdGtpYzRxYzY1ZDNiZDgifQ==; path=/; expires=Tue, 08 Aug 2017 15:49:21 GMT; secure; httponly
< Set-Cookie: avos:sess.sig=TyI_sXTvNa4nUSxByoX3zxWRZ8M; path=/; expires=Tue, 08 Aug 2017 15:49:21 GMT; secure; httponly
<
在响应里多了两个 Set-Cookie
信息,收到这样的响应后,浏览器会在 cookie 里写入这些信息,其中 avos:sess
对应的值是一个 base64 字符串,具体内容是 :
{"uid":"551d2de6e4b0b3671aecfeb2","sessionToken":"acj7wy80t8ftkic4qc65d3bd8"}
所以标示用户身份的 sessionToken
信息保存在 cookie 里。
avos:sess.sig
是一个校验使用字符串,可以不关心。cookie 有个特性:每次请求服务器时,会把 cookie 自动添加到请求的 header 中。所以之后再请求该站点的其他页面:
curl 'https://todo-demo.leanapp.cn/todos' -H 'cookie: avos:sess=eyJfdWlkIjoiNTUxZDJkZTZlNGIwYjM2NzFhZWNmZWIyIiwiX3Nlc3Npb25Ub2tlbiI6ImFjajd3eTgwdDhmdGtpYzRxYzY1ZDNiZDgifQ==; avos:sess.sig=TyI_sXTvNa4nUSxByoX3zxWRZ8M'
当这些请求到达云引擎应用之后, cookieSession 中间件 会再次起作用,从请求 header
中取出相关的 cookie 并校验,从中能获取到登录用户的 sessionToken
,然后从存储服务获取该用户的信息(或称为判断 sessionToken
是否有效),并将 user 信息赋值到 request.currentUser
属性上。
之后,请求会到达具体的自定义路由,此时就可以从 request.currentUser
获取发起请求的登录用户信息了。
对于服务端渲染的应用:
服务端渲染的应用在用户体验方面存在不足,比如一系列表单填写完成之后一次性提交,此时服务端判断参数是否有效再响应用户;还有服务端每次响应整个 HTML 有很大的带宽浪费。之后出现了 AJAX 技术使得光标离开某个表单项之后,浏览器单独发送请求到服务端直接判断其有效性并迅速响应;并且每次浏览器与服务端通信都是一些数据结构(JSON 或者 XML)来降低流量,浏览器根据数据结果来修改 DOM 结构进行展现。
LeanCloud 将存储服务以 REST API 的方式提供服务,让前端(浏览器,或移动设备)可以方便的操作数据,这使得基于 LeanCloud 的应用基本都是前后端分离的。
当前示例使用一些简单页面来模拟前后端分离的应用。
请求一个前后端分离的示例(页面代码):
$ curl 'https://todo-demo.leanapp.cn/static/page1.html'
<html>
<head>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
</head>
<body>
<h1>page1</h1>
<script>
...
console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
console.log('开始登录...')
AV.User.logIn('zhangsan', 'zhangsan')
.then(function(user) {
console.log('登录成功: username: %s, sessionToken: %s', user.get('username'), user._sessionToken)
})
.then(function() {
console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
...
</script>
</body>
</html>
服务端响应了一个页面,浏览器渲染页面时,会执行 script 部分的脚本,该脚本可能会做大量工作,比如生成或者修改页面 DOM,并向服务器发请求获取其他数据。比如这个示例就在页面打开之后 3 秒,通过 JS SDK 向服务器发起一个用户登录的请求,收到响应后在浏览器 console 输出一些日志。
使用浏览器请求 page1 ,整个流程如下:
var APP_ID = 'kdrt5GNCjojUjiIujawd5A4n-gzGzoHsz';
var APP_KEY = 'Xvxjo6SVUITIqet69q3mudlF';
AV.init({
appId: APP_ID,
appKey: APP_KEY
});
setTimeout(function() {
console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
console.log('开始登录...')
AV.User.logIn('zhangsan', 'zhangsan')
}, 3000)
{
"sessionToken": "u2xtq3dxxvonapqn5uc9snbz7",
"updatedAt": "2017-08-07T14:39:07.619Z",
"objectId": "59887b8b570c350062430143",
"username": "zhangsan",
"createdAt": "2017-08-07T14:39:07.619Z",
"emailVerified": false,
"mobilePhoneVerified": false
}
JS SDK 将该信息反序列化构造出AV.User
对象,然后将其保存在浏览器 Local Storage
中。
通过 JS SDK 的 AV.User.current()
方法获取当前登录用户,本质上就是去 Local Storage
获取用户的信息并返回调用方(比如请求 page2 ,页面代码):
...
console.log('当前登录用户:%s', AV.User.current() && AV.User.current().get('username'))
...
云函数 是运行在云引擎(服务端)的一个方法,通过 JS SDK 的 AV.Cloud.run 方法可以很方便的调用。
示例中定义了一个云函数(代码):
...
AV.Cloud.define('whoami', function(req, res) {
console.log('whoami:', req.currentUser);
var username = req.currentUser && req.currentUser.get('username');
res.success(username);
});
...
在浏览器中通过 JS SDK 调用云函数(请求 page3 ,页面代码):
...
AV.Cloud.run('whoami')
.then(function(username) {
console.log('whoami:', username);
})
...
浏览器请求云函数流程如下:
通过 JS SDK 调用云函数,并根据需要传递参数(示例中未涉及)。JS SDK 会根据 Local Storage 中的信息在请求的 header 中附加 X-LC-Session ,值为用户身份标示 sessionToken。
请求到达云引擎应用,云引擎中间件会判断是否存在 X-LC-Session 的信息,如果有,就使用该值通过存储服务获取用户信息,并赋值给 request.currentUser。
请求进入云函数相关代码流程,开发者就可以获取到 currentUser 了:
console.log('whoami:', req.currentUser);
var username = req.currentUser && req.currentUser.get('username');
res.success(username);
因为使用 LeanCloud 的前后端分离应用,运行应用的域(比如云引擎的二级域名 http://abc.leanapp.cn )和提供服务的域(比如 LeanCloud 存储服务 https://api.leancloud.cn/1.1/class/Todo )不同,根据 cookie 的安全策略是不能在不同域传递 cookie 的。
所以 LeanCloud 的 SDK 会在请求的 header 中携带信息让服务端感知到当前登录用户。
基于 LeanCloud 的前后端分离应用:
登录方式 | 云引擎自定义路由 | 浏览器 JS SDK + REST API(云函数) |
---|---|---|
保存位置 | cookie | Local Storage |
服务端感知方式 | 通过 cookieSession 中间件 从 cookie 获取 | 通过云引擎中间件从 header 获取 |
与服务端交互方式 | 页面跳转或表单提交。因为同域,cookie 自动携带 | 通过 JS SDK 操作存储服务的数据或调用云函数。因为跨域,cookie 无法携带,使用 header。 |
服务端用户登录/登出操作 | 自定义路由中用户登录/登出后可以操作相关 cookie,浏览器 cookie 更新,影响后续请求。 | 云函数中用户登录/登出没有意义,不会改变浏览器 Local Storage 的内容,不影响后续浏览器对云函数的请求。 |
相信到这里,最初提出的疑问可以解释了:
在云引擎登录了,但是云函数却没有 currentUser
云引擎自定义路由登录只改变浏览器 cookie,而后续在浏览器通过 JS SDK 调用云函数时,是否携带 SessionToken
的信息在 header
中,和 cookie 无关。
在浏览器调用 JS SDK 登录用户,页面跳转时云引擎中没有 currentUser
浏览器调用 JS SDK 用户登录相关的 API 之后,只是 Local Storage
有变化,并在之后的访问存储服务或云函数时会将 sessionToken
携带在 header
中,cookie 并无变化。而应用页面跳转,或者 form 表单提交访问云引擎自定义路由时, cookieSession 中间件 无法从 cookie 中获取需要的信息。
sessionToken
。sessionToken
,并调用 JS SDK 的 AV.User.become
方法在浏览器登录。在此之后,不管是请求云引擎自定义路由还是请求云函数,都能确保 currentUser 的存在。当然 cookie 还存在过期的问题,不过这里就不展开讨论了。
通过控制云引擎中间件的 fetchUser 属性,可以降低一部分不必要的 _User
的查询请求。
以 AV.Cloud.define API 为例,当收到云函数请求时,云引擎中间件从请求 header
中获取 sessionToken
信息,并且确认下 fetchUser
属性的值:
sessionToken
从存储服务读取用户(_User
表)的信息。之后将 sessionToken
和 currentUser
信息复制到 request
的相关属性上。sessionToken
赋值到 request 的属性上。也就意味着云函数中 ```request.currentUser
为 undefined
。如果云函数的相关逻辑需要 _User
的其他信息,比如 username
,那就设置 fetchUser
为 true
,或者不设置使其保持默认值。
否则,可以设置 fetchUser
为 false
,但是需要在所有数据操作(和云函数调用)时将 sessionToken 加入到请求中:
var query = new AV.Query('Todo');
query.equalTo('status', 0);
query.find({sessionToken: req.sessionToken})
如果 req.sessionToken 有效,则存储服务会根据查询条件和 ACL 返回适当的信息。
如果 req.sessionToken 无效(过期或伪造),则存储服务可能因为 ACL 拒绝操作或返回空结果。
文章浏览阅读635次。【代码】tkinter创建子窗口(只创建一个)_tkinter子窗口
文章浏览阅读345次,点赞9次,收藏6次。博主介绍:CSDN特邀作者、985计算机专业毕业、某互联网大厂高级全栈开发程序员、码云/掘金/华为云/阿里云/InfoQ/StackOverflow/github等平台优质作者、专注于Java、小程序、前端、python等技术领域和毕业项目实战,以及程序定制化开发、全栈讲解、就业辅导、面试辅导、简历修改。精彩专栏 推荐订阅2023-2024年最值得选的微信小程序毕业设计选题大全:100个热门选题推荐2023-2024年最值得选的Java毕业设计选题大全:500个热门选题推荐。_微信小程序书代码
文章浏览阅读3.6k次,点赞2次,收藏2次。DELL7080台式机两块硬盘。_没有u盘怎么装ubuntu
文章浏览阅读32次。题面Bessie wants to navigate her spaceship through a dangerous asteroid field in the shape of an N x N grid (1 <= N <= 500). The grid contains K asteroids (1 <= K <= 10,000), which are conv...
文章浏览阅读2.6w次,点赞21次,收藏112次。机器视觉则主要是指工业领域视觉的应用研究,例如自主机器人的视觉,用于检测和测量的视觉系统等。它通过在工业领域将图像感知、图像处理、控制理论与软件、硬件紧密结合,并研究解决图像处理和计算机视觉理论在实际应用过程中的问题,以实现高效的运动控制或各种实时操作。_工业机器视觉系统的构成与开发过程(理论篇—1
文章浏览阅读5.9w次,点赞32次,收藏58次。legend 传奇、图例。plt.legend()的作用:在plt.plot() 定义后plt.legend() 会显示该 label 的内容,否则会报error: No handles with labels found to put in legend.plt.plot(result_price, color = 'red', label = 'Training Loss') legend作用位置:下图红圈处。..._plt.legend
文章浏览阅读141次。LongTimeAgo题目是一道备受关注的CTF(Capture The Flag)题目,旨在考察参赛选手在网络安全方面的技能和知识。该题目主要涉及密码学和逆向工程的内容,要求选手通过分析给定的程序,找到隐藏的漏洞并获取关键信息。该题目主要涉及到密码学和逆向工程的内容,需要选手通过分析给定的程序,找到隐藏的漏洞并获取关键信息。观察源代码中的加密函数,我们可以发现密钥的长度是可变的,它取决于输入明文的长度。观察源代码中的加密函数,我们可以发现密钥的长度是可变的,它取决于输入明文的长度。是当前明文字符的索引。_强网杯
文章浏览阅读289次。进程间通信简称IPC(interprocess communication),是指在不同进程间传播或交换信息。一个进程需要另一个或另一组进程发送消息,通知它(们)发生了某种事件;一个进程需要将它的数据发送给另一个进程;,信号量,共享内存,消息队列和套接字。多个进程之间共享同样的资源;一个进程完全控制另一个进程;传统的UNIX进程间通信机制。System V IPC机制。System V 消息队列。System V 共享内存。System V 信号量。POSIX 消息队列。POSIX 共享内存。_嵌入式 linux 跨进程 信号
文章浏览阅读175次。Netperf是一种网络性能的测量工具,主要针对基于TCP或UDP的传输。Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer)模式和请求/应答(request/reponse)模式。工作原理Netperf工具以client/server方式工作。server端是netserver,用来侦听来自client端的连接,c..._netperf 麒麟
文章浏览阅读1.1k次,点赞2次,收藏3次。作者| qcrao责编 | 屠敏出品 | 程序员宅基地刚开始写这篇文章的时候,目标非常大,想要探索 Go 程序的一生:编码、编译、汇编、链接、运行、退出。它的每一步具体如何进行,力图弄清 Go 程序的这一生。在这个过程中,我又复习了一遍《程序员的自我修养》。这是一本讲编译、链接的书,非常详细,值得一看!数年前,我第一次看到这本书的书名,就非常喜欢。因为它模仿了周星驰喜剧..._go run 每次都要编译吗
文章浏览阅读1.4k次,点赞4次,收藏2次。0、C++的输入输出分为三种:(1)基于控制台的I/O (2)基于文件的I/O (3)基于字符串的I/O 1、头文件[cpp] view plaincopyprint?#include 2、作用istringstream类用于执行C++风格的字符串流的输入操作。 ostringstream类用_c++ istringstream a >> string
文章浏览阅读2k次,点赞3次,收藏14次。我们在每个修改的地方都记录一条对应的 redo 日志显然是不现实的,因此实现方式是用时间换空间,我们在数据库崩了之后用日志还原数据时,在执行这条日志之前,数据库应该是一个一致性状态,我们用对应的参数,执行固定的步骤,修改对应的数据。1,MySQL 就是通过 undolog 回滚日志来保证事务原子性的,在异常发生时,对已经执行的操作进行回滚,回滚日志会先于数据持久化到磁盘上(因为它记录的数据比较少,所以持久化的速度快),当用户再次启动数据库的时候,数据库能够通过查询回滚日志来回滚将之前未完成的事务。_binglog