前后端数据交互
1、简介、说明
传统软件开发一般不存在前后端数据交互的说法,往往是简单的MVC架构,将数据直接绑在html相应的字段,像PHP,jsp,.net最开始都是这样。而随着软件技术的发展,业务复杂度,维护成本高等诸多因素的影响下,渐渐的,前后端开始分离,分别开发。而后又随着web2.0时代的到来,动态网站的需求,页面的局部刷新等等,前端工程越来越大,规模也逐步的起来了。前后端逐步变成了两种相互独立的业务开发模块,而数据的交互也成为了我们日常开发过程中最常见,最频繁的沟通渠道。本文主要是做一个科普,对于相应的技术点不会涉及的太深,面向的主要人群是刚刚接触到前后端开发,或者刚刚工作的人。
2、介绍http,https,报文
在大学期间我们通常会学到http是超文本传输协议,运行于TCP协议簇上,是一个简单的请求-相应的协议。相应的,我们能知道最起码http是一个可靠的协议,它会有一个三次握手的过程。
三次握手用不恰当的例子一句话简单介绍三次握手的概念,你到别人家去,需要敲门,然后家主人问一句,谁呀,你说你是谁谁谁,那么这个过程就是一次三次握手,用于确立身份。
当然了,三次握手不是http的优点,而是tcp这个协议的优点,而http是建立和运行在tcp以上的,所以也继承了这个优点。但是我们访问一个网站的时候,可能有大量的请求,往往一个页面就有三四十个请求,总不至于每个请求我们都来那么一次三次握手,所以http为了解决这个问题,将自己作为一种无状态协议,不保留与用户之间的任何状态,这样就大大减轻了服务器的记忆负担,从而保证较快的相应速度。
从原理上来讲,http允许传输任何类型的数据,并根据不同的大小进行不同程度的压缩传输,但是大多数情况下我们需要对传输的格式做一定的限制.常用的传输格式有xml,json,yml,text,流传输,根据需要传值的不一样选用不同类型的数据格式。
而https则在http的基础上加了一层secure,意味着更安全的http,在前后端的传输中,我们很难对传输的数据进行加密,传统的md5,sha1都是对数据本身进行加密,但是加密后的密文依然每个数据报文都能够被监听,这时就需要http+ssl进行传输加密和身份认证,以此来保证传输的安全性。HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。ssl的默认端口号是443,它在http的默认端口加了一个验证的层,身份验证通过后再转发到http的默认端口。同时ssl还解决了http的一些可靠性问题,例如传输数据不完整,它在数据响应时进行了数据完整性的校验;又例如数据的安全性,ssl也可以将传输的数据按照一定的序列化的形式进行封装,这样就像你的快递外面被一个无提示的盒子包着,别人也就无法知道里面是什么了,一般这种会用于防治http的中间人攻击。
报文就是我们平时说的传输中的数据。我们可以通过浏览器中的f12打开开发者工具,然后看到具体的内容,大体分为请求行,请求头,请求体(Body)组成。像我们经常看到的http:www.baidu.com?a=b这个问号后面的就是常见的get请求所带的参数,我们一般称为query,而post请求所带的参数在请求体中,也就是我们后端同学经常会写的@RequestBody这个注解,就是为了拿到body中的值,对应的还有一个@RequestParams拿的是get所带的值。下面给个真实的例子
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/96.0.4664.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: BIDUPSID=8B0207CE0B6364E5934651E84F17999B; PSTM=1619707475;
3、传输时的数据类型
在日常开发过程中,我们总会去定义接口的入参和出参,往往这些参数都是以json的格式进行传输,而json本身又是一种特殊的字符串,我们不能以偏概全的理解为传输只能以文本的形式进行传输,其实可以用的数据类型有很多。
application/xhtml+xml :XHTML格式
application/xml :XML数据格式
application/atom+xml :Atom XML聚合格式
application/json :JSON数据格式
application/pdf :pdf格式
application/msword :Word文档格式
application/octet-stream :二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded :默认的表单上传格式
...
还有很多,不一一列出来,这些都是跟我们生活很贴近或者是经常使用的格式。
而JSON作为我们最常使用的格式,当然有它独到的好处。第一个就是对于传输来说更加的方便,因为冗余的字符相对其他来说更少,最大的对比就是xml就是需要前后标签,虽然传二进制的效率肯定是最高的,但是二进制不好解析,解析前还得进行反解,并且常见的浏览器都对json做了直接的支持。第二个就是方便转换,前端的js/ts都天生的兼容json,js中的对象数据格式与json一致,后端java中最常用的框架spring的出入接口也支持json,并且有很多json的api都提供了json字符串转成对象,对象转成json格式的方法。最后一个就是json具有易于阅读的优点,因为具有良好结构,所以我们也可以用json来解读一个数据的数据结构,这个也是最近low code或者无代码中很核心的json schema。
除了JSON外,我们在前后端的开发工作中还有文件需要传输,文件的传输通常会使用multipart/form-data来作为数据格式,也有一些情况会使用x-www-form-urlencoded、raw或者application/octet-stream作为数据格式,我们就不一一展开介绍了。
4、异步数据交换和同步数据交换
在传统的开发模式中,页面的数据刷新基本采用的都是路由跳转,或者location.reload()来重新获取服务端的数据,我们也称这种数据获取方式为同步数据交换,也有地方称为同步传输。这种数据加载模式,是自顶向下的加载包括页面样式,html标签,还有脚本文件,一次性的加载,然后需要加载js的时候就阻塞掉,等加载完成了再继续加载,所有的控制也都由后端和浏览器掌控。
首先,我们说清,技术没有好与坏,优或劣,只有不同的使用场景,选用不同的技术方案而已
异步数据交换是相对于同步数据交换的。同步是一次性加载,而异步就是不确定的先后顺序,一般来说就是,比如,我写在最后的代码可能会最先执行,当然了,这里说的可能,因为异步是不确定的,不可控的,如果需要控制部分异步,则需要将这部分的异步交换变成同步数据交换的模式去做,具体的实现方式我们不去叙述,只谈论理论。
在实际的工作中,我们希望页面的数据因为一个事件的触发而去动态的刷新局部数据,比如页面上有一个刷新按钮,我希望按了它后刷新新闻列表,而不是页面全部重新加载,那么这个时候所进行的及时异步数据加载。这样做的好处就是我只需要局部刷新,减少用户的等待时间,从而提升用户体验感。
不管是同步还是异步数据交换时,我们经常会定义响应的数据格式,就我们工作中最常见到的是 {code: '',data: '',message: ''}这样的数据,code是该条响应数据的状态,data是响应的实际数据,message用于携带对应的信息。
1**是服务器的信息,服务器收到请求后,需要请求者继续执行操作
2**是服务器的成功响应
3**重定向到其他页面或数据
4**客户端错误,一般是前端或网络的问题
5**服务器错误,可能是服务器系统错误
5、session、cookie
数据传输中还可以利用session、cookie带参的方式进行传输和实现用户跟踪。
session跟踪是web程序中最常见的技术,用来跟踪用户的整个会话,以此来制作用户画像,从而获取用户的访问习性从而提供更好的服务支持。cookie经常会记录信息来确认用户的信息,而session通过服务器记录信息确认用户身份,当然,也可以通过其他方式,比如header,后面我们会讲。
前面我们说过,http是一个无状态的协议,这个无协议在这里就很好理解,比如说你去一家店铺,进去后,别人说欢迎光临,当你出去后,服务员就完全忘记你是谁了,你下次进去的时候,服务员对你毫无印象,这就是无状态协议。这是一个很大的特点,但是也造成很大的问题,比如说,同样是这家店铺,他们在统计每天该店铺的顾客访问量,肯定是不希望有重复记录的,那么你频繁的进出势必会影响到他们的统计,那么这个时候就需要一种机制,既可以拥有无状态的特点,也同时避免无状态所带来的无法记忆的问题,这里的无法记忆用电子商务来讲就是,你将商品A放入购物车,你再进入B商品页面,加入购物车,如果是无状态的话就无法判断你到底是谁了。所以我们需要跟踪用户,去临时的保存用户信息。
Cookie就是这样一种解决方案,它的出现就弥补了http无状态协议的不足,在session出现之前,基本上所有的网站都是使用这种方式来跟踪用户的。
Cookie的初衷是,既然是无状态的协议,那么我就给每个访问的人一个通行证,那么服务器就可以通过这个通行证确认客户的身份。在每一次请求的过程中,都会携带上cookie,在服务器发出相应时也会携带cookie,在这过程中,服务器就知道用户的身份了。那么这样也会给我们带来其他的问题,例如隐私问题,我们做的什么事情都会服务器记录下来了,所有的信息都可能被第三方所获取,这个第三方有可能是网站服务商,也有可能是挂载在这个网站上的广告商,也有可能是其他的用户。
Cookie有着隐私问题,安全性问题,明文传输,传输量小等等缺点,session也就应运而生了,从字面就可以指导session是会话的意思,那么session仅仅会保存一次性的会话中的所有信息,当用户关闭掉这个网站的所有页面,那么该次会话就结束了,会话结束了后数据就全部清楚了,隐私问题就相对解决了。在使用上session比cookie简单一些,但是也相应的增加了服务器的存储压力。
那么什么是session?session是另一种记录客户状态的机制,它跟cookie最大的不同就是cookie存储于客户端的浏览器中,而session存在服务器中,客户端访问服务器时,服务器把客户端的信息以某种形式记录在服务器中,这就是session,客户端浏览器再访问时就可以查找了自己的状态,当然,可能会有问题,既然用户数据存储于服务器,那么我又怎么知道服务中哪份数据是我的呢。服务器在生成一个客户的session域后,会产生这个session域的sessionid,又用户自己来保存这个sessionid,然后就可以用这个sessionid来换取自己的状态了。每一次访问服务器,服务器就会刷新session的有效时间,当session的有效时间过了,服务器就会删除这个session域。
6、header、token
http的组成中,请求头也是一个很重要的部分,当使用http请求一个网站的时候,浏览器会向对方的服务器发送一个http请求,这个请求包含有三个部分,分别是请求方法,URL请求路径和协议版本。在请求的过程中携带了包括带不限于请求方法,请求的目的地,语言以及浏览器的各种信息,有了这些信息后,对方的服务器才能够通过这些信息辨识你的浏览器。
而响应头包括两个部分,响应头和body,body就是返回给你的内容,比如说一个html页面,又或者是个字符串,又或者是个json。响应头信息里包括了服务器的响应信息,如http版本,压缩方式,相应文件类型,文件编码等等。
那么header能做什么呢?header里可以做的事情非常多,它和cookie一样,是用户无感的,每次交互时都会提交,但不同的是,cookie可以被禁止使用,而header则必须携带。header可以声明文件类型,使用的文件编码;可以提交post数据,可以设置post通过header提交,识别用户使用的语言,根据不同的语言国际化页面,也可以通过header来判断用户用什么浏览器打开的网站,通过这个数据来进行判定网站在维护更新时,用哪种浏览器做为理想浏览器更好。
随着业务的发展,一个网站可能就需要区分用户和角色,不同的用户的展示页面不一样,那么这个时候就需要我们使用用户名和密码来进行用户的区分。但是不能每次用户发起一次请求就去验证一次用户的用户名和密码。那么token就产生了,以一个例子来说明什么是token,简单的说就是你去一家健身房,办理了健身房的会员后,健身房就给你发了一张会员卡,以后你再来这家健身房就不用再出示一堆证明来证明你是你,只用将会员卡给健身房看看就知道你是你了。
那么token的目的就是为了减轻服务器的压力和避免不必要的数据库查询,使服务器的性能更多的花在更需要的地方。
常见的token有几种方式呢?首先就是mac地址,因为mac地址是全球唯一的,绝对不会冲突,这个可以作为token传输到服务器,问题是你用自己的用户名和密码却只能在这一台电脑上登录使用,在其他电脑上就无法使用;也可以使用sessionid作为token,这样的好处是方便,不用存储数据,缺点是session过期后,客户端必须重新登录才能访问数据,还有很多方法我们不一一介绍了,最流行的方式是jwt(json web token),它是一种基于json的紧凑,安全的方式,特别适合分布式环境。
不管用哪种方式的token,最终还是需要在客户端浏览器中存储该值,而我们一般都会放在header中,理由也在header最开始的时候说了,因为cookie可以被禁止,而header是必须携带的。
7、跨域、jsonp、代理
最后,在前后端交互的时候我们最常见的问题,就是跨域问题了。跨域一句话说清,就是浏览器不能执行其他网站的脚本。相对来说,这是一个好事情,因为浏览器对JavaScript进行了限制,这样也避免了很多安全问题。但是有时候我们又需要跨域,去运行其他网站的脚本,那么就需要去了解到底浏览器限制我们哪些方式不能访问。
URL | 说明 | 是否允许跨域 |
---|---|---|
http://www.whcode.cc/a.js http://www.whcode.cc/b.js |
同一域名下 | 允许 |
http://www.whcode.cc/lab/a.js http://www.whcode.cc/script/b.js |
同一域名不同文件夹下 | 允许 |
http://www.whcode.cc/a.js http://www.whcode.cc:8000/b.js |
同一域名,不同端口 | 不允许 |
http://www.whcode.cc/a.js https://www.whcode.cc/b.js |
同一域名,不同协议 | 不允许 |
http://www.whcode.cc/a.js http://150.158.195.238/b.js |
域名和域名对应ip | 不允许 |
http://www.whcode.cc/a.js http://script.whcode.cc/b.js |
域名相同,主机不同 | 不允许 |
http://www.whcode.cc/a.js http://whcode.cc/b.js |
同一域名,主机不同 | 不允许(cookie这种情况下也不允许访问) |
http://www.whcode.cc/a.js http://www.juejin.cn/b.js |
不同域名 | 不允许 |
这些是浏览器不允许跨域的几个规则,在这里先说明一下,不允许跨域是浏览器禁止的,不用浏览器访问是不受跨域的限制的。一般情况下我们最好听从浏览器给我们的建议避免跨域访问,但是现实场景往往不如我们的意,特别在现在大量应用分布式的环境下,使用的服务端口越来越多,很难避免跨域问题。常用的跨域方案是利用代理和jsonp来解决。
首先说jsonp,jsonp不是一门语言,也不是一种开发技术,甚至都不算是一种解决方案,更像是一个bug,这个bug是开发者找出浏览器的跨域传输数据的漏洞,虽然名字中有json,但是严格来说,传输的是JavaScript代码,只是js是天生支持json的。jsonp的原理其实很简单,在html中,所有src属性的标签都是可以跨域请求的,那么我们的js脚本同样可以使用script标签来执行跨域的脚本,也可以通过这些代码,实现跨域请求数据。这里有个小细节,既然是属性src,那么jsonp必然只能是get请求,注意这一点。
而我们真实业务场景中很少会用到jsonp来跨域请求数据,更多的还是使用代理来请求数据。跨域的禁止更多是禁止不同域名,不同端口号,那么我让这些处于同一域名统一端口号不就好了吗。这就是理论的最初的猜想,实现起来也很简单,使用Nginx反向代理。后端则将分布式服务通过网关来集中到一个域名一个端口号,这样就可以与前端进行同域同源的数据交互了。
8、总结与畅想
前端和后端的发展越来越快,其耦合程度也会越来越低,可能未来会演变出更多更实用的解决方案和技术栈。在我看来,前端包括但不限于移动端开发,web端开发,而后端则包括数据业务开发,前端则是更聚焦于用户体验的开发。在这个基础上,可能会演变出第三种开发端,我称为数据中心的开发。
前端开发的开发过程分为页面构建,逻辑开发,数据联调几个部分,而这个里面有着大量的重复工。就数据联调而言,通常会由后端来指定数据结构、字段信息,再交予前端,前端也只有根据这些信息才方便做逻辑部分,不然的话,等后端定义好后又需要更改。如果在前端和后端之间再加一层数据中心端则可以解决这个问题。
数据中心端替代了前端开发中的数据联调,不管是移动端产品还是web端产品都需要进行数据联调,而数据联调往往是最耗时间而且效率最低的一个步骤,并且移动端做的联调与web端做的联调除了开发语言外没有区别。那么增加一个数据中心,用于和后端联调,而在数据中心的图形化界面自动生成目标端所需要的ts数据类型,json schema完成前端的联调部分,那么前端的效率会更高且节约大量的时间专注于用户体验部分。
对此我也只是有一个初步的设想,细节部分并没有想清楚,也希望有大佬能够与我交流并让这个设想落地成为现实。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?