前后端数据交互
交互方式
利用Cookie
Cookie 是一些数据, 存储于电脑上的文本文件中,只要客户端cookie开放且有数据,每一次请求都会自动添加到http报文中,后台可以实时接收观察获取这些Cookie 。
Cookie 的作用就是用于解决 "如何记录客户端的用户信息":
- 当用户访问 web 页面时,他的名字可以记录在 cookie 中。
- 在用户下一次访问该页面时,可以在 cookie 中读取用户访问记录。
利用Session对象
session对象表示特定会话session的用户数据。
客户第一次访问支持session的JSP网页,服务器会创建一个session对象记录客户的信息。当客户访问同一网站的不同网页时,仍处于同一个session中。
request.getSession().setAttribute(); request.getSession().getAttribute();
只要浏览器不关闭,就能使用。所以用户访问网站整个生命都会用到的数据一般都用session来存储,比如用户名、登录状态之类的。
利用Request参数设置
request.setAttribute(); request.getRequestDispatcher("welcome.jsp").forward(request, response); request.getAttribute();
不能用sendRedirect(),因为已经切换到另一个请求了,request参数的有效期为本次请求。
Form表单
form表单的action设置好路径
<form id="loginform" name="loginform" action="<%=path %>/login" method="post"> </form>
Ajax
前端用ajax发起请求。
window.onload=function(){ var jsondata={ "name":"Sarrans", "password":"123456" } $.ajax({ type:"post", url:"login", data:jsondata, success:function(data){ alert(data.name+"请求成功"); } error:function(e){ alert("Error"); } }) }
后台servlet接收请求处理
@WebServlet("/login")//ajax的url public class login extends HttpServlet{ protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{ //从前端传递的request取值 String name=request.getParameter("name"); //构造一个新的json传回去 String s="{\"name\":\"Gerorge\",\"password\":\"1234567\"}"; response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(s);//写入返回结果 //如果前台结果为success,会输出 Gerorge请求成功 } }
jsonp
结合跨域方式,因为前端请求到数据需要在回调函数中使用,所以后端得将数据放回到回调函数中。
$.ajax({ url:"", dataType:"jsonp", jsonp:'callback', success(function(res){ console.log(res) }) })
性能改进
Comet
Comet的实现主要有两种方式,基于Ajax的长轮询方式和基于 Iframe 及 htmlfile 的流(http streaming)方式。而这些大部分功能在后台完成,前端要做的就是通过ajax请求成功后,在XMLHttpRequest的onreadystatechange函数中持续获取数据。
典型的Ajax通信方式也是http协议的经典使用方式,要想取得数据,必须首先发送请求。在低延迟要求比较高的web应用中,只能增加服务器请求的频率。Comet则不同,客户端与服务器端保持一个长连接,只有客户端需要的数据更新时,服务器才主动将数据推送给客户端。
var xhr=getXmlHttpRequest(); xhr.onreadystatechange=function(){ console.log(xhr.readyStae); if(xhr.readyState===3&&xhr.status===200){ //获取成功后执行操作 //数据在xhr.responseText console.log(xhr.responseText); } }
SSE
SSE是一种允许服务端向客户端推送新数据的HTML5技术。它是 WebSocket 的一种轻量代替方案,使用 HTTP 协议。
严格地说,HTTP 协议是没有办法做服务器推送的,但是当服务器向客户端声明接下来要发送流信息时,客户端就会保持连接打开,SSE 使用的就是这种原理。
与由客户端每隔几秒从服务端轮询拉取新数据相比,这是一种更优的解决方案。
应用场景:例如邮箱服务的新邮件提醒,微博的新消息推送、管理后台的一些操作实时同步等.
var source=new EventSource("myevent"); source.onmessage=function(event){ console.log(event.data); }; source.onerror=function(){ console.log("失败,连接状态"+source.readySate) };
EventSource对象参数为入口点,必须与创建对象的页面同源(url模式,域、端口)。连接断开会自动建立,或者使用source.close()强制断开。open事件在连接建立时触发,message事件在接收到新数据时触发,error事件在无法建立连接时触发。推送数据保存在event.data中。
WebSocket
Websocket是一个全新的、独立的协议,基于TCP协议,与http协议兼容、却不会融入http协议。他被设计出来的目的就是要取代轮询和 Comet 技术。
WebSocket通过单个TCP连接提供全双工(双向通信)通信信道的计算机通信协议。此WebSocket API可在用户的浏览器和服务器之间进行双向通信。用户可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器。 它可以让多个用户连接到同一个实时服务器,并通过API进行通信并立即获得响应。
它允许用户和服务器之间的流连接,并允许即时信息交换。在聊天应用程序的示例中,通过套接字汇集消息,可以实时与一个或多个用户交换,具体取决于谁在服务器上“监听”(连接)。
WebSockets适用于需要实时更新和即时信息交换的任何应用程序。一些示例包括但不限于:现场体育更新,股票行情,多人游戏,聊天应用,社交媒体等。
var socket=new WebSocket("url"); socket.send("hello world"); socket.onmessage=function(event){ console.log(event.data); console.log(event.readyState); }
请求方式变化
jQuery ajax
传统 Ajax 指的是 XMLHttpRequest(XHR), 最早出现的发送后端请求技术,隶属于原始js中,核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。
JQuery ajax 是对原生XHR的封装,除此以外还增添了对JSONP的支持。
$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} });
缺点:
- 本身是针对MVC的编程,不符合现在前端MVVM的浪潮
- 基于原生的XHR开发,XHR本身的架构不清晰。
- JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
- 不符合关注分离(Separation of Concerns)的原则
- 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
fetch
fetch
try { let response = await fetch(url); let data = response.json(); console.log(data); } catch(e) { console.log("Oops, error", e); }
fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多了,参数有点像jQuery ajax。但是,fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
优点:
- 符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 同构方便,使用 isomorphic-fetch
- 脱离了XHR,是ES规范里新的实现方式
- 更加底层,提供的API丰富(request, response)
缺点:
- fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
- fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
- fetch没有办法原生监测请求的进度,而XHR可以
axios
axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。
特点:
- 从浏览器中创建 XMLHttpRequest
- 支持 Promise API
- 客户端支持防止CSRF
- 提供了一些并发请求的接口(重要,方便了很多的操作)
- 从 node.js 创建 http 请求
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换JSON数据
跨域
跨域的原因是什么?
当一个资源从与该资源本身所在服务器中不同域、协议、端口请求一个资源时,出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求,XMLHttpRequest和Fetch API。
引入这个机制主要是用来防止CSRF攻击的(利用用户的登录态发起恶意请求)。
没有同源策略的情况下,A 网站可以被任意其他来源的 Ajax 访问到内容。如果你当前 A 网站还存在登录态,那么对方就可以通过 Ajax 获得你的任何信息。(当然跨域并不能完全阻止 CSRF)
解决跨域方式
JSONP
JSONP 的原理很简单,就是利用 <script>
标签没有跨域限制的漏洞。
通过 <script>
标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script> <script> function jsonp(data) { console.log(data) } </script>
JSONP 使用简单且兼容性不错,但是只限于 get
请求。
在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现:
function jsonp(url, jsonpCallback, success) { let script = document.createElement('script') script.src = url script.async = true script.type = 'text/javascript' window[jsonpCallback] = function(data) { success && success(data) } document.body.appendChild(script) } jsonp('http://xxx', 'callback', function(value) { console.log(value) })
CORS
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest
来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin
就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和预检请求。
另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务器是否允许该跨域请求。
服务器确认允许之后,才发起实际的HTTP请求。 在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 cookie 和 HTTP 认证相关数据)。
如果发起请求时设置WithCredentials标志设置为 true,从而向服务器发送cookie,但是如果服务器的响应中未携带Access-Control-Allow-Credentials: true,浏览器将不会把响应内容返回给请求的发送者。
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin的值为*
,必须是某个具体的域名。
注意,简单 GET 请求不会被预检;如果此类带有身份凭证请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。
document.domain
该方式只能用于二级域名相同的情况下,比如 a.test.com
和 b.test.com
适用于该方式。
只需要给页面添加 document.domain = 'test.com'
表示二级域名都相同就可以实现跨域了。
postMessage
通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息。
// 发送消息端 window.parent.postMessage('message', 'http://test.com') // 接收消息端 var mc = new MessageChannel() mc.addEventListener('message', event => { var origin = event.origin || event.originalEvent.origin if (origin === 'http://test.com') { console.log('验证通过') } })
iframe的跨域
用iframe的获取方式: 只能显示,不能控制
a.location.hash
b.window.name
iframe+postMessage
window.postMessage(data,origin)
子页面向父页面传递数据,则在子页面中调用父级window的postMessage
window.parent.postMessage=function(data,origin)