前言

为什么浏览器跨域请求,多了1次OPTIONS请求?

为什么在浏览器和后端服务端之间,增加1台Nginx反向代理服务器,浏览器和后端服务器就没有跨域问题?

服务端:即Web服务器,可以由C、Python、Java、NodeJS、Golang等后端语言使用其自身Web框架构建;

客户端:可以是由于编程语言开发的APP和Web浏览器;

如果服务端面向的客户端是浏览器这1种客户类型,可以称呼此服务端程序为B(Browser)/S(Server)架构;

下文将围绕B/S架构的以下几大话题展开?

  • B/S架构中可以传输什么数据格式?
  • B/S架构通信过程中遇到的跨域问题是什么?
  • B/S架构通信跨域如何解决?

一、Json数据格式

在B/S架构下,浏览器可以基于HTTP协议传输

  • x-www-form-urlencoded(form表单)
  • multipart/form-data(传输图片/视频等二进制内容form表单)
  • application/json

等数据类型,完成浏览器和服务器之间的数据传输,下文主要介绍Json;

1.什么是json?

JSON英文全程JavaScript Object Notation,即JavaScript对象标记法, 是一种轻量级的数据交换格式。

2.Json对象和JavaScript对象的关系

json对象:JavaScript对象的子集,json继承了JavaScript的number,string(" "),bool,null,array,{"name":"egon" ,"age ":18} 注意json只支持双引号 

3.Json对象和json字符串的关系: 

json字符串就是json对象+引号的字符串化,‘json对象  ’,json对象=JS对象,json字符串‘ 里面’存储的JavaScript的数据类型,所以相比其他语言JavaScript把json字符串解析成JS对象有天然优势;

3.Json字符串和Python的关系

Python拥有Python的数据类型;

JaveScript有JavaScript的数据类型;

但浏览器和服务器之间之所以可以交互,是因为中间有Json字符串做中间转换;

4.合格的json对象:

["one", "two", "three"]

{ "one": 1, "two": 2, "three": 3 }

{"names": ["张三", "李四"] }

[ { "name": "张三"}, {"name": "李四"} ]
View Code

5.不合格的json对象

{ name: "张三", 'age': 32 }                     // 属性名必须使用双引号

[32, 64, 128, 0xFFF] // 不能使用十六进制值

{ "name": "张三", "age": undefined }            // 不能使用undefined

{ "name": "张三",
  "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
  "getName":  function() {return this.name;}    // 不能使用函数和日期对象
}
View Code

6.JS序列化/反序列化

JSON.stringify(JS对象)     将JS对象序列化成 JSON字符串    ------->python的json.dumps()

<script>
{#JSON.stringify(JS对象) 将JS对象序列化成 JSON字符串#}
    console.log(typeof JSON.stringify([1,2,2,2]))
    console.log(typeof JSON.stringify({'name':'egon'}))
</script>
View Code

JSON.parse('JSON对象')    将json字符串反序列化成 JS对象--------->python的json.loads()

<script>
{#    JSON.parse('JSON对象')   将json字符串反序列化成 JS对象    #}
    console.log(JSON.parse('{"name":"egon","age":18}'))
    console.log(JSON.parse('[1,2,3,4,5]'))
</script>
View Code

7.Json和XML的比较

 JSON 格式于2001年由 Douglas Crockford 提出,目的就是取代繁琐笨重的 XML数据交换格式。

用XML表示中国部分省市数据如下:

<?xml version="1.0" encoding="utf-8"?>
<country>
    <name>中国</name>
    <province>
        <name>黑龙江</name>
        <cities>
            <city>哈尔滨</city>
            <city>大庆</city>
        </cities>
    </province>
    <province>
        <name>广东</name>
        <cities>
            <city>广州</city>
            <city>深圳</city>
            <city>珠海</city>
        </cities>
    </province>
    <province>
        <name>台湾</name>
        <cities>
            <city>台北</city>
            <city>高雄</city>
        </cities>
    </province>
    <province>
        <name>新疆</name>
        <cities>
            <city>乌鲁木齐</city>
        </cities>
    </province>
</country>
View Code

用JSON表示如下:

{
    "name": "中国",
    "province": [{
        "name": "黑龙江",
        "cities": {
            "city": ["哈尔滨", "大庆"]
        }
    }, {
        "name": "广东",
        "cities": {
            "city": ["广州", "深圳", "珠海"]
        }
    }, {
        "name": "台湾",
        "cities": {
            "city": ["台北", "高雄"]
        }
    }, {
        "name": "新疆",
        "cities": {
            "city": ["乌鲁木齐"]
        }
    }]
View Code

优:

可以看到,JSON 简单的语法格式和清晰的层次结构明显要比 XML 容易阅读,并且在数据交换方面,由于 JSON 所使用的字符要比 XML 少得多,可以大大得节约传输数据所占用得带宽。

劣:

注意:

JSON格式取代了xml给网络传输带来了很大的便利,但是却没有了xml的一目了然,尤其是json数据很长的时候,我们会陷入繁琐复杂的数据节点查找中。
但是国人的一款在线工具 BeJson 、SoJson在线工具让众多程序员、新接触JSON格式的程序员更快的了解JSON的结构,更快的精确定位JSON格式错误

8.把Python的datetime时间数据类型,转换成json字符串的方式

class DatetimeSerializer(json.JSONEncoder):
    """
    实现 date 和 datetime 类型的 JSON 序列化,以符合 公司数据格式要求。
    """

    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            head_fmt = "%Y-%m-%d %H:%M:%S"
            return "{main_part}.{ms_part}".format(
                main_part=obj.strftime(head_fmt),
                ms_part=int(obj.microsecond / 1000))
        elif isinstance(obj, datetime.date):
            fmt = '%Y-%m-%d'
            return obj.strftime(fmt)
        return json.JSONEncoder.default(self, obj)
date 和 datetime 类型的 JSON 序列化
 def to_json(self, d):
        return json.dumps(d, cls=DatetimeSerializer)

二、Ajax

AJAX(Asynchronous Javascript And XML)翻译成中文就是异步JavascriptXML”

Javascript语言与服务器进行异步交互的一种方式,传输的数据格式可以是XML和JSON

ajax相对于Form表单提交而言的,可以在保证页面不刷新的前提下,向后台发送数据

ajax特点

  • 通过异步的方式向服务器提交数据
  • 偷偷向server端发送数据,页面不刷新;

Jquery和Ajax关系

Jquery没有Ajax功能,它之所以可以调用Ajax向服务端提交数据,是因为Jquey封装了原生Ajax的代码

使用原生Ajax的优势

使用Ajax直接使用JS的XMLHttp Request对象, 无需引入Jquery了。这样响应客户端携带信息量减少,可节省流量。

1.使用Ajax

1.0.在发送ajax之前初始化ajax设置, beforeSend 函数;(如何在发送ajax之前,做一些操作)

例如设置发送ajax需要携带的CSRF-TOCKEN

 function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            // 请求头中设置一次csrf-token
            if(!csrfSafeMethod(settings.type)){
                xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
            }
        }
    });
View Code 

1.1.jQueryAjax

不再过多赘述,就是jQuery框架封装了原生Ajax实现出来的;

<script>
    $('p button').click(function () {
      var $user=$('p input').val()
       d={
    "name": "中国",
    "province": [{
        "name": "黑龙江",
        "cities": {
            "city": ["哈尔滨", "大庆"]
        }
    }, {
        "name": "广东",
        "cities": {
            "city": ["广州", "深圳", "珠海"]
        }
    }, {
        "name": "台湾",
        "cities": {
            "city": ["台北", "高雄"]
        }
    }, {
        "name": "新疆",
        "cities": {
            "city": ["乌鲁木齐"]
        }
    }]
}
     d=JSON.stringify(d)
      $.ajax({
           url:'/ajax/',
           type:'POST',
{# traditional:一般是我们的data数据有数组时会用到 #}
           traditional:true,
           data:d,
{#  ----------------- ajax的回调函数----------------------#}
{#       1、server端response 200成功,执行的回调函数#}
           success:function (data) {
               data=JSON.parse(data)
               console.log(typeof data)},
{#      2、erver端 response错误,执行的回调函数 #}
           error:function () {
               console.log(arguments)
               alert(123) },
{#      3、无论server端返回的结果如何,都会执行的回调函数#}
           complete:function () {
               alert(321)  },
{#      4、根据server端返回的状态码,执行的回调函数#}
           statusCode:{
               '403':function () {alert(403)},
               '503':function () {alert(503)}

           }
          })
    })
</script>
View Code

 Django 响应错误状态码 

def Ajax(request):
    d=json.loads(request.body)
    print(d)
    province=d['province'][0]
    print(province)
    d={'city': ['哈尔滨', '大庆']}
    HttpResponse.status_code=503
    # return HttpResponse(json.dumps(d))
    return HttpResponse('OK')
View Code

 $.ajax参数

请求参数

data:
ajax请求要携带的数据是肯定是一个json的object对象,ajax方法就会默认地把它编码成urlencode (urlencoded:?a=1&b=2)就像form表单、a标签格式的数据发送给server,此外,ajax默认以get方式发送请求。


注意:因为ajax要携带的数据是json的object对象,也会默认编码成urlcode格式,思考 如果你用 requests模块或者原生ajax向server发送数据、或者发送json数据,应该怎么做?
当然要指定 数据content-Type 和报头信息?

#-----------------------------------------------------------------
processData:声明当前的data数据是否进行转码或预处理,默认为true,即预处理;if为false,
             那么对data:{a:1,b:2}会调用json对象的toString()方法,即{a:1,b:2}.toString()
             ,最后得到一个[object,Object]形式的结果。



#-----------------------------------------------------------------------



contentType:默认值: "application/x-www-form-urlencoded"。发送信息至服务器时内容编码类型。
             用来指明当前请求的数据编码格式;urlencoded:?a=1&b=2;如果想以其他方式提交数据,
             比如contentType:"application/json",即向服务器发送一个json字符串:
               $.ajax("/ajax_get",{
             
                  data:JSON.stringify({
                       a:22,
                       b:33
                   }),
                   contentType:"application/json",
                   type:"POST",
             
               });                          //{a: 22, b: 33}

             注意:contentType:"application/json"一旦设定,data必须是json字符串,不能是json




----------------------------------------------
traditional:一般是我们的data数据有数组时会用到 :data:{a:22,b:33,c:["x","y"]},
              traditional为false会对数据进行深层次迭代;
View Code

响应参数

/*

dataType:  预期服务器返回的数据类型,服务器端返回的数据会根据这个值解析后,传递给回调函数。
            默认不需要显性指定这个属性,ajax会根据服务器返回的content Type来进行转换;
            比如我们的服务器响应的content Type为json格式,这时ajax方法就会对响应的内容
            进行一个json格式的转换,if转换成功,我们在success的回调函数里就会得到一个json格式
            的对象;转换失败就会触发error这个回调函数。如果我们明确地指定目标类型,就可以使用
            data Type。
            dataType的可用值:html|xml|json|text|script
            见下dataType实例

*/
View Code

1.2.原生的ajax

Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除早起的IE),Ajax首次出现IE5.5中存在(ActiveX控件)。

XmlHttpRequest对象的主要方法:

XmlHttpRequest.open("请求方法","请求的url",是否执行回调函数?)

send用户发送请求

在请求体里

setRequestHead()
在请求头里设置数据


getRespnseHeaders()
获取所有响应头数据

getRespnseHeader(响应头)
获取响应头中指定得header对应值


abort 终止请求
View Code

XmlHttpRequest对象的属性:

bj.readyState XMLHttpResposr的状态

0:未初始化,尚未调用open方法
1:启动调用了open方法
2:发送已经调用了send方法
3:接收状态(已经接收了部分数据)
4:完成(接收完毕服务端响应的数据)


只要对象的ready State改变就会执行该函数
xhr.onreadystatechange=function () {
            
        }



obj.responseText 服务端返回的数据(字符串类型)
obj.responseXML 服务端返回的数据(XML对象)

obj.number states 服务端返回的状态码
obj.response stateText 服务端返回的状态信息
View Code

使用XmlHttpRequest对象发送 GET请求

    function add1() {
{#        得到一个XMLHttpRequest对象 xhr   #}
        var xhr=new XMLHttpRequest();
{#        open打开一个http连接到指定url   XMLHttpRequest请求#}
        xhr.open('get','/add1/?i1=18&name=张根 ')
          {#       设置请求体的数据#}
        xhr.send()
{#        XMLHttpRquest的状态改变这个函数的状态就会执行jquryAjax中的success回调函数就是基于此#}
        xhr.onreadystatechange=function () {
            if(xhr.readyState==1){console.log("对象未调用open方法")}
            if(xhr.readyState==2){console.log("请求发送")}
            if(xhr.readyState==3){console.log("接收中")}
            if (xhr.readyState == 4) {console.log(xhr.responseText)}}
    }
View Code

使用XmlHttpRequest对象发送 POST请求

function add1() {

var xhr= new XMLHttpRequest()
xhr.open('post','/add1/')
xhr.send('name=张根&age=12')
}
View Code

 问题出现了为什么后端request.post方法没有数据呢?

使用jQuery发送post请求jQuery会默认把 发送的数据(js对象)编码成urlencoded格式,使用原生ajax就不会了,

所以使用原生ajax发POST请求之前,请求头中 务必要指定数据的编码格式“Content-Type”, “application/x-www-form-urlencoded”才能发到server端;

function add1() {
var xhr= new XMLHttpRequest()
xhr.open('post','/add1/')
xhr.setRequestHeader("ConTent-Type" ,"application/x-www-form-urlencoded")
xhr.send('name=张根&age=12')
               }
View Code

1. 3.iframe标签+form表单伪造Ajax

 

所谓伪装Ajax操作就是不利于任何Ajax,利于其他技术向后台发送数据,这个其他的技术要从 iframa标签说起。。。。。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎来到乐X网</title>
</head>
<body>

<iframe style="width:1000px; height:2000px" 

src="https://www.hhh286.com/htm/movieplay1/6748.htm" ></iframe>

</body>
</html>
View Code

神奇的一幕发生了 自动跳转到其他网站了

还可以 这么玩

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎来到乐C网</title>
</head>
<body>
<div>
    <input type="text" id="txt1">
    <input type="button" value="查看" onclick="cheangurl()">
</div>
<iframe id="ifr" style="width:1000px; height:2000px" src="https://www.baidu.com" ></iframe>

</body>

<script>
   function cheangurl() {
   var inpu=document.getElementById('txt1').value
    document.getElementById('ifr').src=inpu
   }

</script>
</html>
View Code

玩了2把就得出结论了

1、iframe标签可以不刷新发送http请求特性

2、服务端返回什么值 就 iframe标签中就会显示什么

思考:form表单和 iframe标签配合起来不就可以伪造 ajax请求了!

<form action="/fakeajax/" method="post" target="ifr">
{#     target="ifr" taget目标靶子 表示From不直接把打包的input标签数据提交到后台,而是通过属性为 

ifr的iframe标签  #}
    <iframe name="ifr" style="display: none"></iframe>
    <input name="user" type="text">
    <input type="submit" value="提交">
</form>
View Code

form把数据提交给 iframe标签间接向服务端发送POST请求,既向后台发送了数据,页面也不会刷新,但是怎么模拟回调函数呢?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>伪造Ajax</title>
</head>
<body>

<input type="text">
<form action="/fakeajax/" method="post" target="ifr">
    <iframe name="ifr" style="display: block" onload="loadiframe()"></iframe>
{#    当iframe加载时,也就是有返回值的时候 执行loadiframe()     模拟回调函数#}
    <input name="user" type="text">
    <input type="submit" value="提交">
</form>
</body>
<script>
   function loadiframe() {
       alert('回来了')
   }
</script>
</html>
View Code

会报错因为 加载<iframe name="ifr" style="display: block" onload="loadiframe()"></iframe>第一次加载触发onload时间 但是JS代码还在下面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>伪造Ajax</title>
</head>
<body>

<input type="text">
<form id="f1" action="/fakeajax/" method="post" target="ifr">
    <iframe name="ifr" id="ifr"></iframe>
{#    当iframe加载时,也就是有返回值的时候 执行loadiframe()     模拟回调函数#}
    <input name="user" type="text">
    <a onclick="submit_form()">提交 </a>
</form>
</body>
<script>
    function submit_form() {
       document.getElementById('f1').submit()
       document.getElementById('ifr').onload=loadiframe
    }
   function loadiframe() {
       alert('回来了')
   }
</script>
</html>
View Code

 最终版

iframe标签中 值不是简单的文本而是又嵌套了一个页面,如何获取iframe标签中的值 ?

 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>伪造Ajax</title>
</head>
<body>

<input type="text">
<form id="f1" action="/fakeajax/" method="post" target="ifr">
    <iframe name="ifr" id="ifr"></iframe>
{#    当iframe加载时,也就是有返回值的时候 执行loadiframe()     模拟回调函数#}
    <input name="user" type="text">
    <a onclick="submit_form()">提交 </a>
</form>
</body>
<script>
    function submit_form() {
       document.getElementById('f1').submit()
       document.getElementById('ifr').onload=loadiframe
    }
   function loadiframe() {
      var return_values=document.getElementById('ifr').contentWindow.document.body.innerHTML
      console.log(return_values)
   }
</script>
</html>
View Code

2.基于Ajax上传文件并预览

不管是原生ajax和jQuery都可以依赖JS中一个FormData对象上传文件

上面我们已经尝试过使用原生的ajax发送字符串数据,那怎么上传文件呢?且听 京西沙河淫王娓娓道来。。。。。。。

话说 想要使用原生的ajax要想发送文件,就必须借助JS中formdata对象,( var formdata=new FormData())

帮我们把文件 构造成特定的请求体和请求头,注意发送字符串时要自定义请求头中ConTent-Type,现在不用多此一举了;

2.1.原生的ajax

前端

!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>原生Aja上传文件</title>
</head>
<body>
<h1>原生ajax上传文件</h1>
<p><input type="file"  id="f1"></p>
<p><input id="f2" type="button" onclick="fileup()" value="上传"></p>
</body>
<script>
    function fileup(){
        var formdata=new FormData()
{#        要想发文件必须借助一个特殊的 formdata对象,帮助我们封装 文件对象 #}
        formdata.append('k1','v1')
        var xhr= new XMLHttpRequest()
        xhr.open('POST','/fileup/')
{#        xhr.setRequestHeader("ConTent-Type" ,"application/x-www-form-urlencoded")#}
{#        切记 既然文件对象帮我们做了文件封装,#}
{#        也包括了请求头"ConTent-Type,就无需在像原来发字符串的时候加"ConTent-Type了#}
        xhr.send(formdata)
        xhr.onreadystatechange=function () {
            if(xhr.readyState==4){alert(xhr.responseText)}
        }

    }
</script>
</html>
View Code

后端

def fileup(request):
    if request.method=='GET':
        return render(request,'up_file.html')
    else:
        print(request.POST,request.FILES)
        return HttpResponse('OK')
View Code

 使用formdata对象做图片预览

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>原生Aja上传文件</title>
</head>
<body>
<h1>原生ajax上传文件</h1>
<div id="container"></div>
<p><input type="file"  id="f1"></p>
<p><input id="f2" type="button" onclick="fileup()" value="上传"></p>
</body>
<script>
    function fileup(){
        var formdata=new FormData()
{#        要想发文件必须借助一个特殊的 formdata对象,帮助我们封装 文件对象 #}
        formdata.append('k1','v2')
        formdata.append('k1',document.getElementById('f1').files[0])
{#        获取文件对象.files得到是一个列表(全部的文件对象)获取第1个切片[0]#}
        var xhr= new XMLHttpRequest()
        xhr.open('POST','/fileup/')
{#        xhr.setRequestHeader("ConTent-Type" ,"application/x-www-form-urlencoded")#}
{#        切记 既然文件对象帮我们做了文件封装,#}
{#        也包括了请求头"ConTent-Type,就无需在像原来发字符串的时候加"ConTent-Type了#}
        xhr.send(formdata)
        xhr.onreadystatechange=function () {
            if(xhr.readyState==4){
                var filepath=xhr.responseText
{#                获取服务端返回的文件路径#}
                var tag=document.createElement('img')
                tag.src="/"+filepath
{#                在本地生成一个img标签显示#}
                document.getElementById('container').appendChild(tag)

                }
        }
View Code

后端

def fileup(request):
    print('ok')
    if request.method=='GET':
        return render(request,'up_file.html')
    else:
        print('OK')
        fileobj=request.FILES.get('k1')
        filepath=os.path.join('static',fileobj.name)
        with open(filepath,'wb') as f:
            for chunk in fileobj.chunks():
                f.write(chunk)
        return HttpResponse(filepath)
View Code

2.2.jQuery Ajax文件上传预览

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>原生Aja上传文件</title>
</head>
<body>
<h1>原生ajax上传文件</h1>
<div id="container"></div>
<p><input type="file" id="f1"></p>
<p><input id="f2" type="button" onclick="Queryup()" value="上传"></p>
<script src="/static/zhanggen.js"></script>
<script>
 function Queryup() {
    var formdata=new FormData()
{#   $(document.getElementById('f1'))       #}
{#    doc对象转换成Jquery对象 $(doc对象)    #}
{#    jQueryup对象转成doc对象 jQuery对象[0] #}
{#    $('#f1')[0]#}
    formdata.append("k1", $('#f1')[0].files[0])
    $.ajax({
        url:'/fileup/',
        type:'POST',
        contentType:false,
{#        告诉Jquery不要对请求头做特殊处理,因为formDta已经把数据封装好了#}
        processData:false,
        data:formdata ,
        success:function (args) {
           var ele=document.createElement('img')
            ele.src="/"+ args
           document.getElementById('container') .appendChild(ele)
        }
    })
}

</script>
</body>
</html>
View Code

 2.3.iframe+form标签伪造ajax操作(兼容性更好)

由于FormData对象是HTML5之后提出的新对象,不兼容之前老版本的浏览器;

一般JS的上传插件都是通过 iframe+form伪造出来的,所以这种上传方法的兼容性会更好些;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>伪造Ajax上传</title>
</head>
<body>
<h1>伪造Ajax上传</h1>
<form method="post" id="f1" action="/fileup/" target='ifr'enctype="multipart/form-data">
  <p><input type="file" name="k1"></p>
  <p><iframe id="ifr"name="ifr" style="display: none"></iframe></p>
  <p><input type="button" value="提交" onclick="upfile()"></p>
</form>
<div id="container"></div>
</body>
<script>
function upfile() {
{#    找到iframe标签在线绑定事件 页面加载事件()#}
    document.getElementById('ifr').onload=loadIframe
{#    使用.submit()方法 提交表单#}
    document.getElementById('f1').submit()
                }
function loadIframe() {
{#    服务端有返回值 iframe标签就会加载,利用此特性绑定 加载事件,然后创建img标签,apendchilder到 一个div中显示 #}
    var return_value=document.getElementById('ifr').contentWindow.document.body.innerHTML
    var tag=document.createElement('img')
    tag.src='/'+return_value
    document.getElementById('container').appendChild(tag)}
</script>
</html>
View Code

2.4.Ajax文件上传方式总结

上传方法总结

上传文件--->iframe+input(伪造ajax)

上传数据---->Jquery、XMLHttpRespose(原生ajax)

图片预览功能实现思路:

  1. 上传图片到服务端
  2. 服务端返回一个URL到客户端
  3. 客户端再去通过img标签src服务端的返回的URL地址 

2.5.ajax长连接(不断刷新)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/jquery-3.2.1.js"></script>
</head>
<body>
<div id="time">{{ c_time }}</div>
</body>
<script>
    $(function () {
        GetTime()
    });

    function GetTime() {
        $.ajax({
            url:{% url 'gettime' %},
            type: 'POST',
            data: {'csrfmiddlewaretoken': "{{ csrf_token }}"},
            dataType: 'JSON',
            success: function (res) {
                $('#time').text(res);
                GetTime()


            }
        })
    }
</script>
</html>
不断更新当前时间

 

三、跨域

默认情况下www.Laden.com域名下的网页是无法和www.Aobam.com域名下以及任何其他服务器跨域沟通的;

因为以上2个域名不同源;

1.同源内容

2个什么样URL PATH才算是同源呢!

域名,协议,端口相同

2.跨域请求与预检请求

跨域之后为什么多了1次OPTIONS请求?

当我们进行跨域请求时,尤其是使用CORS(跨域资源共享)时;

浏览器在正式发送第1个复杂请求之前,先自动发送1个OPTIONS请求,这就是所谓的预检请求。

一旦服务器通过了预检请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,

浏览器请求头:会新增1个Origin头信息字段。

服务器响应头:会新1个Access-Control-Allow-Origin信息字段。

既然复杂请求发送之前需要发送预检请求,那复杂请求和简单请求如何界定呢?

2.1.复杂请求和简单请求界定

在请求跨域时,若请求满足以下全部条件,则该请求可视为简单请求:

  • HTTP 方法限制:只能使用 GET、HEAD、POST 这3种 HTTP 方法之一。如果请求使用了其他 HTTP 方法,就不再被视为简单请求。
  • 自定义标头限制:请求的 HTTP 标头只能是以下几种常见的标头:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain)。HTML 头部 header field 字段:DPR、Download、Save-Data、Viewport-Width、WIdth。如果请求使用了其他标头,同样不再被视为简单请求。
  • 请求中没有使用 ReadableStream 对象。
  • 不使用自定义请求标头:请求不能包含用户自定义的标头。
  • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问

单纯地的使用POST这种HTTP方法属于简单请求,但POST请求头中自定义了content-type=application/json内容就违反了简单请求界定规则了呀!!!

我们常见的POST请求头中包含content-type=application/json ,那此POST请求,就不在属于简单请求了,需要额外再触发1次预检请求哦!!!

  • 使用POSTPUTDELETEHTTP方法。
  • 请求中包含自定义的HTTP头字段。
  • 请求体(Body)中包含非文本数据(如JSONXML)。

当浏览器检测到跨域请求,满足上述任何1个条件时,就会自动发送1个OPTIONS预检请求,而简单请求不会发送OPTIONS预检请求

2.2.简单跨请求跨域

服务器响应头应设置以下1个字段;

  • Access-Control-Allow-Origin = '域名' 或者 '*'

2.3.复杂请求跨域

由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。

服务器响应头应设置以下3个字段;

  • “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
  • “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
  • “预检”缓存时间,服务器设置响应头:Access-Control-Max-Age

3.浏览器跨域流程分析

因为浏览器的同源策略会导致跨域失败,但是失败在哪个环节?

  • 1.当客户端发送一个GET请求服务端返回HTML字符串(若HTML字符串中包含的JS代码让浏览器,再去其他网站获取数据
  • 2.这时客户端(浏览器)就不能再去发送请求到其他服务端的(除非带有src属性的标签例外
  • 3.如果客户端发送了请求到其他服务端下,其他服务器也会收到,但在其他服务端再返回到浏览器时会被浏览器阻止掉;

实验证明

 API(http://127.0.0.1:8000/)

def data(request):
    print('跨域请求来了!!')
    userlist=["alex","egon",'eric']
    return HttpResponse(json.dumps(userlist))
View Code

跨域者(http://127.0.0.1:8086)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<h1>jsop</h1>
<ul id="user">

</ul>
<input type="button" value="获取API的用户列表" onclick="getusers()">
</body>
<script src="/static/zhanggen.js"></script>
<script>
function getusers(){
    var xhs=new XMLHttpRequest()
    xhs.open('GET','http://www.api.com:8000/data/')
    xhs.send()
    xhs.onreadystatechange=function () {
        if(xhs.readyState==4){
            var content=xhs.responseText
            console.log(content)
        }
    }
}

</script>
</html>
View Code

 

 

 思考:

浏览器的同源策略不会限制带有src属性的标签跨域向其他服务器发送请求

有些程序员机制的利用了这一点,绕开了浏览器的同源策略,发明了jsonp既保证了浏览的安全策略,也解决了浏览器跨站访问数据的问题;

四、跨域解决方案

Jsonp、CORS、反向代理都可以从不同方向出发解决跨域问题;

1.Jsonp

可以使用JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题

啰嗦版

1.定义个名为的函数 列入名为b;

2.使用JS动态创建script标签(例如:tag.src='http://www.api.com:8000/data/?functionname=b)通过带有src属性的script标签,跨域向其他服务器发送GET请求;

所以JSONP只能发送GET请求

3.其他服务器收到后 获取函数名 v=request.GET.get('functionname')

两人交互约定很重要啊

4.由于客户端浏览器收到了函数名+括号会立即执行 最初定义的函数B

简洁版

1、客户端定义函数
2、客户端把函数名发给其他服务端
3.其他服务端 拼接一个 函数(参数)返回给客户端
4.客户端执行

一言以敝之版

客户端定义函数,服务端字符串拼接 函数、()、参数,返回客户端,客户端执行最初定义的函数(参数)

 代码版

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<h1>jsop</h1>
<ul id="user">

</ul>
<input type="button" value="获取API的用户列表" onclick="getusers()">
</body>
<script src="/static/zhanggen.js"></script>
<script >
    function getusers() {
      var tag=document.createElement('script')
        tag.src='http://www.api.com:8000/data/?functionname=b'
      document.head.appendChild(tag)
        
    }
function b(args) {
   document.getElementById('user').innerHTML=args
}

</html>
View Code

其他服务器

def data(request):
    v=request.GET.get('functionname')
    print('跨域请求来了!!')
    userlist=["alex","egon",'eric']
    temp='%s(%s)'% (v,userlist)
    return HttpResponse(temp)
View Code

 既然傻傻的你,早已看透了JSONP跨域访问的一切,JjQuery也会,于是它可以发送JSONP请求封装在了自己的ajax里面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<h1>jsop</h1>
<ul id="user">

</ul>
<input type="button" value="获取API的用户列表" onclick="getusers()">
</body>
<script src="/static/zhanggen.js"></script>
<script >
        $.ajax({
            url:'http://www.api.com:8000/data/',
            type:'get',
            dataType:'jsonp',
            jsonp:'functionname',
            jsonpCallback:'b'
        })
    }
function b(args) {
   document.getElementById('user').innerHTML=args
}
</script>

</html>
View Code

2.CORS

CORS全称Cross-Origin Resource Sharing,即跨源站资源共享;

 看完了jsonp的跨域方式,是不是赶紧有些复杂呢?

在第3方网站的服务端返回客户端的响应头里面,设置特殊标记(令牌), 浏览器看到就不会阻止内容渲染了,这就是CORS跨站共享;

通常在Django/Gin/Nginx的响应头中设置以下下参数

 ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
  ctx.set("Access-Control-Allow-Credentials", true);
  ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
  ctx.set(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept, cc"
  );

3.Nginx反向代理

Nginx反向代理根本就不会跨域;

反向代理本身没有同源策略,用户访问的反向代理,由反向代理去请求后端服务器,反向代理响应用户;

参考

银角大王

二龙湖浩哥

posted on 2017-07-08 15:47  Martin8866  阅读(754)  评论(0编辑  收藏  举报