eagleye

解决跨域请求中 Content-Disposition 响应头不可见问题

# 解决跨域请求中 Content-Disposition 响应头不可见问题

## 问题背景
在 Django REST Framework (DRF) 后端设置 `Content-Disposition` 响应头后,前端通过 Axios 请求时无法获取该头部信息。手动在视图中设置 `Access-Control-Expose-Headers` 无效,必须在 `django-cors-headers` 配置文件中设置。

## 根本原因
`django-cors-headers` 中间件会覆盖视图中手动设置的 `Access-Control-Expose-Headers`。中间件处理顺序导致视图中的设置被覆盖。

## 解决方案

### 1. 正确配置 django-cors-headers
在 `settings.py` 中添加以下配置:

```python
# settings.py

# 安装应用
INSTALLED_APPS = [
# ...
'corsheaders',
]

# 中间件设置(必须放在顶部)
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 必须放在第一个
'django.middleware.security.SecurityMiddleware',
# ...其他中间件
]

# CORS 配置
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000", # 前端开发地址
"https://your-production-domain.com" # 生产环境地址
]

# 允许的请求方法
CORS_ALLOW_METHODS = [
'GET',
'OPTIONS',
'POST',
'PUT',
'PATCH',
'DELETE',
]

# 暴露给前端的响应头(关键配置)
CORS_EXPOSE_HEADERS = [
'Content-Disposition',
'Content-Length',
'X-Request-ID',
# 其他需要暴露的自定义头部
]

# 是否允许携带凭据(如 cookies)
CORS_ALLOW_CREDENTIALS = True
```

### 2. 后端视图代码(移除无效设置)
在视图文件中,只需设置 `Content-Disposition`,**不要**手动设置 `Access-Control-Expose-Headers`:

```python
# views.py
from django.http import HttpResponse
from django.utils.http import urlquote
from rest_framework.views import APIView
from django.conf import settings

class ExcelExportView(APIView):
def get(self, request):
# 生成文件内容(伪代码)
file_content = generate_excel_content()

# 创建响应对象
response = HttpResponse(
file_content,
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)

# 设置安全的文件名
safe_filename = "审计日志"
encoded_filename = urlquote(f"{safe_filename}.xlsx")

# 设置 Content-Disposition(关键)
response['Content-Disposition'] = (
f'attachment; '
f'filename="{safe_filename}.xlsx"; '
f'filename*=UTF-8\'\'{encoded_filename}'
)

# 调试环境添加缓存控制头
if settings.DEBUG:
response['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
response['X-Content-Type-Options'] = 'nosniff'

return response
```

### 3. 前端 Axios 请求示例
```javascript
import axios from 'axios';

async function downloadExcel() {
try {
const response = await axios.get('/api/export-excel/', {
responseType: 'blob', // 必须设置为 blob
withCredentials: true, // 如果需要携带凭据
});

// 现在可以正确获取 Content-Disposition
const contentDisposition = response.headers['content-disposition'];

// 提取文件名(处理不同浏览器兼容性)
let filename = 'audit_logs.xlsx';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, '');
}
}

// 创建下载链接
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();

// 清理
link.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('下载失败:', error);
}
}
```

## 验证步骤

1. **检查响应头**:
- 在浏览器开发者工具的 Network 标签中查看响应
- 确认响应包含:
```
Access-Control-Expose-Headers: Content-Disposition, Content-Length, X-Request-ID
Content-Disposition: attachment; filename="审计日志.xlsx"; filename*=UTF-8''%E5%AE%A1%E8%AE%A1%E6%97%A5%E5%BF%97.xlsx
```

2. **命令行测试**:
```bash
curl -I -H "Origin: http://localhost:3000" http://your-api/export-excel/
```

3. **Axios 调试**:
```javascript
axios.get('/api/export-excel/').then(response => {
console.log('所有响应头:', response.headers);
console.log('Content-Disposition:', response.headers['content-disposition']);
});
```

## 常见问题排查

1. **中间件顺序问题**:
- 确保 `corsheaders.middleware.CorsMiddleware` 是第一个中间件

2. **预检请求(OPTIONS)处理**:
- 确保 OPTIONS 请求返回正确的 CORS 头
- 检查响应中是否包含 `Access-Control-Allow-Headers`

3. **生产环境配置**:
```python
# settings.py
if not DEBUG:
CORS_ALLOWED_ORIGINS = ["https://your-production-domain.com"]
CSRF_TRUSTED_ORIGINS = ["https://your-production-domain.com"]
```

4. **文件名编码问题**:
- 使用 `urlquote` 替代标准 `urllib.parse.quote`
- 确保格式:`filename*=UTF-8''{encoded_filename}`

5. **Nginx 配置**:
```nginx
location /api/ {
add_header 'Access-Control-Expose-Headers' 'Content-Disposition, Content-Length, X-Request-ID';
proxy_pass http://backend;
# 其他配置...
}
```

## 总结
当使用 `django-cors-headers` 时,所有 CORS 相关头部必须在 `settings.py` 中配置。视图中的手动设置会被中间件覆盖。按照本文档配置后,前端应能正确接收 `Content-Disposition` 响应头,实现文件下载功能。

posted on 2025-07-31 22:27  GoGrid  阅读(41)  评论(0)    收藏  举报

导航