【四】Ajax与异步编程之数据传输格式ajax同源策略和跨域方案

发布时间 2023-07-16 15:47:24作者: Chimengmeng

【四】Ajax与异步编程之数据传输格式ajax同源策略和跨域方案

【1】同源策略

  • 同源策略(ame origin policy),是浏览器为了保护用户信息在服务端的安全的而设置一种安全机制。
    • 所谓的同源就是指代通信的两个地址(例如服务端接口地址与浏览器客户端页面地址)之间比较,是否协议、域名(IP)和端口相同。
    • 不同源的客户端脚本[javascript]在没有服务器明确授权的情况下,没有权限读写服务端的信息。
  • ajax本质上还是javascript,是运行在浏览器中的脚本语言,所以会被受到浏览器的同源策略所限制。
前端地址:http://www.fuguang.cn/index.html 是否同源 原因
http://www.fuguang.cn/user/login.html 协议、域名、端口相同
http://www.fuguang.cn/about.html 协议、域名、端口相同
https://www.fuguang.cn/user/login.html 协议不同 ( https和http ),端口不同
http:/www.fuguang.cn:5000/user/login.html 端口不同( 5000和80)
http://bbs.fuguang.cn/user/login.html 域名不同 ( bbs和www )

image-20220706102258620

同源策略针对ajax的拦截

代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="text" name="music" value="月亮之上">
    <button class="btn">搜索歌曲</button>
    <script>
    let btn = document.querySelector(".btn")
    let music = document.querySelector("input[name=music]")
    btn.onclick = function(){
        fetch(`http://msearchcdn.kugou.com/api/v3/search/song?keyword=${music.value}`,{
            method: "get",
        }).then(response=>response.json()).then(response=>{
            console.log(response)
        }).catch(error=>{
            console.log(error)
        })
    }
    </script>
</body>
</html>

效果:

【2】ajax跨域(跨源)方案

  • 因为同源策略的问题,所以我们一般情况下只要使用了ajax进行服务端的数据的操作都会面临着同源策略的拦截和影响。

    • 一般在开发中解决这个问题,我们也叫ajax的跨域(跨源)问题。
  • 所谓的跨域实际是就是让我们的javascript代码可以在基于与服务端不同源的客户端下正常运行。

  • 一般出现跨域问题时,代码在浏览器的console终端下,都会显示如下错误信息:

当然,上面的错误有时候并非一定就是同源策略的问题,也有可能是因为以下原因:
1. 服务端代码报错,或者服务端没有正确的返回授权
2. 客户端请求的服务端的请求报文出错[错误的url地址,错误的请求方法,错误的请求头]
3. 客户端携带了不明来历的cookie信息

在工作中,跨域的解决方案一般无非以下三种:

  • cors方案

    • W3C维护的一个跨域资源共享的协议。

    • 这个协议要求服务端通过响应头的方式告知浏览器,当前跨域访问的客户端是否属于可信任客户端。

    • cors方案的使用前提是服务端已经支持了cors。

  • 服务端代理(Proxy Server)

    • 自己搭建一个可控的代理服务端(自己的服务端项目),因为同源策略只存在于浏览器,所以我们让客户端直接请求代理服务端,然后代理服务端接收客户端请求以后,执行代码请求目标服务端那边获取到客户端需要的数据,最终由代理服务端把数据返回给客户端。
  • jsonp方案

    • 原则上来说jsonp不是ajax技术,而是利用了HTML中的某些默认能跨域的标签来实现的。
    • script,link,src属性,iframe,…

    客户端先预定义一个js函数。

    然后服务端生成js代码,代码里面把数据作为函数的参数,在客户端请求的时候返回。

(1)cors方案和服务端代理

客户端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="text" name="music" value="月亮之上">
    <button class="btn">搜索歌曲</button>
    <script>
    let btn = document.querySelector(".btn")
    let music = document.querySelector("input[name=music]")
    btn.onclick = function(){
        fetch(`http://127.0.0.1:8000/?music=${music.value}`,{
            method: "get",
        }).then(response=>response.json()).then(response=>{
            console.log(response)
        }).catch(error=>{
            console.log(error)
        })
    }
    </script>
</body>
</html>

代理服务端代码:

import json

from aiohttp import web, ClientSession

import aiohttp_cors

routes = web.RouteTableDef()

@routes.get("/")
async def get_index(request):
    # 返回json数据
    music = request.query.get("music", None)
    if music is None:
        return web.json_response({"errno": -1, "errmsg": "参数有误!"})
    # 发起请求,搜索音乐
    # http://msearchcdn.kugou.com/api/v3/search/song?keyword=
    url = f"http://msearchcdn.kugou.com/api/v3/search/song?keyword={music}"
    async with ClientSession() as session:
        async with session.get(url) as response:
            print("status:{}".format(response.status))
            if response.status == 200:
                data = await response.text()
                data = json.loads(data)
                print(data)
                return web.json_response(data)
            else:
                return web.json_response({"errno": -1, "errmsg": "搜索失败!"})

class Application(web.Application):
    def __init__(self, routes):
        """
        :param routes: url和服务端处理程序之间的绑定关系, 这种绑定关系,我们一般称之为路由,当然,在服务端开发中,我们也会经常把具有路由功能功能的类/对象,称之为"路由类"或"路由对象"
        """
        print(routes._items) # 路由列表
        # 先让官方的aiohttp的web框架先进行初始化
        super(Application, self).__init__()
        # 注册路由信息到web框架中,routes是一个list列表
        self.add_routes(routes)

        # 这里内部完成了一个for循环,把routes里面每一个路由信息都了遍历,添加一个返回值用于实现跨域资源共享[CORS]。
        cors = aiohttp_cors.setup(self, defaults={
            "*": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
        })

        # Configure CORS on all routes.
        for route in list(self.router.routes()):
            cors.add(route)


if __name__ == '__main__':
    web.run_app(Application(routes),host="0.0.0.0",port=8000)

(2)jsonp方案

客户端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="text" name="username" value="小明">
    <button class="btn">发送数据</button>
    <script>
    var btn = document.querySelector('.btn')
    var user = document.querySelector('input[name="username"]')
    btn.onclick = function(){
        // <script src="http://127.0.0.1:8000/1.js?username=xiaoming&func=callback"><//script>
        let script = document.createElement("script")
        script.src= `http://127.0.0.1:8000/search?username=${user.value}&func=callback`
        document.head.append(script)
    }
    function callback(data){
        console.log(data);
    }

    </script>
</body>
</html>

服务端代码:

import json
from aiohttp import web

routes = web.RouteTableDef()


@routes.get("/search")
async def get_js(request):
    # 接受客户端上传过来的请求体数据,并使用json.loads()转换成字典
    username = request.query.get("username")
    func = request.query.get("func", "callback")
    # 假设根据username到数据库中查询数据
    data = {
        "id": 1,
        "username": username,
        "age": 20
    }

    return web.Response(text=f"{func}({json.dumps(data)})", content_type="text/javascript")


class Application(web.Application):
    def __init__(self, routes):
        """
        :param routes: url和服务端处理程序之间的绑定关系, 这种绑定关系,我们一般称之为路由,当然,在服务端开发中,我们也会经常把具有路由功能功能的类/对象,称之为"路由类"或"路由对象"
        """
        print(routes._items) # 路由列表
        # 先让官方的aiohttp的web框架先进行初始化
        super(Application, self).__init__()
        # 注册路由信息到web框架中,routes是一个list列表
        self.add_routes(routes)

if __name__ == '__main__':
    web.run_app(Application(routes),host="0.0.0.0",port=8000)

(3)总结:

1. 同源策略:浏览器的一种保护用户数据的一种安全机制。
   浏览器会限制脚本语法不能跨源访问其他源的数据地址。
   同源:判断两个通信的地址之间,是否协议,域名[IP],端口一致。
   
   ajax:  http://127.0.0.1/index.html
   api数据接口:  http://localhost/index
   

   这两个是同源么?不是同源的。
   
2. ajax默认情况下会受到同源策略的影响,一旦受到影响会报错误如下:
	 No 'Access-Control-Allow-Origin' header is present on the requested resource

3. 解决ajax只能同源访问数据接口的方式有3种,比较常用的是cors.
   在服务端的响应头中设置:
      Access-Control-Allow-Origin: 允许访问的域名地址