Django PDF下载功能异常:点击链接报404,Content-Disposition未生效
问题:Django+Gunicorn+Nginx环境下PDF下载链接404且Content-Disposition不生效
我使用Django、Gunicorn和Nginx开发功能:用户提交邮箱后,系统发送自定义PDF下载链接,点击链接应触发PDF下载。但目前点击链接持续出现「页面未找到(404)」错误,且Content-Disposition未按预期生效。
相关代码及配置
models.py
from django.db import models import string import random class BrochureToken(models.Model): email = models.EmailField(blank=True) token = models.CharField(max_length=20, unique=True) # 随机token is_used = models.BooleanField(default=False) # 标记token是否已使用 def generate_token(self): length = 20 chars = string.ascii_letters + string.digits self.token = ''.join(random.choice(chars) for _ in range(length)) def save(self, *args, **kwargs): # 保存前如果没有token则生成 if not self.token: self.generate_token() super().save(*args, **kwargs) def __str__(self): return self.email
views.py
class BrochreRequest(generics.CreateAPIView): queryset = BrochureToken.objects.all() serializer_class = BrochureSerializers permission_classes = (permissions.AllowAny,) def post(self, request, format=None): serializer = BrochureSerializers(data=request.data) if serializer.is_valid(): print("Serializer is valid") serializer.save() email = request.data.get("email") token, created = BrochureToken.objects.get_or_create(defaults={'email': email}) download_link = f'https://api.mysite.com/api/filedownload/brochure/{token.token}' send_mail( '下载OIC手册', f'点击链接下载手册:{download_link}', settings.EMAIL_HOST_USER, [email], fail_silently=False ) return Response(serializer.data, status=status.HTTP_200_OK) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def brochure_download_view(request, token): download_token = get_object_or_404(BrochureToken, token=token) if not download_token.is_used: download_token.is_used = True download_token.save() # 设置PDF实际路径 file_path = 'https://api.mysite.com/media/pdfs/oic_brochure.pdf' try: with open(file_path, 'rb') as f: response = FileResponse(f, content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename=oic_brochure.pdf' return response except FileNotFoundError: raise Http404("文件未找到") except SuspiciousFileOperation as e: raise PermissionError(f"打开文件错误:{e}") except Exception as e: # 记录异常用于调试 logger.error(f"文件下载时出现意外错误:{e}") return HttpResponse("内部服务器错误", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
应用级urls.py
from django.urls import path from .views import brochure_download_view, BrochreRequest app_name = 'filedownloads' urlpatterns = [ path('brochure/<str:token>', brochure_download_view, name="brochure"), path('brochure/', BrochreRequest.as_view()), ]
项目级urls.py
urlpatterns = [ # 文件下载 path('api/filedownload/', include('filedownloads.urls')), ]
Nginx配置(针对mysite.com)
server { server_name api.example.com; location = /favicon.ico { access_log off; log_not_found off; } location /statickfiles/ { root /var/www/oicnm-backend; } location /media/ { alias /var/www/api/media/; autoindex off; # 禁止目录浏览 add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires 0; } location / { include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date'; add_header 'Access-Control-Max-Age' 86400; add_header 'Content-Type' 'application/json'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; add_header 'Content-Type' 'application/json'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; } }
问题修复方案
1. 修复URL路径匹配问题
应用级urls.py中,brochure/<str:token>路径缺少末尾斜杠,与生成的下载链接路由规则不匹配。修改应用级urls.py:
urlpatterns = [ path('brochure/<str:token>/', brochure_download_view, name="brochure"), path('brochure/', BrochreRequest.as_view()), ]
同步更新views.py中生成下载链接的代码,确保末尾带斜杠:
download_link = f'https://api.mysite.com/api/filedownload/brochure/{token.token}/'
2. 修复PDF文件读取错误
open()函数只能读取本地文件路径,无法直接读取HTTPS URL。改用本地文件路径或重定向到媒体文件:
方案一:使用本地文件路径
如果PDF存储在本地/var/www/api/media/pdfs/oic_brochure.pdf,修改file_path:
import os from django.conf import settings file_path = os.path.join(settings.MEDIA_ROOT, 'pdfs', 'oic_brochure.pdf')
方案二:重定向到媒体文件并设置下载头
from django.http import HttpResponseRedirect def brochure_download_view(request, token): download_token = get_object_or_404(BrochureToken, token=token) if not download_token.is_used: download_token.is_used = True download_token.save() response = HttpResponseRedirect('/media/pdfs/oic_brochure.pdf') response['Content-Disposition'] = 'attachment; filename=oic_brochure.pdf' return response else: return HttpResponse("链接已失效", status=status.HTTP_410_GONE)
3. 修复Nginx的响应头暴露配置
要让前端获取到Content-Disposition头,需在Nginx的GET请求配置中添加该头:
if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range,Content-Disposition' always; }
4. 修复token创建逻辑
get_or_create调用缺少查询条件,导致无法正确根据邮箱获取或创建token,修改为:
token, created = BrochureToken.objects.get_or_create(email=email)
内容的提问来源于stack exchange,提问作者DevolamiTech
相关产品推荐
相关产品推荐

