本文基于一个实际的项目需求,做下记录并分享。
在 Web 开发中,通过 Nginx 代理转发下载文件是一个常见需求。特别是当目标文件包含中文文件名时,经常会遇到两个棘手的问题:
- 浏览器下载时文件名丢失,变成了代理接口的名称(如
changeprotocol)。 - 中文文件名在转发过程中出现乱码,导致下载失败或文件名显示错误。
本文将重点介绍如何配置 Nginx,在代理转发场景下,完美解决中文文件下载及文件名保留的问题。
场景描述
- 文件源服务器 (Port 9301):存放静态资源,重点是包含中文文件名的文件(如
中国.png)。 - 代理转发服务器 (Port 9302):作为中转站,接收客户端请求,解析参数中的真实地址,将请求转发给源服务器,并将文件流返回给用户。
- 目标:用户访问代理地址时,浏览器弹出的下载框中显示的是原始文件名(如
中国.png)。
访问地址示例:http://10.86.37.169:9302/changeprotocol?changeprotocol=http://10.86.37.169:9301/中国.png
核心问题
在代理转发过程中,Nginx 默认只是透传数据流,不会自动处理Content-Disposition头。
- 代理导致文件名丢失:浏览器看到的 URL 是代理服务器的地址(如
/changeprotocol),而不是真实文件的路径,因此默认使用接口名作为文件名。 - 中文编码陷阱:中文文件名在 URL 中通常被编码(如
%E4%B8%AD...),直接转发可能导致源服务器无法识别路径,或者浏览器下载后文件名乱码。
解决方案
为了解决上述问题,我们需要在代理服务器的 Nginx 配置中进行干预:
- 解析真实地址:从请求参数中获取目标文件的 URL。
- 提取中文文件名:使用正则表达式从 URL 中提取出原始文件名。
- 重写响应头:通过
add_header强制设置Content-Disposition,明确告诉浏览器“这是一个附件,请使用提取出的中文文件名保存”。
Nginx 配置示例
以下是完整的nginx.conf配置片段:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # ---------------------------------------------------- # 1. 文件源服务器 (模拟后端存储) # ---------------------------------------------------- server { listen 9301; server_name 10.86.37.169; charset utf-8; # 关键:设置字符集,防止中文文件名读取失败 location / { # 确保此目录下存在 "中国.png" 等文件 root D:/nginx-1.17.1/www; index index.html index.htm; } } # ---------------------------------------------------- # 2. 代理下载服务器 # ---------------------------------------------------- server { listen 9302; server_name 10.86.37.169; # 精确匹配 /changeprotocol 路径 location = /changeprotocol { # 必须指定 resolver 以便解析域名(如果是 IP 可忽略,但建议加上) resolver 180.76.76.76; # 1. 从 query 参数中获取真实 URL set $stream_url $arg_changeprotocol; # 安全检查:如果参数为空,返回 400 if ($stream_url = "") { return 400; } # 2. 使用正则提取文件名(支持中文) # 逻辑:匹配最后一个 / 之后,且不包含 ? 的字符序列 if ($stream_url ~* ".*/([^/?]+)$") { set $filename $1; # 3. 关键步骤:添加 Content-Disposition 头 # 强制浏览器使用提取出的原始文件名(包含中文)进行下载 add_header Content-Disposition 'attachment; filename="$filename"'; } # 清除可能影响代理的头信息 proxy_set_header Cookie ""; proxy_set_header Host ""; # 执行代理 proxy_pass $stream_url; # 设置字符集和超时 charset utf-8; proxy_connect_timeout 60s; proxy_read_timeout 600s; proxy_send_timeout 600s; } } }关键点解析
set $stream_url $arg_changeprotocol;
Nginx 自动将 URL 参数?changeprotocol=xxx映射为变量$arg_changeprotocol。正则提取文件名:
if ($stream_url ~* ".*/([^/?]+)$")~*: 不区分大小写匹配。.*/: 匹配最后一个斜杠之前的所有内容。([^/?]+): 捕获组,匹配斜杠之后、问号(如果有参数)之前的内容。这就是我们需要的文件名。
Content-Disposition:add_header Content-Disposition 'attachment; filename="$filename"';这是告诉浏览器“这是一个附件,请保存为
$filename”的标准 HTTP 头。常见错误排查 (CreateFile failed):
如果在日志中看到CreateFile() ... failed (2: The system cannot find the file specified),通常是因为:- 路径错误:9301 端口的
root路径下确实没有该文件。 - 编码问题:请求的文件名是 URL 编码的(如
%E4%B8%AD%E5%9B%BD.png),而文件系统是中文的。确保 Nginx 配置了charset utf-8;,并且文件确实存在于磁盘上。
- 路径错误:9301 端口的
通过以上配置,即可完美解决 Nginx 代理下载时的中文文件名保留问题。