前言
为什么浏览器跨域请求,多了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": "李四"} ]
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;} // 不能使用函数和日期对象 }
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>
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>
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>
用JSON表示如下:
{
"name": "中国",
"province": [{
"name": "黑龙江",
"cities": {
"city": ["哈尔滨", "大庆"]
}
}, {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}, {
"name": "台湾",
"cities": {
"city": ["台北", "高雄"]
}
}, {
"name": "新疆",
"cities": {
"city": ["乌鲁木齐"]
}
}]
优:
可以看到,JSON 简单的语法格式和清晰的层次结构明显要比 XML 容易阅读,并且在数据交换方面,由于 JSON 所使用的字符要比 XML 少得多,可以大大得节约传输数据所占用得带宽。
劣:
注意:
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)
def to_json(self, d): return json.dumps(d, cls=DatetimeSerializer)
二、Ajax
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。
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')); } } });
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>
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')
$.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会对数据进行深层次迭代;
响应参数
/*
dataType: 预期服务器返回的数据类型,服务器端返回的数据会根据这个值解析后,传递给回调函数。
默认不需要显性指定这个属性,ajax会根据服务器返回的content Type来进行转换;
比如我们的服务器响应的content Type为json格式,这时ajax方法就会对响应的内容
进行一个json格式的转换,if转换成功,我们在success的回调函数里就会得到一个json格式
的对象;转换失败就会触发error这个回调函数。如果我们明确地指定目标类型,就可以使用
data Type。
dataType的可用值:html|xml|json|text|script
见下dataType实例
*/
1.2.原生的ajax
Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除早起的IE),Ajax首次出现IE5.5中存在(ActiveX控件)。
XmlHttpRequest对象的主要方法:
XmlHttpRequest.open("请求方法","请求的url",是否执行回调函数?)
send用户发送请求
在请求体里
setRequestHead()
在请求头里设置数据
getRespnseHeaders()
获取所有响应头数据
getRespnseHeader(响应头)
获取响应头中指定得header对应值
abort 终止请求
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 服务端返回的状态信息
使用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)}} }
使用XmlHttpRequest对象发送 POST请求
function add1() { var xhr= new XMLHttpRequest() xhr.open('post','/add1/') xhr.send('name=张根&age=12') }
问题出现了为什么后端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') }
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>
神奇的一幕发生了 自动跳转到其他网站了
还可以 这么玩
<!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>
玩了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>
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>
会报错因为 加载<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>
最终版
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>
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>
后端
def fileup(request): if request.method=='GET': return render(request,'up_file.html') else: print(request.POST,request.FILES) return HttpResponse('OK')
使用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) } }
后端
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)
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>
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>
2.4.Ajax文件上传方式总结
上传方法总结
上传文件--->iframe+input(伪造ajax)
上传数据---->Jquery、XMLHttpRespose(原生ajax)
图片预览功能实现思路:
- 上传图片到服务端
- 服务端返回一个URL到客户端
- 客户端再去通过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次预检请求哦!!!
- 使用
POST
、PUT
、DELETE
等HTTP
方法。 - 请求中包含自定义的
HTTP
头字段。 - 请求体(
Body
)中包含非文本数据(如JSON
或XML
)。
当浏览器检测到跨域请求,满足上述任何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))
跨域者(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>
思考:
浏览器的同源策略不会限制带有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>
其他服务器
def data(request): v=request.GET.get('functionname') print('跨域请求来了!!') userlist=["alex","egon",'eric'] temp='%s(%s)'% (v,userlist) return HttpResponse(temp)
既然傻傻的你,早已看透了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>
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反向代理根本就不会跨域;
反向代理本身没有同源策略,用户访问的反向代理,由反向代理去请求后端服务器,反向代理响应用户;
参考