1. ajax如何处理跨域?
ajax使用jsonp请求实现跨域,关于这个网上已经很多资料了,基本用法这里就不介绍了。其原理可以参考我的另一篇文章《18~19年大厂高级前端面招汇总之基础篇(一)》中的【3. jsonp实现的原理是什么?】,但如果继续深究,很少资料能介绍清楚其参数jsonp和jsonpCallback的区别,以及jsonpCallback的回调函数与success的回调函数的区别,这里用实例再补充分析一下,请看例子:
$(document).ready(function() {
var data = {};
var jsonData = JSON.stringify(beanObject);
$.ajax({
type: 'GET',
url: 'http://localhost:8080/ninVoiceService_V2/NewTTSJP',
dataType: 'jsonp',
data: {...data},
jsonp: 'jsonpcallback',
jsonpCallback: 'todo',
error: function(XmlHttpRequest, textStatus, errorThrown) {
console.log("error");
},
success: function(resp) {
console.log('success');
console.log(resp);
}
});
});
function todo(resp){
console.log('todo');
console.log(resp);
}
// 执行结果
// todo
// {...resp}
// success
// {...resp}
关于参数 jsonp: 'jsonpcallback',默认值为'callback',这个参数主要是服务端用到的,前端只负责定义名称。主要是为了告诉服务器这个是我要的回调函数名称,服务端响应代码如下:
String callback = request.getParameter("jsonpcallback");
// 处理过程...
PrintWriter out = response.getWriter();
out.print(callback + "(" + respObj.toString() + ");");
out.flush();
out.close();
如果前端不设置jsonp值,即使用默认值,则服务端修改如下即可:
String callback = request.getParameter("callback");
关于参数 jsonpCallback: 'todo', 了解jsonp原理就很清楚这个参数的用意,这个参数只跟前端有关,跟服务端边都沾不上,主要用于接收服务端返回出数据的回调函数,它跟success的用途是一样的。这里就有人疑问了,刚告诉服务器我要的回调函数名称不是在参数jsonp: xxx 中设置了吗,这个又是怎么回事?原因很简单,ajax实现了允许前端和后端回调函数名称不同的功能,步骤大概如下:
1)前端告诉服务端,我要发送jsonp请求啦,我的回调函数是 jsonp: 'jsonpcallback'。这里注意,实际上传递给服务端的是url?jsonpcallback= ???? ,即回调函数的实际名称是一串随机字符串而不是jsonpcallback。
2)服务端说,我知道了,我的响应结果是 jsonpcallback({...data}),实际上是 ????({...data})
3)ajax说,我收到jsonpcallback({...data})响应了,你想要执行的实际回调函数名称??? 我这里没有,我给你转成其它函数吧
4)ajax检查自己发现有一个 jsonpCallback: 'todo',那我就转成todo({...data})
5)ajax把回调函数转成todo({...data})执行后,按照自身设定,请求成功还要将回调函数再转换为success函数执行。
(小贴士)正常情况下,这个参数jsonpCallback是无需设置的,使用success就好。
2. 表单可以跨域吗?
表单提交是可以跨域的。在前后端还没分离的时代,就经常使用表单提交验证用户登录,验证通过后由服务端重定向实现页面跳转。为什么可以跨域提交?因为原页面用 form 提交到另一个域名之后,原页面的脚本是无法获取新页面中的任何内容和数据的,所以浏览器认为这是安全的。这是个历史问题,现在基本不会使用<form>标签来请求数据。
3. CORS是什么?CORS如何设置?
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求,比如,站点 http://domain-a.com 的某 HTML 页面通过 <img> 的 src 请求 http://domain-b.com/image.jpg,像这类通过src的请求跨域是允许的。但出于安全原因,浏览器限制了从脚本内发起的跨源HTTP请求, 例如,XMLHttpRequest和Fetch API,它们必须遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
这里推荐一篇文章《跨域资源共享 CORS 详解》
要通过CORS机制实现跨域,就需要在服务端header中配置跨域,主要配置项包括:
- Access-Control-Allow-Origin 必须字段,表示访问来源的域名或ip地址+端口号,也可以设置为*表示所有域都可以通过。
- Access-Control-Allow-Methods 必须字段,表示允许跨域的请求方法。
- Access-Control-Allow-Headers 可选字段,表示允许跨域请求中包含在请求头的字段。
- Access-Control-Allow-Credentials 可选字段,当客户端也把Credentials设为true时,表示请求头中会带上cookie。
服务端使用的语言不同,则配置的方法不同,在node服务端express框架中,可使用:
var express=require('express');
var app=express();
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Token');
res.header('Access-Control-Allow-Credentials','true');
res.header("Content-Type", "application/json;charset=utf-8"); // 如果返回json数据格式,则加上这句
next();
};
app.use(allowCrossDomain);
Java服务端的配置,可使用:
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,PATCH,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept, Token");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setContentType("application/json;charset=utf-8"); // 如果返回json数据格式,则加上这句
// response.setHeader("Access-Control-Max-Age", "3628800"); // 表示特定时间内不需要再发送预检验请求,可以缓存该结果,一般默认,不做设置。
客户端ajax请求:
var data = {}; // 参数
$.ajax({
url: url,
type: 'POST', // 请求方法:GET,PUT,POST,DELETE,PATCH,OPTIONS
data: { ...data},
dataType: 'json',
crossDomain: true,
xhrFields: {
withCredentials: true // 请求头自动带上cookie
},
headers: {
'Content-Type': 'application/json', // 发送给服务端的数据格式
'Accept': 'application/json' // 接收服务端返回的数据格式
'Token': 'TokenInfo' // Token验证
},
success: function(response) {},
error: function(XmlHttpRequest, textStatus, errorThrown) {}
});
客户端axios请求:
import axios from 'axios'
axios.defaults.crossDomain = true;
axios.defaults.withCredentials = true;
axios.interceptors.request.use(
function(config) {
let headertoken = 'TokenInfo';
config.headers['Content-Type'] = 'application/json';
config.headers['Accept'] = 'application/json';
config.headers['TOKEN'] = `${headertoken}`;
return config;
},
function(error) {
return Promise.reject(error);
}
);
var body = {};
var url = '';
axios({
method: 'POST', // 请求方法:GET,PUT,POST,DELETE,PATCH,OPTIONS
url,
data: { ...body} // 请求方法为GET时,属性改为 params: { ...query}
}).then((response) => {
console.log(response);
}, (err) => {
console.log(err);
});
客户端Fetch请求,针对CORS跨域机制,fetch有一个专门属性配置:mode,它有三个枚举的配置项:
- same-origin 该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个error告知不能跨域。
- cors 当服务端有配置了CORS,则使用该配置项。另外,如果服务端恰巧同域而且没有设置CORE,依然允许通信。
- no-cors 服务端没有配置CORS,但使用该配置项依然可以向服务端发送请求,只不过获取不到任何响应数据。
这里还有一个值得注意的点,使用mode: 'cors'配置时,支持三种content-type,但恰恰不支持 application/json:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
另外,针对服务端的"Access-Control-Allow-Credentials" 是否支持cookie传输,在Fetch中也有专门的配置项credentials,它有三个值:
- include 表示请求时带上cookie
- same-origin 表示只有在同源请求时带上cookie
- omit 不带cookie
较常用的是multipart/form-data格式,请求代码如下:
let url = '';
let data = JSON.stringify({
name: 'admin'
});
const body = new FormData();
body.append('data', data);
fetch(url, {
method: 'POST',
body,
headers: {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json',
'Token': 'TokenInfo'
},
mode: 'cors',
credentials: 'include',
})
.then((resp) => resp.json())
.then((json) => {
console.log('请求成功:', JSON.stringify(json));
})
.catch((error) => {
console.log(error);
});
如果使用application/x-www-form-urlencoded格式,请求代码如下:
let url = '';
fetch(url, {
method: 'POST',
body: JSON.stringify({name: 'admin'});
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Token': 'TokenInfo'
},
mode: 'cors',
credentials: 'include',
})
.then((resp) => resp.json())
.then((json) => {
console.log('请求成功:', JSON.stringify(json));
})
.catch((error) => {
console.log(error);
});
有关Fetch的使用手册,请参考 Fetch API 。
4. 介绍下浏览器跨域?怎么去解决跨域问题?
跨域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com 页面去请求 www.google.com 的资源。但是一般情况下不能这么做,它是由浏览器的同源策略造成的,跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域。
解决跨域的方法有多少种:
- CORS跨域资源共享。
- JSONP
- Iframe,通过postMessage和getMessage通信
- 代理服务