1、 列举几种后端通讯的方法,分别使用的场景。
跨域请求存在的原因:由于浏览器的同源策略(same-origin policy),即属于不同域的页面之间不能相互访问各自的页面内容。
所谓同源指三个相同:协议相同,域名相同,端口相同。
如果非同源有三种行为将会受到限制:(1)Cookie、LocalStorage 和 IndexDB 无法读取。(2)DOM无法获得。(3)ajax请求不能发送。
跨域的场景:
1.域名不同 www.yangwei.com 和www.boqi.com 即为不同的域名)
2.二级域名相同,子域名不同(www.wuhan.yangwei.com www.shenzheng.yangwei.com 为子域不同)
3.端口不同,协议不同 ( http://www.yangwei.com 和https://www.yangwei.com属于跨域www.yangwei.con:8888和www.yangwei.con:8080)
跨域的方式:(内容较多,需掌握CORS和jsonp,其他内容也要了解)
1.前端的方式: possMessage、window.name、document.domain、image.src(得不到数据返回),jsonP(script.src后台不配合得不到数据返回),style.href(得不到数据返回)
一.imge.src,script.src,style.href 不受同源策略的影响可以加载其他域的资源,可以用这个特性,向服务器发送数据。最常用的就是使用image.src 向服务器发送前端的错误信息。image.src 和style.href 是无法获取服务器的数据返回的,script.src 服务器端配合可以得到数据返回。
例子:
后台java文件:
public void getName(HttpServletRequest req,HttpServletResponse res){
String str="var json={'name':'tom','age':11};";
CtrlUtils.write2res(res, str);
}
前台页面:
<head>
<meta http-equiv="Content-Type" content="text/html;charset=gb2312">
<title>跨域</title>
<script>
var ele = document.createElement('script');
ele.src = 'http://localhost:8080/crmvipcust/web/getName.topic';
ele.type = 'text/javascript';
ele.language = 'javascript';
document.getElementsByTagName("head")[0].appendChild(ele);
ele.onload = function(){
alert(json.name);
};
</script>
</head>
<body>
</body>
这样就可以跨域调用获得JSON数据。
二.possMessage,window.name,document.domain 是两个窗口直接相互传递数据。
(1)possMessage 是HTML5中新增的,使用限制是 必须获得窗口的window 引用。IE8+支持,firefox,chrome,safair,opera支持
html5引入了全新的API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。
1发送消息
父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
postMessage(data,origin)方法接受两个参数
1.data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
2、、接收消息子窗口向父窗口发送消息写法类似//opener 属性是一个可读可写的属性,可返回对创建该窗口的 Window 对象的引用。(父)
window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口都可以通过message事件,监听对方消息。
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;
var color=container.style.backgroundColor;
window.parent.postMessage(color,'*');
},false);
message事件的时间对象event,提供一下三个属性。event.source发送消息的窗口、event.origin:消息发向的网址、event.data:消息内容。
例子:子窗口通过event.source属性引用父窗口。然后接收消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*');
}
event.origin属性可以过滤不是发给本窗口的消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== 'http://aaa.com') return;
if (event.data === 'Hello World') {
event.source.postMessage('Hello', event.origin);
} else {
console.log(event.data);
}}
(2)window.name ,在一个页面中打开另一个页面时,window.name 是共享的,所以可以通过window.name 来传递数据,window.name的限制大小是2M,这个所有浏览器都支持,且没有什么限制。
浏览器窗口有Window.name属性。这个属性最大的特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取他。父窗口打开一个子窗口,载入一个不同源的网页,该网页信息写入Window.name属性。
window.name = data;
接着,子窗口回调一个与主窗口同域的网址
location = 'http://parent.url.com/xxx.html';
然后主窗口就可以读取自创建的Window.name了。
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置放长的字符串;
缺点是必须监听子窗口Window.name属性的变化,影响网页的性能。
(3) document.domain 将两个页面的document.domain 设置成相同,document.domain 只能设置成父级域名,既可以访问,使用限制:这顶级域名必须相同。
顶级域名又称为一级域名、父级域名。一级域名是由一个合法字符串+域名后缀组成。所以,lisp.com这种形式的域名才是一级域名。lisp是域名主题,com是域名后缀。例子:
document.domain = 'example.com';
现在,A网页通过脚本设置一个cookie
document.cookie = "test1=hello";
B网页就可以读到这个cookie
var allCookie = document.cookie;
2.纯后端方式: CORS,服务器代理
CORS 是w3c标准的方式,通过在web服务器端设置:响应头Access-Cntrol-Alow-Origin 来指定哪些域可以访问本域的数据,ie8&9(XDomainRequest),10+,chrom4 ,firefox3.5,safair4,opera12支持这种方式。
服务器代理,同源策略只存在浏览器端,通过服务器转发请求可以达到跨域请求的目的,劣势:增加服务器的负担,且访问速度慢。
3.前后端结合:JsonP
script.src 不受同源策略的限制,所以可以动态的创建script标签,将要请求数据的域写在src 中参数中附带回调的方法,服务器端返回回调函数的字符串,并带参数。
如 script.src="http://www.yangwei.com/?id=001&callback=getInfoCallback,服务器端返回 getInfoCallBack("name:yangwei;age:18") 这段代码会直接执行,在前面定义好getInfoCallBack函数,既可以获得数据并解析。 这种是最长见的方式。
4.webSocke(了解性拓展)
应用场景
都可以进行服务端推送,并且都是使用长连接来进行.但两者的实现又有一点不同,sse仍使用http协议,并且使用相同的链接发送正常的http协议报文.而websocket是使用http协议进行握手,然后再使用同一个链接进行websocket协议的通信.
websocket可以进行双向的通信,即服务端可以往客户端发信息,客户端也可以向服务端发信息.而sse是单向的,只能由服务端往客户端发.
websocket自带连接的保持,即通过ping/pong协议保证连接可以始终维持,sse没有这个保证,不过可以参考ping/pong协议,自己周期性地发送信息来同样地进行处理.比如,5秒往客户端发一个特别的信息(通过type/name进行区分).其次,因为是基于浏览器的使用,sse有一个特性,就是浏览器发现一个连接断掉了,就会自动地进行重联,即重新发送请求.这样,服务端也不用担心连接被断开,不过需要处理新的请求必须和上一次请求的内容相连续,以及新的推送注册.
因为都是使用http协议进行起始处理,因此在签权上都可以使用到http协议本身的一些东西,比如header/cookie签权.在相应的握手阶段,通过读取cookie(session)来保证相应的请求必须是经过授权的,也可以用于定位使用人.甚至可以通过这些信息保证单个用户只能有一个请求,避免重复请求
由于都是基于浏览器使用,因此建议的数据传输都是文本型.虽然websocket支持二进制frame传输,不过一些都不建议使用.sse只能传输文本
不管是websocket还是sse,在用于通信时,都建议只用于进行数据的推送,而不是进行完整的应用处理.这里可以理解为,常规的业务处理仍然交给后端的服务来处理.这样,即可以使用之前的业务开发的优势,又可以使用推送的优势.而不是走向另一个级端,即所有的信息都想通过推送来传递.
开发方式
websocket开发首选netty,因为netty对协议的封装已经做到了完全的支持.通过 HttpServerCodec作为握手协议,WebSocketServerProtocolHandler作为协议处理,然后再加一个自己的handler,就完成了相应的业务处理.同时在性能上,netty在一个ws的请求建立起来之后,会自动地去除httpServerCodec相关的handler,这样保证后续的处理都是按照ws的协议来进行.
sse开发首选jersey,jersey-media-sse提供了相应的sse支持,并且通过与rest相集成,开发一个sse就跟普通的业务开发相同.
ws和sse在文本支持上都只支持utf-8编码,因此在处理上需要注册编码方式.同时在使用sse时,如果后端第一次进行响应时,相应的编码不对.chrome会直接报错,包括utf8都会报错(这是之前后端开发的一个问题),可以修正或者增加相应的拦截器,保证后端content-type响应中的charset=UTF-8.
ws和sse都可以通过nginx进行代理转发.ws的处理只需要设置http版本,以及重新转发前端的Upgrade和Connection头即可.而sse,也可以通过禁用buffer来处理.参考 http://stackoverflow.com/questions/27898622/server-sent-events-stopped-work-after-enabling-ssl-on-proxy
特定实现
为保证在开发时推送类的和业务类的系统不会耦合在一起,或者同一个应用内有两种处理模式的功能存在.建议直接在系统层就开发2个不同的系统,一个专门用于推送,另一个用于相应的业务处理.然后业务处理后的数据,需要再交由推送处理,则可以在后端进行通过消息系统进行中转,如kafka(持久保证)或redis(内存订阅)等
因为二者在ie上的支持都很有限,因此不建议在ie上进行尝试
使用sse还是websocket,取决于是否需要前台交互,还取决于对后端的支持技术的了解程序.比如,了解jersey多一点,还是netty多一点.由于最近netty进行微服务化底层通信支持越来越流行,个人更倾向于使用websocket.但如果仅仅是一个简单的推送功能,又不希望修改代码,那也可以使用jersey(毕竟之前的系统就是在上面进行开发的)
需要后端有的时候需要进行定向发送或者是群发,这种需求ws和sse的实现中都有相应的处理.如ChannelGroup和SseBroadcaster,这样在后端获取到一个消息,需要进行路由时就可以从这里面拿相应的channel信息.不过,前提是对各个channel上进行了特定的消息绑定,这样就好区分具体的路由信息.具体路由策略可以在建立时绑定session,后续通过session来路由.
2.纯后端方式: CORS,服务器代理
CORS 是w3c标准的方式,跨源资源分享(Cross-Origin Resource Sharing)的缩写,它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了ajax只能使用同源的限制。整个CORS通信过程都是浏览器自动完成,不需要用户参与。
浏览器一旦发现ajax请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。因此实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信。
浏览器将CORS请求分成两类:简单请求simple request和非简单请求not-so-simple request。
满足一下两种请求的就属于简单请求,不能同时满足这个两个条件的就属于非简单请求。
通过在web服务器端设置:响应头Access-Cntrol-Alow-Origin 来指定哪些域可以访问本域的数据,ie8&9(XDomainRequest),10+,chrom4 ,firefox3.5,safair4,opera12支持这种方式。
服务器代理,同源策略只存在浏览器端,通过服务器转发请求可以达到跨域请求的目的,劣势:增加服务器的负担,且访问速度慢。
跨源资源分享(Cross-Origin Resource Sharing)的缩写
3.前后端结合:JsonP
jsonp是服务器端与客户端跨源通信的常用方法。最大的特点是简答适用老式浏览器全部都支持,服务器改造非常小。他的基本思想是网页通过添加一个<script>元素,向服务器请求json数据,script.src 不受同源策略的限制,所以可以动态的创建script标签,将要请求数据的域写在src 中参数中附带回调的方法,服务器端返回回调函数的字符串,并带参数。这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
首先网页动态插入<script>元素,由他向跨源网址发出请求。
function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); };上面代码通过添加<script>元素,向服务器发出请求,注意:该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这个jsonp是必须的。服务器收到这个请求后,会将数据放在回调函数的参数位置返回。由于<script>元素请求的脚本,直接作为代码运行,这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的json数据被视为JavaScript对象,而不是字符串,因此避免了json.prase的步骤。foo({ "ip": "8.8.8.8" });
4.webSocke(了解性拓展)
webscoket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持就可以通过它进行跨源通信。下面是浏览器发出的webscokets请求的头信息。
origin表示该请求的请球源origin,即发自哪个域名。正是因为有了origin这个字段,所以webscokets才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域在白名单内,服务器就会做出如下回应。GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
应用场景
都可以进行服务端推送,并且都是使用长连接来进行.但两者的实现又有一点不同,sse仍使用http协议,并且使用相同的链接发送正常的http协议报文.而websocket是使用http协议进行握手,然后再使用同一个链接进行websocket协议的通信.
websocket可以进行双向的通信,即服务端可以往客户端发信息,客户端也可以向服务端发信息.而sse是单向的,只能由服务端往客户端发.
websocket自带连接的保持,即通过ping/pong协议保证连接可以始终维持,sse没有这个保证,不过可以参考ping/pong协议,自己周期性地发送信息来同样地进行处理.比如,5秒往客户端发一个特别的信息(通过type/name进行区分).其次,因为是基于浏览器的使用,sse有一个特性,就是浏览器发现一个连接断掉了,就会自动地进行重联,即重新发送请求.这样,服务端也不用担心连接被断开,不过需要处理新的请求必须和上一次请求的内容相连续,以及新的推送注册.
因为都是使用http协议进行起始处理,因此在签权上都可以使用到http协议本身的一些东西,比如header/cookie签权.在相应的握手阶段,通过读取cookie(session)来保证相应的请求必须是经过授权的,也可以用于定位使用人.甚至可以通过这些信息保证单个用户只能有一个请求,避免重复请求
由于都是基于浏览器使用,因此建议的数据传输都是文本型.虽然websocket支持二进制frame传输,不过一些都不建议使用.sse只能传输文本
不管是websocket还是sse,在用于通信时,都建议只用于进行数据的推送,而不是进行完整的应用处理.这里可以理解为,常规的业务处理仍然交给后端的服务来处理.这样,即可以使用之前的业务开发的优势,又可以使用推送的优势.而不是走向另一个级端,即所有的信息都想通过推送来传递.
开发方式
websocket开发首选netty,因为netty对协议的封装已经做到了完全的支持.通过 HttpServerCodec作为握手协议,WebSocketServerProtocolHandler作为协议处理,然后再加一个自己的handler,就完成了相应的业务处理.同时在性能上,netty在一个ws的请求建立起来之后,会自动地去除httpServerCodec相关的handler,这样保证后续的处理都是按照ws的协议来进行.
sse开发首选jersey,jersey-media-sse提供了相应的sse支持,并且通过与rest相集成,开发一个sse就跟普通的业务开发相同.
ws和sse在文本支持上都只支持utf-8编码,因此在处理上需要注册编码方式.同时在使用sse时,如果后端第一次进行响应时,相应的编码不对.chrome会直接报错,包括utf8都会报错(这是之前后端开发的一个问题),可以修正或者增加相应的拦截器,保证后端content-type响应中的charset=UTF-8.
ws和sse都可以通过nginx进行代理转发.ws的处理只需要设置http版本,以及重新转发前端的Upgrade和Connection头即可.而sse,也可以通过禁用buffer来处理.参考 http://stackoverflow.com/questions/27898622/server-sent-events-stopped-work-after-enabling-ssl-on-proxy
特定实现
为保证在开发时推送类的和业务类的系统不会耦合在一起,或者同一个应用内有两种处理模式的功能存在.建议直接在系统层就开发2个不同的系统,一个专门用于推送,另一个用于相应的业务处理.然后业务处理后的数据,需要再交由推送处理,则可以在后端进行通过消息系统进行中转,如kafka(持久保证)或redis(内存订阅)等
因为二者在ie上的支持都很有限,因此不建议在ie上进行尝试
使用sse还是websocket,取决于是否需要前台交互,还取决于对后端的支持技术的了解程序.比如,了解jersey多一点,还是netty多一点.由于最近netty进行微服务化底层通信支持越来越流行,个人更倾向于使用websocket.但如果仅仅是一个简单的推送功能,又不希望修改代码,那也可以使用jersey(毕竟之前的系统就是在上面进行开发的)
需要后端有的时候需要进行定向发送或者是群发,这种需求ws和sse的实现中都有相应的处理.如ChannelGroup和SseBroadcaster,这样在后端获取到一个消息,需要进行路由时就可以从这里面拿相应的channel信息.不过,前提是对各个channel上进行了特定的消息绑定,这样就好区分具体的路由信息.具体路由策略可以在建立时绑定session,后续通过session来路由.