Flutter项目Nginx切换HTTPS后报Unexpected token < JSON解析错误
问题背景
项目后端采用Django、前端采用Flutter开发,HTTP协议下全功能运行正常,将Nginx代理从HTTP切换为HTTPS后,大部分功能可正常使用,仅当数据表无数据、后端需要返回空列表的场景下抛出如下错误:
GET https://servername/project-name/api/commitments/?email=emailaddress%40companyname.de&jahr=2022&kunde=adac-ev 500 FormatException: SyntaxError: Unexpected token < in JSON at position 1
该报错的核心原因是接口未返回合法JSON格式内容,实际返回了以
<开头的HTML内容(通常为Nginx错误页、Django异常页或鉴权跳转页),并非Flutter端JSON解析逻辑故障。
对应Flutter端请求实现代码如下:
var uri = (APIPROTOCOL == 'http://') ? Uri.http(APIHOST, '/api/commitments/', uriQuery) : Uri.https(APIHOST, '$APIHOST_PREFIX/api/commitments/', uriQuery); try { final response = await http.get( uri, headers: {"Authorization": "Bearer $authToken"}, );
该问题在本地localhost环境、HTTP协议服务器环境下均无法复现,仅在HTTPS配置的服务器上触发。
服务器端调试方法
- 直接抓取接口原始响应:跳过Flutter客户端,使用curl或Postman构造和客户端完全一致的请求(携带相同Authorization请求头、查询参数)请求目标HTTPS接口,查看返回的完整响应内容,绝大多数场景下可直接定位返回的是Nginx错误页、Django调试页还是拦截跳转页。curl调试命令参考:
curl -v -H "Authorization: Bearer 实际业务token值" "https://servername/project-name/api/commitments/?email=emailaddress%40companyname.de&jahr=2022&kunde=adac-ev" - 排查Nginx运行日志:Nginx错误日志默认存储路径为
/var/log/nginx/error.log,定位报错时间点对应的日志条目,可直接确认故障原因是代理超时、请求头大小超限、上游服务返回异常还是location路径匹配错误。 - 排查Django服务日志:如果使用uWSGI/Gunicorn托管Django服务,直接查看对应服务的运行日志,重点排查空列表查询场景下的异常栈。HTTPS切换后常见触发原因包括:Django配置中
SECURE_SSL_REDIRECT、CSRF_TRUSTED_ORIGINS未添加HTTPS域名;空列表响应时响应头被Nginx的gzip、内容替换规则篡改;子路径代理规则在空响应场景下匹配错误,返回了站点默认index.html页面(这也是响应内容以<开头的最常见原因)。 - 测试环境临时开启Django DEBUG模式(生产环境禁止长期开启):直接访问目标接口,Django调试页会展示完整异常调用栈,可快速定位是否为空列表序列化逻辑问题——比如序列化器在空值场景下读取了不存在的字段,HTTP环境下因代理未修改响应头未触发分支,HTTPS环境下代理添加HSTS等安全头后触发异常逻辑。
本地HTTPS调试环境搭建方案
可完全模拟线上https://servername/project-name/的访问形式,无需采购公网证书,使用本地自签证书即可:
- 生成本地受信任自签证书:使用本地证书生成工具生成本地域名的SSL证书,初始化本地根证书信任链后,为自定义本地测试域名(可任意命名,比如
local.test,不建议使用localhost)生成证书和私钥文件。 - 修改本地hosts映射:将自定义测试域名指向本地回环地址127.0.0.1,Windows系统hosts路径为
C:\Windows\System32\drivers\etc\hosts,Mac/Linux系统路径为/etc/hosts,添加如下映射规则:127.0.0.1 local.test - 配置本地Nginx反向代理:保持和线上Nginx配置逻辑一致,配置HTTPS监听端口、证书路径、子路径代理规则,将
/project-name/路径的请求转发到本地运行的Django服务(比如本地Django默认运行在8000端口),核心配置参考:server { listen 443 ssl; server_name local.test; # 替换为本地生成的证书实际存储路径 ssl_certificate /path/to/local.test.pem; ssl_certificate_key /path/to/local.test-key.pem; location /project-name/ { # 转发到本地Django服务,路径重写规则和线上保持一致 proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 以上三个请求头是Django正确识别HTTPS协议的关键配置,缺失会导致Django生成的内部链接仍为HTTP协议 } } - 调整Django本地配置:将测试域名
local.test添加到ALLOWED_HOSTS列表,若开启了SSL相关安全配置,需将https://local.test添加到CSRF_TRUSTED_ORIGINS列表,正常启动Django服务即可。 - 调整Flutter本地调试配置:将
APIPROTOCOL参数设为https://,APIHOST参数设为local.test,APIHOST_PREFIX参数设为/project-name,即可和线上环境保持同协议、同路径调试。如果使用安卓模拟器调试,需将自签证书添加到安卓系统信任证书列表,调试阶段也可临时关闭HTTP客户端的证书校验(仅可用于本地调试,上线前必须恢复校验逻辑)。 - 轻量调试方案:如果不需要模拟Nginx代理规则,可直接为Django安装SSL服务扩展,使用Django自带的HTTPS启动命令加载自签证书运行服务,配合hosts映射即可快速搭建HTTPS调试环境,适合快速排查Django本身的逻辑问题。
内容的提问来源于stack exchange,提问作者Davood
相关产品推荐
相关产品推荐

