66. Django解决跨域问题

2022-01-17 13:57:48 浏览数 (1)

前言

在业务开发的过程中,一般存在旧项目是使用Django模板开发的,这种并未前后端分离,这时候新来了一些需求,需要后面的app模块 具备 前后端分离跨域API请求

这是一种比较特殊的情况,当然还有另外一种就是一开始就是按照前后端分离的架构开发的项目,那么对这种项目处理跨域请求当然就比较简单。

下面对这两个情况,进行逐个解决。

针对旧项目Django模块开发,解决部分API请求的跨域问题

解决的思路

对于这种情况,较好的方式就是自己手写一个视图类,用来忽略csrf token的认证。对于跨域请求处理,则设置一下response响应信息即可。

首先演示一下存在的跨域问题

1.准备好一个视图函数处理post请求
代码语言:javascript复制
# ex: /assetinfo/test_ajax
class TestAjax(View):

    def post(self,request):
        """接收处理ajax的post请求"""

        # 和前端约定的返回格式
        result =  {"resCode": '0', "message": 'success',"data": []}
        # 查询服务器信息
        servers = ServerInfo.objects.all()

        # 将model对象逐个转为dict字典,然后设置到data的list中
        for server in servers:
            server = model_to_dict(server) # model对象转dict字典
            server['server_used_type_id'] = serializers.serialize('python', server['server_used_type_id']) # 外键模型对象需要序列化,或者去除不传递
            print("server = ",server)
            result["data"].append(server)

        return JsonResponse(result)
2.使用postman测试接口是否正常

image-20200319113045159

可以看到在postman是没有跨域的情况的。那么下面写一个简单的网页示例来测试一下。

3.准备一个网页请求示例
代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
		<!--  1.导入vue.js库  -->
		<script src="lib/vue.js"></script>
		<!-- 2.导入axios库 -->
		<script src="lib/axios/axios.min.js"></script>

	</head>
	<body>

		<div id="app">
			<input type="button" value="post请求form" @click="postInfoForm">
		</div>

		<script>
			
			// 配置axios全局url
			axios.defaults.baseURL = 'http://127.0.0.1:8000';

			// 2. 创建一个Vue的实例
			var vm = new Vue({
				el: '#app',
				data: {
				},
				methods: {
					postInfoForm() { // 发起 post 请求   application/x-wwww-form-urlencoded

						// 发送 POST 请求
						axios({
								method: 'post',
								url: '/assetinfo/test_ajax',
								data: {
								},
								// 设置 transformRequest 和 headers 是兼容发送form表单格式的请求
								transformRequest: [function(data) {
									// Do whatever you want to transform the data
									let ret = '';
									for (let it in data) {
										ret  = encodeURIComponent(it)   '='   encodeURIComponent(data[it])   '&'
									}
									return ret
								}],
								headers: {
									'Content-Type': 'application/x-www-form-urlencoded',
								}
							})
							.then(function(response) {
								console.log(response);
							})
							.catch(function(error) {
								console.log(error);
							});
					},

				},

			});
		</script>

	</body>
</html>

使用浏览器执行一下请求测试,如下:

image-20200319113252156

可以看到此时使用网页端进行请求是提示跨域错误信息的。

设置视图返回的reponse信息允许跨域

其实允许跨域跨域只需要设置一下响应信息即可,如下:

1.设置视图的响应reponse允许跨域
代码语言:javascript复制
# ex: /assetinfo/test_ajax
class TestAjax(View):

    def post(self,request):
        """接收处理ajax的post请求"""

        # 和前端约定的返回格式
        result =  {"resCode": '0', "message": 'success',"data": []}
        # 查询服务器信息
        servers = ServerInfo.objects.all()

        # 将model对象逐个转为dict字典,然后设置到data的list中
        for server in servers:
            server = model_to_dict(server) # model对象转dict字典
            server['server_used_type_id'] = serializers.serialize('python', server['server_used_type_id']) # 外键模型对象需要序列化,或者去除不传递
            print("server = ",server)
            result["data"].append(server)

        # 处理跨域
        response = HttpResponse(json.dumps(result))
        response["Content-Type"] = "application/json" # 响应信息的内容格式
        response["Access-Control-Allow-Origin"] = "*"  # 允许跨域请求的源地址, * 表示:允许所有地址
        response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS" # 允许跨域请求的具体方法
        response["Access-Control-Max-Age"] = "1000" # 用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求。
        response["Access-Control-Allow-Headers"] = "*"

        return response

处理跨域的代码:

代码语言:javascript复制
        # 处理跨域
        response = HttpResponse(json.dumps(result))
        response["Content-Type"] = "application/json" # 响应信息的内容格式
        response["Access-Control-Allow-Origin"] = "*"  # 允许跨域请求的源地址, * 表示:允许所有地址
        response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS" # 允许跨域请求的具体方法
        response["Access-Control-Max-Age"] = "1000" # 用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求。
        response["Access-Control-Allow-Headers"] = "*"

        return response

再从网页端测试一下请求如下:

image-20200319141013579

可以看到已经允许跨域请求了。

但是如果每个视图如果都要写这么几行reponse构建代码才能返回一个跨域的json响应数据,其实挺麻烦的。所以,我会将其封装成为一个通用的方法。

2.封装跨域 json 响应为一个通用的方法
代码语言:javascript复制
from django.http import HttpResponse
import json

# 配置跨域视图类处理
def CrossDomainJsonResponse(result):
    response = HttpResponse(json.dumps(result))
    response["Content-Type"] = "application/json"  # 响应信息的内容格式
    response["Access-Control-Allow-Origin"] = "*"  # 允许跨域请求的源地址, * 表示:允许所有地址
    response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"  # 允许跨域请求的具体方法
    response["Access-Control-Max-Age"] = "1000"  # 用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求。
    response["Access-Control-Allow-Headers"] = "*"

    return response

然后只需要导入这个方法,就可以返回跨域的 json 响应了。

示例使用如下:

image-20200319141943411

使用 django-cors-headers 处理所有API请求的跨域问题

django-cors-headers Github

  • https://github.com/adamchainz/django-cors-headers

首先演示一下存在的跨域问题

1.准备好一个视图函数处理 post put delete 请求
代码语言:javascript复制
# ex: /assetinfo/test_ajax
class TestAjax(View):

    def post(self, request):
        """接收处理ajax的post请求"""

        # 和前端约定的返回格式
        result = {"resCode": '0', "message": 'success', "data": []}
        # 查询服务器信息
        servers = ServerInfo.objects.all()

        # 将model对象逐个转为dict字典,然后设置到data的list中
        for server in servers:
            server = model_to_dict(server)  # model对象转dict字典
            server['server_used_type_id'] = serializers.serialize('python',
                                                                  server['server_used_type_id'])  # 外键模型对象需要序列化,或者去除不传递
            print("server = ", server)
            result["data"].append(server)

        return JsonResponse(result)
    
    
    def put(self, request):
        """处理 put 请求"""

        # 和前端约定的返回格式
        result = {"resCode": '0', "message": 'success', "data": []}

        result["data"] = "put 请求"
        return JsonResponse(result)

    
    def delete(self, request):
        """处理 delete 请求"""

        # 和前端约定的返回格式
        result = {"resCode": '0', "message": 'success', "data": []}

        result["data"] = "delete 请求"
        return JsonResponse(result)
2.使用postman测试接口是否正常
测试post请求:

image-20200826142031939

测试put请求:

image-20200826142050803

测试 delete 请求:

image-20200826142123975

3.准备一个网页请求示例
代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--  1.导入vue.js库  -->
    <script src="lib/vue.js"></script>
    <!-- 2.导入axios库 -->
    <script src="lib/axios/axios.min.js"></script>

</head>
<body>

<div id="app">
    <input type="button" value="post请求form" @click="postInfoForm">
    <input type="button" value="put请求" @click="putRequest">
    <input type="button" value="delete请求" @click="deleteRequest">
</div>

<script>

    // 配置axios全局url
    axios.defaults.baseURL = 'http://127.0.0.1:8000';

    // 2. 创建一个Vue的实例
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {
            postInfoForm() { // 发起 post 请求   application/x-wwww-form-urlencoded

                // 发送 POST 请求
                axios({
                    method: 'post',
                    url: '/assetinfo/test_ajax',
                    data: {},
                    // 设置 transformRequest 和 headers 是兼容发送form表单格式的请求
                    transformRequest: [function (data) {
                        // Do whatever you want to transform the data
                        let ret = '';
                        for (let it in data) {
                            ret  = encodeURIComponent(it)   '='   encodeURIComponent(data[it])   '&'
                        }
                        return ret
                    }],
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    }
                })
                    .then(function (response) {
                        console.log(response);
                    })
                    .catch(function (error) {
                        console.log(error);
                    });
            },
            putRequest() {

                // 发送 PUT 请求
                axios.put('/assetinfo/test_ajax', {})
                    .then(response => {
                        console.log(response)
                    })
                    .catch(error => {
                        console.log(error)
                    })

            },
            deleteRequest() {

				// 发送 DELETE 请求
				axios.delete('/assetinfo/test_ajax', {})
						.then(response => {
							console.log(response)
						})
						.catch(error => {
							console.log(error)
						})
            	
            },

        },

    });
</script>

</body>
</html>

执行请求存在跨域问题,如下:

image-20200826160147632

安装 django-cors-headers 允许跨域

1.安装django-cors-headers
代码语言:javascript复制
pip install django-cors-headers
# django-cors-headers 3.5.0 has requirement Django>=2.2
# 因为至少需要Django 2.2
pip install django==2.2

# 为了保持稳定,我的项目还是使用 django==2.1.7 的版本,那么可以使用 django-cors-headers==3.4.0的版本
pip install django-cors-headers==3.4.0
pip install django==2.1.7

注意:安装这个库目前最新版本还会将 Django 自动更新到 Django 3.1 ,如果是 Django 2.1.x 的,最好指定一下版本安装。

2.配置settings.py文件
2.1 在应用设置 INSTALLED_APPS 里添加 "corsheaders"
代码语言:javascript复制
INSTALLED_APPS = [
    ...
    'corsheaders', # 跨域处理
    ...
]
2.2 在中间件 MIDDLEWARE_CLASSES 添加配置:
代码语言:javascript复制
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # 尽量放到最高的位置,在设置最早处理响应
    'django.middleware.common.CommonMiddleware', # Django框架已有的通用中间件
    ...
]

说明:

CorsMiddleware应该放置在尽可能高的位置,尤其是在可以生成响应的任何中间件之前。例如:放在 django 框架的默认中间件django.middleware.common.CommonMiddleware之前。

2.3 跨域中间件的行为设置
代码语言:javascript复制
CORS_ALLOW_CREDENTIALS = True # 如果为True,则将允许将cookie包含在跨站点HTTP请求中。默认为False。
CORS_ORIGIN_ALLOW_ALL = True # 如果为True,则将允许所有来源。限制允许的原点的其他设置将被忽略。默认为False。
CORS_ORIGIN_WHITELIST = () # 授权进行跨站点HTTP请求的来源列表。

# 请求所允许的HTTP动词列表。
CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
)

# 发出请求时可以使用的非标准HTTP标头的列表。
CORS_ALLOW_HEADERS = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
)

更多配置参考Github:https://github.com/adamchainz/django-cors-headers

3.启动服务,再次执行测试

image-20200826160302320

再次执行就没有跨域问题了,可以正常请求了。

0 人点赞