AJAX

  • Asynchronous Javascript And Xml通过JS异步的向服务器发送请求并接收响应数据

优点

  1. 异步访问
  2. 局部刷新

使用场合

  1. 搜索建议
  2. 表单验证
  3. 前后端分离

XMLHttpRequest

  • XMLHttpRequest称为异步对象,代替浏览器向服务器发送异步的请求并接收响应xhr是由JS来提供的

异步对象

  1. IE7+,Chrome,Firefox,Safari,Opera调用 XMLHttpRequest 生成 xhr对象 ​2. IE低版本浏览器中(IE6以及以下)调用 ActiveXObject() 生成xhr
<script>
    if(window.XMLHttpRequest){
        //支持 XMLHttpRequest
        var xhr = new XMLHttpRequest();
    }else{
        //不支持XMLHttpRequest,使用 ActiveXObject 创建异步对象
        var xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }
</script>

xhr的成员

  1. open()
  • 作用:创建请求
  • 语法:open(method,url,asyn)
// method:请求方式,取值'get' 或 'post'
// ​url:请求地址,字符串
// asyn:是否采用异步的方式  - true:异步 / false:同步

xhr.open('get','/server',true);
  1. send()
  • 作用:通知xhr向服务器端发送请求
  • 语法:send(body)
// get请求:body的值为null  ->  send(null)
// ​post请求:body的值为请求数据  ->  send("请求数据")

xhr.send()
  1. readyState
  • 作用:请求状态,通过不同的请求状态来表示xhr与服务器的交互情况
状态 说明
0 代理被创建,但尚未调用 open() 方法。
1 open() 方法已经被调用。
2 send() 方法已经被调用,响应头也已经被接收
3 下载中; responseText 属性已经包含部分数据。
4 下载操作已完成
  1. responseText
  • 作用:响应数据
  1. status
  • 作用:服务器端的响应状态码
状态码 说明
200 表示服务器正确处理所有的请求以及给出响应
404 请求资源不存在
500 服务器内部错误
  1. onreadystatechange
  • 作用:每当xhr的readyState发生改变的时候都要触发的操作 ​- 也称作回调函数;当readyState的值为4且status值为200的时候,才可以获取响应数据

AJAX的操作步骤

GET请求

//1.创建xhr请求
var xhr = createXhr();
//2.创建请求 - open()
xhr.open('get',url,asyn[true|false])
//3.设置回调函数 - onreadystatechange
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4 && xhr.status == 200){
        //接收响应
        var res = xhr.responseText;

    }
}
//4.发送请求
xhr.send(null);

//注意:若含有请求参数 - URL后拼接 查询字符串 QueryString
//ex: xhr.open('get','/url?key=value&key=value',asyn)

POST请求

//1.创建xhr请求
var xhr = createXhr();
//2.创建请求 - open()
xhr.open('post',url,asyn[true|false])
//3.设置回调函数 - onreadystatechange
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4 && xhr.status == 200){
        //接收响应
        xhr.responseText;
    }
}
//4设置Content-Type;
//默认ajax post的Content-Type为 "text/plain;charset=utf-8"
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
//5.发送请求
xhr.send('请求数据');
//请求数据同查询字符串 "uname=guoxiaonao&age=18"

jquery对ajax的支持

$.get() 和 $.post()

  • 作用:通过get方式异步的向远程地址发送请求
$.get(url,data,callback,type)
        url:请求地址
        data:传递到服务器端的参数
        可以是字符串:"name=sf.zh&age=18"
        也可以是js对象:
            {
                name:"sf.zh",
                age:18
            }
        callback:响应成功后的回调函数
        ex: function(data){
           console.log(data)
        }
        type:响应回来的数据的格式
            取值如下:
            1.html : 响应回来 的文本是html文本
            2.text : 响应回来的文本是text文本
            3.script : 响应回来的文本是js执行脚本
            4.json : 响应回来的文本是json格式的文本

$.post(url,data,callback,type)
    同上

$.ajax()

参数对象中的属性:
    1.url : 字符串,表示异步请求的地址
    2.type : 字符串,请求方式,get 或 post
    3.data : 传递到服务器端的参数
        可以是字符串 :"name=sf.zh&age=18"
        也可以是js对象:
            {
                name:"sf.zh",
                age:18
            }
    4.dataType : 字符串,响应回来的数据的格式
        1.'html'
        2.'xml'
        3.'text' 
        4.'script'
        5.'json'
        6.'jsonp' : 有关跨域的响应格式
    5.success:回调函数,请求和响应成功时回来执行的操作
    6.error : 回调函数,请求或响应失败时回来执行的操作
    7.beforeSend : 回调函数,发送ajax请求之前执行的操作,如果return false,则终止请求
        使用场景:
            1,发请求之前可将提交摁钮置成不可点击状态,防止用户重复提交
            2,摁钮点击后,loading画面
            3,所有数据相关的校验

    8.async  是否启用异步请求,默认为true【异步】

跨域

  • 跨域:非同源的网页,相互发送请求的过程,就是跨域
  • 基本上目前所有的浏览器都实现了CORS标准,其实目前几乎所有的浏览器ajax请求都是基于CORS机制的,只不过可能平时前端开发人员并不关心而已(所以说其实现在CORS解决方案主要是考虑后台该如何实现的问题)

f3a6bcbbac0ba069dbe604e324ae2b60.png

如何判断是否是简单请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求

  • 请求方法是以下三种方法之一:HEAD,GET,POST
  • HTTP的头信息不超出以下几种字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type(只限于三个值application/x-www-form-urlencoded、 multipart/form-data、text/plain)
  • 凡是不同时满足上面两个条件,就属于非简单请求。

ajax跨域的表现

第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404

56ef296b106a7a70a317c0309403d6d1.png

  • 出现这种情况的原因如下
  • 本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)
  • 服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址
  • 解决方案: 后端允许options请求

第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 405

8edf274915d2581fdb9c34154b035d59.png

  • 这种现象和第一种有区别,这种情况下,后台方法允许OPTIONS请求,但是一些配置文件中(如安全配置),阻止了OPTIONS请求,才会导致这个现象
  • 解决方案: 后端关闭对应的安全配置

第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且status 200

3588553b8d7734afca6b72723cf11456.png

  • 这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许OPTIONS请求,并且接口也允许OPTIONS请求,但是头部匹配时出现不匹配现象
  • 比如origin头部检查不匹配,比如少了一些头部的支持(如常见的X-Requested-With头部),然后服务端就会将response返回给前端,前端检测到这个后就触发XHR.onerror,导致前端控制台报错
  • 解决方案: 后端增加对应的头部支持

第四种现象:heade contains multiple values '*,*'

  • 表现现象是,后台响应的http头部信息有两个Access-Control-Allow-Origin:*
  • 说实话,这种问题出现的主要原因就是进行跨域配置的人不了解原理,导致了重复配置
  • 常见于.net后台(一般在web.config中配置了一次origin,然后代码中又手动添加了一次origin(比如代码手动设置了返回*))
  • 常见于.net后台(在IIS和项目的webconfig中同时设置Origin:*)
  • 建议删除代码中手动添加的*,只用项目配置中的即可
  • 建议删除IIS下的配置*,只用项目配置中的即可

JSONP方式解决跨域问题

jsonp解决跨域问题是一个比较古老的方案(实际中不推荐使用),这里做简单介绍(实际项目中如果要使用JSONP,一般会使用JQ等对JSONP进行了封装的类库来进行ajax请求)

  • JSONP之所以能够用来解决跨域方案,主要是因为<script>脚本拥有跨域能力,而JSONP正是利用这一点来实现

62067c9e687a12ab86a11062fae16071.png

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('response data: ' + JSON.stringify(data));
};
  • 客户端网页网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制
  • 请求时,接口地址是作为构建出的脚本标签的src的,这样,当脚本标签构建出来时,最终的src是接口返回的内容
  • 服务端对应的接口在返回参数外面添加函数包裹层
foo({"test" :"testData"});
  • 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤
  • 注意,一般的JSONP接口和普通接口返回数据是有区别的,所以接口如果要做JSONO兼容,需要进行判断是否有对应callback关键字参数,如果有则是JSONP请求,返回JSONP数据,否则返回普通数据

CORS解决跨域问题

PHP后台配置

<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
//主要为跨域CORS配置的两大基本信息,Origin和headers

配置Apache web服务器跨域(httpd.conf中)

<Directory />
    AllowOverride none
    Require all denied
</Directory>
  • 改为以下代码
<Directory />
    Options FollowSymLinks
    AllowOverride none
    Order deny,allow
    Allow from all
</Directory>

Node.js后台配置(express框架)

  • Node.js的后台也相对来说比较简单就可以进行配置。只需用express如下配置
app.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.header("X-Powered-By", ' 3.2.1')
        //这段仅仅为了方便返回json而已
    res.header("Content-Type", "application/json;charset=utf-8");
    if(req.method == 'OPTIONS') {
        //让options请求快速返回
        res.sendStatus(200); 
    } else { 
        next(); 
    }
});

JAVA后台配置

  1. 获取依赖jar包 下载 cors-filter-1.7.jar, java-property-utils-1.9.jar 这两个库文件放到lib目录下。(放到对应项目的webcontent/WEB-INF/lib/下)
  2. 如果项目用了Maven构建的,请添加如下依赖到pom.xml中:(非maven请忽视)
<dependency>
    <groupId>com.thetransactioncompany</groupId>
    <artifactId>cors-filter</artifactId>
    <version>[ version ]</version>
</dependency>
  1. 添加CORS配置到项目的Web.xml中( App/WEB-INF/web.xml)
<!-- 跨域配置-->    
<filter>
        <!-- The CORS filter with parameters -->
        <filter-name>CORS</filter-name>
        <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>

        <!-- Note: All parameters are options, if omitted the CORS 
             Filter will fall back to the respective default values.
          -->
        <init-param>
            <param-name>cors.allowGenericHttpRequests</param-name>
            <param-value>true</param-value>
        </init-param>

        <init-param>
            <param-name>cors.allowOrigin</param-name>
            <param-value>*</param-value>
        </init-param>

        <init-param>
            <param-name>cors.allowSubdomains</param-name>
            <param-value>false</param-value>
        </init-param>

        <init-param>
            <param-name>cors.supportedMethods</param-name>
            <param-value>GET, HEAD, POST, OPTIONS</param-value>
        </init-param>

        <init-param>
            <param-name>cors.supportedHeaders</param-name>
            <param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
        </init-param>

        <init-param>
            <param-name>cors.exposedHeaders</param-name>
            <!--这里可以添加一些自己的暴露Headers   -->
            <param-value>X-Test-1, X-Test-2</param-value>
        </init-param>

        <init-param>
            <param-name>cors.supportsCredentials</param-name>
            <param-value>true</param-value>
        </init-param>

        <init-param>
            <param-name>cors.maxAge</param-name>
            <param-value>3600</param-value>
        </init-param>

    </filter>

    <filter-mapping>
        <!-- CORS Filter mapping -->
        <filter-name>CORS</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

请注意,以上配置文件请放到web.xml的前面,作为第一个filter存在(可以有多个filter的)

  1. 可能的安全模块配置错误(注意,某些框架中-譬如公司私人框架,有安全模块的,有时候这些安全模块配置会影响跨域配置,这时候可以先尝试关闭它们)
  • 仅列举简单的全局配置,新建配置,然后添加Configuration注解即可配置成功
@Configuration
public class CorsConfig {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        // 可以自行筛选
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");

        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", buildConfig());

        return new CorsFilter(source);  
    }
}

NET后台配置

  • 打开控制面板,选择管理工具,选择iis;右键单击自己的网站,选择浏览;打开网站所在目录,用记事本打开web.config文件添加下述配置信息,重启网站

47eb3cf94365ef702afc0395b68beb7e.png

  • 请注意,以上截图较老,如果配置仍然出问题,可以考虑增加更多的headers允许
"Access-Control-Allow-Headers":"X-Requested-With,Content-Type,Accept,Origin"

其它更多配置,如果第一步进行了后,仍然有跨域问题,可能是:

  • 接口中有限制死一些请求类型(比如写死了POST等),这时候请去除限 制
  • 接口中,重复配置了Origin:*,请去除即可
  • IIS服务器中,重复配置了Origin:*,请去除即可

OPTIONS预检的优化

Access-Control-Max-Age:
  • 这个头部加上后,可以缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据。非常有用,可以大幅优化请求次数

如何分析ajax跨域

上述已经介绍了跨域的原理以及如何解决,但实际过程中,发现仍然有很多人对照着类似的文档无法解决跨域问题,主要体现在,前端人员不知道什么时候是跨域问题造成的,什么时候不是,因此这里稍微介绍下如何分析一个请求是否跨域

  • 第一步当然是得知道我们的ajax请求发送了什么数据,接收了什么,做到这一步并不难,也不需要fiddler等工具,仅基于Chrome即可
  • Chrome浏览器打开对应发生ajax的页面,F12打开Dev Tools
  • 右侧面板->NetWork->XHR,然后找到刚才的ajax请求,点进去

06ac301cb02173b3bcd3fe1ee5c5af3c.png

  • 上述请求是一个正确的请求,为了方便,我把每一个头域的意思都表明了,我们可以清晰的看到,接口返回的响应头域中,包括了
Access-Control-Allow-Headers: X-Requested-With,Content-Type,Accept
Access-Control-Allow-Methods: Get,Post,Put,OPTIONS
Access-Control-Allow-Origin: *
  • 所以浏览器接收到响应时,判断的是正确的请求,自然不会报错,成功的拿到了响应数据

跨域错误的ajax请求

3588553b8d7734afca6b72723cf11456.png

  • 这个请求中,接口Allow里面没有包括OPTIONS,所以请求出现了跨域

4cf2b1608e726d5889671e953b123362.jpg

  • 这个请求中,Access-Control-Allow-Origin: *出现了两次,导致了跨域配置没有正确配置,出现了错误。
  • 更多跨域错误基本都是类似的,就是以上三样没有满足(Headers,Allow,Origin),这里不再一一赘述。

与跨域无关的ajax请求

9b661f948b8e06a1ec9ca64d1e916452.png

  • 比如这个请求,它的跨域配置没有一点问题,它出错仅仅是因为request的Accept和response的Content-Type不匹配而已