request.build_absolute_uri()为什么没有获得端口?

Django 没有正确识别请求中的端口号 ,通常是因为 反向代理(如 Nginx、Apache)或负载均衡器 在转发请求时 没有传递原始的 Host,或者 Django 没有正确读取它。


🔍 根本原因分析

虽然你浏览器访问的是:

复制代码
http://39.105.138.65:8080/qr-code/generate/

但你的 Django 应用实际监听的可能是 127.0.0.1:8000 ,而前面有一个 Nginx / Apache / 云服务器安全组 / 容器端口映射8080 转发到了内部端口(比如 8000)。

在转发过程中:

  • 如果 没有设置 proxy_set_header Host $host;
  • 或者 Django 没有启用 USE_X_FORWARDED_HOST

那么 Django 收到的 Host 头就可能是:

复制代码
Host: 39.105.138.65   # ❌ 没有 :8080

而不是:

复制代码
Host: 39.105.138.65:8080   # ✅ 正确

于是 request.build_absolute_uri() 就生成了不带端口的 URL。


✅ 解决方案

第一步:确认是否用了反向代理(如 Nginx)

如果你用了 Nginx,请检查配置是否包含:

复制代码
server {
    listen 8080;

    location / {
        proxy_pass http://127.0.0.1:8000;  # 假设 Django 跑在 8000
        proxy_set_header Host $host;           # 👈 必须有!
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

🔑 关键是 proxy_set_header Host $host;
$host 会保留原始请求的 Host(包括端口,如果有的话)

⚠️ 注意:$host 不包含端口!

要保留端口,应该用 $http_host

复制代码
proxy_set_header Host $http_host;  # ✅ 包含端口(如 39.105.138.65:8080)
  • $host = 域名或 IP(无端口)
  • $http_host = 客户端请求头中的完整 Host(含端口)

✅ 所以正确配置是:

复制代码
proxy_set_header Host $http_host;

第二步:在 Django settings.py 中启用代理支持
复制代码
# settings.py

# 允许从 X-Forwarded-Host 获取主机名
USE_X_FORWARDED_HOST = True

# 如果是 HTTPS(这里你是 HTTP,可忽略)
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

这样 Django 才会信任来自代理的 Host 头。


第三步:验证请求头(调试用)

在你的视图中临时加一段代码,打印请求头:

复制代码
def generate_qr_code(request, student):
    print("=== Request META ===")
    print("HTTP_HOST:", request.META.get('HTTP_HOST'))
    print("SERVER_NAME:", request.META.get('SERVER_NAME'))
    print("SERVER_PORT:", request.META.get('SERVER_PORT'))
    print("X-Forwarded-Host:", request.META.get('HTTP_X_FORWARDED_HOST'))
    print("=====================")
    # ... 继续原有逻辑

访问页面后看日志:

  • 如果 HTTP_HOST39.105.138.65(无端口)→ 说明代理没传端口
  • 如果是 39.105.138.65:8080 → 说明 Django 配置有问题

🚫 如果你没用 Nginx,而是直接 python manage.py runserver 0.0.0.0:8080

那问题更奇怪,因为 runserver 会直接读取 Host 头。

但请注意:

  • 浏览器在访问 标准端口(HTTP 80 / HTTPS 443)时会省略端口
  • 但你用的是 8080(非标准),所以浏览器一定会发送 Host: 39.105.138.65:8080

此时 request.get_host() 应该返回带端口的值。

你可以测试:

复制代码
# 在视图中
print(request.get_host())  # 应该输出 "39.105.138.65:8080"

如果不是,可能是:

  • 云服务器安全组 / 防火墙做了端口转发(如 8080 → 80)
  • Docker 端口映射未正确传递 Host 头

✅ 临时 workaround(不推荐长期使用)

如果你无法改 Nginx 或服务器配置,可以手动拼接端口(仅限固定端口场景):

复制代码
from urllib.parse import urljoin

# 获取基础 URL(不含端口)
base = request.build_absolute_uri('/').rstrip('/')
# 强制加上 :8080
if not base.endswith(':8080'):
    base = base + ':8080'

relative_url = reverse('student_volunteer_erlist', kwargs={'student_id': student.id})
qr_data = urljoin(base + '/', relative_url.lstrip('/'))

但这是"打补丁",根本解决还是靠正确配置代理


✅ 最终建议

  1. 检查是否有 Nginx/Apache → 有就加 proxy_set_header Host $http_host;
  2. Django 设置 USE_X_FORWARDED_HOST = True
  3. 重启服务生效

这样 request.build_absolute_uri() 就能正确生成:

复制代码
http://39.105.138.65:8080/student-volunteer-er/6/?token=xxx

而不是丢失端口的版本。

相关推荐
小北方城市网2 小时前
第 5 课:后端工程化进阶 ——Python 分层架构 + 中间件 + 日志 / 异常统一处理(打造企业级高可用后端)
数据库·人工智能·python·mysql·数据库架构
山山而川 潺潺如镜2 小时前
python防止程序多开,但程序运行脚本
android·开发语言·python
大千AI助手2 小时前
HiveOperator 中 hql 模板路径解析失败的原因分析
hive·python·任务调度·airflow·模版·大千ai助手·hiveoperator
小北方城市网2 小时前
第 4 课:前端工程化进阶 ——Vue 核心语法 + 组件化开发(前端能力质的飞跃)
大数据·开发语言·数据库·python·状态模式·数据库架构
㳺三才人子2 小时前
初探 Python + Django
开发语言·python·django
寻星探路2 小时前
网络原理全景图:从通信起源到 TCP/IP 体系架构深度拆解
java·网络·c++·python·tcp/ip·http·架构
子一!!2 小时前
MySQL==表的结构操作1
android·python·adb
清水白石0082 小时前
动态规划中的记忆化与缓存:原理、差异与 Python 实战指南
python·缓存·动态规划
无垠的广袤2 小时前
【上海晶珩睿莓 1 单板计算机】物联网环境监测终端
linux·python·嵌入式硬件·物联网·mqtt·home assistant