如何在Nginx中对匹配到的路径段进行URL编码?
Nginx实现路径参数URL编码后反向代理的方案
问题描述
我有一个用于获取项目内文件所有者的API端点,需要将请求URL中的项目路径和文件名部分做URL编码后,反向代理到后端服务器。期望的配置逻辑和请求示例如下:
目标配置逻辑
location ~ /api/project_path/(.*)/file/(.*)/owner { # 将$1编码为$encoded_projectpath # 将$2编码为$encoded_file proxy_pass http://backend/$encoded_projectpath/$encoded_file/owner; }
请求示例
curl http://localhost/api/project_path/my/project/path/file/my/file/name/owner # 期望Nginx反向代理到:http://backend/my%2Fproject%2Fpath/my%2Ffile%2Fname/owner
请问Nginx是否有内置方法实现该需求?我自己尝试了njs方案,但该方案无法扩展到其他端点,代码如下:
现有njs脚本(http.js)
function urlencode_projectpath(r) { let pattern = "(?<=/api/project_path/)(.*)/file/(.*)/upload_url" let url = r.variables.request_uri let project_path = url.match(pattern)[0] return encodeURIComponent(project_path) } function urlencode_file(r) { let pattern = "(?<=/api/project_path/)(.*)/file/(.*)/upload_url" let url = r.variables.request_uri let file= url.match(pattern)[1] return encodeURIComponent(file) } export default { urlencode_projectpath, urlencode_file }
现有Nginx配置片段
http { js_import /etc/nginx/conf.d/http.js; js_set $encoded_projectpath http.urlencode_projectpath; js_set $encoded_file http.urlencode_file; server { location ~ /api/project_path/(.*)/file/(.*)/owner { proxy_pass http://backend/$encoded_projectpath/$encoded_file/owner; } } }
解决方案
一、第三方模块实现(依赖ngx_set_misc)
Nginx官方核心模块没有内置的URL编码函数,但可以通过第三方模块ngx_set_misc提供的set_escape_uri指令快速实现。
配置示例
location ~ /api/project_path/(.*)/file/(.*)/owner { # 对捕获的分组$1、$2分别做URL编码 set_escape_uri $encoded_projectpath $1; set_escape_uri $encoded_file $2; proxy_pass http://backend/$encoded_projectpath/$encoded_file/owner; }
注意:需要先安装
ngx_set_misc模块(可通过OpenResty直接获取,或自行编译到Nginx)。
二、可扩展的njs实现方案
如果不想依赖第三方模块,可以改进njs脚本,使其支持通用URL编码,并轻松扩展到其他端点。
方案1:通用编码函数+Nginx变量传递
这种方式无需修改njs脚本即可扩展到任意端点:
改进后的njs脚本(http.js)
// 通用URL编码函数,接收Nginx传递的待编码内容 function urlEncode(r) { const input = r.variables.input_to_encode; return input ? encodeURIComponent(input) : ''; } export default { urlEncode };
对应的Nginx配置
http { js_import /etc/nginx/conf.d/http.js; # 定义通用编码变量 js_set $encoded_input http.urlEncode; server { # 文件所有者端点 location ~ /api/project_path/(.*)/file/(.*)/owner { # 传递$1给编码函数,得到编码后的项目路径 set $input_to_encode $1; set $encoded_projectpath $encoded_input; # 传递$2给编码函数,得到编码后的文件名 set $input_to_encode $2; set $encoded_file $encoded_input; proxy_pass http://backend/$encoded_projectpath/$encoded_file/owner; } # 扩展:新增upload_url端点 location ~ /api/project_path/(.*)/file/(.*)/upload_url { set $input_to_encode $1; set $encoded_projectpath $encoded_input; set $input_to_encode $2; set $encoded_file $encoded_input; proxy_pass http://backend/$encoded_projectpath/$encoded_file/upload_url; } # 扩展:其他任意结构的端点 location ~ /api/another/(.*)/resource/(.*)/info { set $input_to_encode $1; set $encoded_path $encoded_input; set $input_to_encode $2; set $encoded_resource $encoded_input; proxy_pass http://backend/$encoded_path/$encoded_resource/info; } } }
方案2:端点专属提取函数(适合固定结构的API)
如果你的API端点结构固定,可以在njs中编写对应提取逻辑,避免重复配置:
改进后的njs脚本(http.js)
// 提取并编码项目路径(匹配owner端点) function encodeOwnerProjectPath(r) { const match = r.variables.request_uri.match(/^\/api\/project_path\/(.*)\/file\/.*\/owner$/); return match ? encodeURIComponent(match[1]) : ''; } // 提取并编码文件名(匹配owner端点) function encodeOwnerFileName(r) { const match = r.variables.request_uri.match(/^\/api\/project_path\/.*\/file\/(.*)\/owner$/); return match ? encodeURIComponent(match[1]) : ''; } // 扩展:提取并编码upload_url端点的参数 function encodeUploadProjectPath(r) { const match = r.variables.request_uri.match(/^\/api\/project_path\/(.*)\/file\/.*\/upload_url$/); return match ? encodeURIComponent(match[1]) : ''; } function encodeUploadFileName(r) { const match = r.variables.request_uri.match(/^\/api\/project_path\/.*\/file\/(.*)\/upload_url$/); return match ? encodeURIComponent(match[1]) : ''; } export default { encodeOwnerProjectPath, encodeOwnerFileName, encodeUploadProjectPath, encodeUploadFileName };
对应的Nginx配置
http { js_import /etc/nginx/conf.d/http.js; # 为每个端点的参数定义编码变量 js_set $encoded_owner_project http.encodeOwnerProjectPath; js_set $encoded_owner_file http.encodeOwnerFileName; js_set $encoded_upload_project http.encodeUploadProjectPath; js_set $encoded_upload_file http.encodeUploadFileName; server { location ~ /api/project_path/(.*)/file/(.*)/owner { proxy_pass http://backend/$encoded_owner_project/$encoded_owner_file/owner; } location ~ /api/project_path/(.*)/file/(.*)/upload_url { proxy_pass http://backend/$encoded_upload_project/$encoded_upload_file/upload_url; } } }
内容的提问来源于stack exchange,提问作者Chiamin
相关产品推荐
相关产品推荐

