JQuery中ajax操作和跨站访问详解(后端Django版本)

最近在学习JQuery中的AJAX操作,操作本身比较简单,主要是遇到了跨站访问的一些问题,这里详细记录一下原理和解决思路。

Ajax操作

Ajax是一种异步请求服务器数据的技术,可以实现在前端不加载整个网页的情况下更新网页的部分内容。原生的Ajax这里就不讨论了,直接用JQuery,简单而且兼容性强。其中最常用的就是下面的3中方法了

$.ajax()

$.ajax({name:value, name:value, ... })

执行异步ajax请求,参数是一个字典类型,常用的一些字段如下

字段说明
url规定发送请求的 URL,默认是当前页面
type规定请求的类型(GET 或 POST)
success(result,status,xhr)当请求成功时运行的函数,三个参数分别是返回的结果,返回状态和xhr
error(xhr,status,error)如果请求失败要运行的函数
contentType发送数据到服务器时所使用的内容类型。默认是:“application/x-www-form-urlencoded”
data规定要发送到服务器的数据
dataType预期的服务器响应的数据类型

$.get()

$.get(URL,data,function(data,status,xhr),dataType)

使用ajax的http get请求从服务器加载数据,这里的参数比较简单,只有URL是必须的,另外几个参数都是可选。

$.post()

$(selector).post(URL,data,function(data,status,xhr),dataType)

使用ajax的http post请求从服务器加载数据,参数和get方法一样。

实际操作

一般情况下用$.ajax()就能够满足需求了,下面用一个测试页面来实际操作下。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="/static/jquery-3.5.1.js"></script>
    <title>Test Ajax</title>
    <script>
        function login(){
            $.ajax({
                type: "POST",//方法类型
                dataType: "json",//预期服务器返回的数据类型
                url: "http://172.29.56.178/test/hello/" ,//url
                data: $('#form1').serialize(),
                success: function (result) {
                    console.log(result);//打印服务端返回的数据(调试用)
                    if (result.resultCode == 200) {
                        alert("SUCCESS");
                    }
                    ;
                },
                error : function() {
                    alert("异常!");
                }
            });
        }
    </script>
</head>
<body>
<form action="##" id="form1">
    Name:<input type="text" name="userName" placeholder="xiaofu"><br>
    Password: <input name="password" type="password"><br>
    <input type="button" onclick="login()" value="submit">
</form>
</body>
</html>

页面中一共就一个表单,两个输入框和一个按钮,点击按钮会执行上面定义的login函数。在函数中执行了一个ajax请求到同网站的/test/hello/地址,传递的数据是表单数据中的userNamepassword,接受的返回是一个json数据。

同时有必要说明下form表单的按钮,如果是type='submit'的话表单是会有一个默认的onsubmit的行为,必须要设置为return false,不然按钮的点击事件会无法触发,如下

<form action="##" id="form1" onsubmit="return false">
    Name:<input type="text" name="userName" placeholder="xiaofu"><br>
    Password: <input name="password" type="password"><br>
    <input type="submit" onclick="login()" value="submit">
</form>

后端的响应我是用Django写的,view函数如下

@csrf_exempt
def hello(request):
    if request.method == 'GET':
        response = JsonResponse(data={'status': 'Success', 'message': 'hello from xiaofu'})
    elif request.method == 'POST':
        name = request.PpasswordOST.get('userName')
        password = request.POST.get('password')
        response = JsonResponse(data={'status': 'Success', 'name': name, 'password': password, 'message': 'hello from xiaofu'})
    return response

因为Django有自带防跨站的csrf中间件,为了不和这里浏览器的跨站混淆,使用装饰器的方式进行了豁免。对Django的csrf中间件感兴趣的朋友可以参考我的另一篇博客《【Django 024】中间件Middleware(三):Django自带csrf中间件源码分析以及跳过csrf报错的四种方法》

后端会获取post上来的userNamepassword字段的内容并放入json返回,测试结果如下

1-normal.png

成功通过ajax进行了一次post请求,并且返回了数据。但是注意,这里是用ajax访问的同一个网站的内容

我这里使用Nginx部署的网站,感兴趣的朋友可以参考这几篇nginx动静分离部署Django项目的教程

浏览器同源策略

之所以在上面要强调是同一网站的内容,是因为如果直接进行跨站访问默认会失败,因为浏览器有一个叫做同源策略(Same Origin Policy)的安全策略在限制这种跨站访问行为。

同源策略是浏览器最核心也最基本的安全功能,也就是说一个网站的脚本只能去请求同一网站的资源。这里的同源指的是:协议,域名和端口都相同。

例如针对网站http://www.xiaofu.com/test/index.html来说

URL是否同源原因
http://www.xiaofu.com/other/index.html协议,域名,端口都一致
https://www.xiaofu.com/test/index.html协议不一致
http://www.xiaofu.com:8080/test/index.html端口不一致
http://www.xiaofu.cn/test/index.html域名不一致

那么到底为什么要有这个同源策略的限制存在呢?

因为浏览器在访问某网站的时候会带上与该网站相关的所有cookie信息,假如用户登陆了银行网站http://www.bank.com,该网站就会像用户使用的浏览器下发用于用户认证的cookie。此时用户访问了钓鱼网站http://www.xiaofu.com,该网站有一个ajax脚本会在后台访问http://www.bank.com,此时浏览器会带上用户认证过的cookie,使得银行根本不能分辨是否是用户本人在访问,从而造成隐私信息泄露。而且可怕的是因为ajax在后台可以直接运行,用户甚至都感觉不到自己的信息已经泄露。

跨域操作

下面用实际操作来感受下浏览器的同源策略。

将刚才的前端页面搬到本地127.0.0.1来,同样是访问相同的后端,此时在浏览器的console中就会有如下报错出现

2-error.png

这里提到的cors就是其中一种跨站访问的方法

CORS

全称为Cross Origin Resource Sharing,跨域资源共享。它是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10,所以关键是看服务器端是否支持。

先来看看流程再来尝试实现。

浏览器发现是在做一个跨域请求的时候,会在请求头中添加一个Origin字段,包含发起请求网站的源信息(协议,域名和端口),例如Origin:http://www.xiaofu.com;浏览器会去检查请求头的Origin字段,如果允许访问,就会在返回头中添加一个Access-Control-Allow-Origin字段,返回相同的源信息,例如Access-Control-Allow-Origin:http://www.xiaofu.com,当然也可以回一个通配符,表示对所有网站都开放访问;如果服务器返回的源信息和发送的源信息不符合,则会出现上面的报错。

下面是刚才发送的请求头信息,可以看到浏览器发送了Origin字段

3-request.png

再看服务器返回的头信息,并没有Access-Control-Allow-Origin字段

4-response.png

下面修改下服务器的view函数,给返回设置头信息

@csrf_exempt
def hello(request):
    if request.method == 'GET':
        response = JsonResponse(data={'status': 'Success', 'message': 'hello from xiaofu'})
        response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8848'
    elif request.method == 'POST':
        name = request.POST.get('userName')
        password = request.POST.get('password')
        response = JsonResponse(data={'status': 'Success', 'name': name, 'password': password, 'message': 'hello from xiaofu'})
        response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8848'
    return response

之后发现可以成功跨站访问,并且从浏览器查看返回头也看到了Access-Control-Allow-Origin信息

5-cors.png

值得一提的是这种跨域方式是不带敏感信息的,例如cookie信息。要携带敏感信息,还需要在ajax请求中设置xhr的属性 withCredentials 为 true,同时还需要服务器返回的头信息中有Access-Control-Allow-Credentials: true信息。因为cookie话题展开会较大,下次单独再写博客说明。

那么如果人为将请求网站的源信息修改一下可不可以呢?

可以,通过document.domain就可以修改。但是不能随便修改,只能修改为自身或者更高一级的父域才可以。例如http://www.xiaofu.com只能修改为http://xiaofu.com。同时目标页面也要显性设置为同样的domain才可以跨域访问。所以这种方式一般用于主域名相同,而子域名不同的情况。这里也不展开说明了。

JSONP

细心的朋友可能想到了,我们在页面中引入外部的js脚本,css样式或者是图片都不受上面的同源策略的限制。那么就可以在script标签的src属性中进行跨站访问。

JSONP(JSON with Padding)是一种跨站发送JSON数据的方法,使用<script>标签的src属性来请求数据,并且请求路径必须带上callback字段。服务器在返回的时候将数据作为参数包裹在callback函数调用中,客户端收到返回之后调用本地的同名函数,该函数必须在客户端已存在才行。但是要注意,该方式只支持GET方式

下面来实际操作一下。

首先把前面服务端设置的CORS返回头给注释掉,确保直接访问会出错。然后修改下前端如下

<script type="text/javascript">
        function handleJson(data){
			console.log(data);
		}
		
		function login(){
			var name = $('#userName').val();
			var passwd = $('#password').val();
			var $newScript = $("<script><\/script>");
			$newScript.attr('src','http://172.29.56.178/test/hello/?callback=handleJson&userName='+name+'&password='+passwd);
			$('body').append($newScript);
		}
 </script>

注意创建script标签时候的那个转义符号

这里声明一个函数handleJson用于处理服务器返回的数据,同时修改了login函数的逻辑,新建一个script标签,同时将表单里面的内容以查询参数的形式传递给后端(因为只能用GET方式),并且附上callback=handleJson的回调函数信息。

在服务端,修改Django的view函数如下

@csrf_exempt
def hello(request):
    if request.method == 'GET':
        if 'callback' in request.GET:
            name = request.GET.get('userName')
            password = request.GET.get('password')
            data = {'status': 'Success', 'name': name, 'password': password, 'message': 'hello from xiaofu'}
            result = '{}({})'.format(request.GET.get('callback'), data)
            response = HttpResponse(result, content_type="application/json")   
    elif request.method == 'POST':
        name = request.POST.get('userName')
        password = request.POST.get('password')
        response = JsonResponse(
            data={'status': 'Success', 'name': name, 'password': password, 'message': 'hello from xiaofu'})
    return response

注意这里返回的是callback(data)的形式,同时指明content_type为json。

成功拿到结果

6-jsonp.png

并且没有任何CORS相关的头信息

7-nocors.png

总结

总结下这一节的知识点

  • JQuery中有3种常用的ajax请求方式,基本用$.ajax()方式即可
  • 浏览器出于安全考虑,默认情况下只允许页面脚本在同源站点间进行互相访问。同源指的是协议,域名和端口都一致
  • 如果浏览器和服务器都支持CORS协议,通过在请求头和响应头里面添加源的信息即可实现跨域访问,GET和POST方式都行
  • 源信息可以被修改,但是只能是同一级或者是父级域。同时必须请求页面和目标页面都显性设置。适用于主域名相同的子域名之间的访问
  • script标签的src因为不受浏览器的同源策略限制,可以实现JSONP的跨域操作。这种方式只支持GET方式,同时必须客户端和服务器的callback函数名要一致

当然,这里对于跨域操作只是抛砖引玉,还有很多种跨域操作。感兴趣的朋友可以参考下面两篇博客

而关于跨域携带cookie的操作,我会在下一篇博客给大家进行分享。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值