37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现
wmproxy
wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
nginx中的try_files
- 语法:
try_files file … uri;或try_files file … = code; - 作用域:server location
- 首先:按照指定的顺序检查文件是否存在,并使用第一个找到的文件进行请求处理
- 其次:处理是在当前上下文中执行的。根据 root 和 alias 指令从 file 参数构造文件路径。
- 然后:可以通过在名称末尾指定一个斜杠来检查目录的存在,例如
"$uri/"。 - 最后:如果没有找到任何文件,则进行内部重定向到最后一个参数中指定的 uri。
注:只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部的 URL 的指向。最后一个参数是回退 URL 且必须存在,否则会出现内部 500 错误。命名的 location 也可以使用在最后一个参数中。
应用场景
1、前端路由处理:
location / {
try_files $uri $uri/ /index.html;
# $uri指请求的uri路径,$uri/表示请求的uri路径加上一个/,例如访问example.com/path,则会依次尝试访问/path,/path/index.html,/index.html
# /index.html表示如果仍未匹配到则重定向到index.html
}
这种场景多用于单页应用,例如vue.js等前端框架的路由管理。当用户在浏览器中访问一个非根路径的路径时,由于这些路径都是由前端路由管理的,nginx无法直接返回正确的静态文件,因此需要将请求重定向到统一的路径,这里是/index.html,由前端路由控制页面的展示。
2、图片服务器:
location /images/ {
root /data/www;
error_page 404 = /fetch_image.php;
try_files $uri $uri/ =404;
}
location /fetch_image.php {
fastcgi_pass 127.0.0.1:9000;
set $path_info "";
fastcgi_param PATH_INFO $path_info;
fastcgi_param SCRIPT_FILENAME /scripts/fetch_image.php;
include fastcgi_params;
}
这种场景多用于图片服务器,当用户访问图片时,先尝试在本地文件系统中查找是否有该文件,如果找到就返回;如果没有找到则会转发到fetch_image.php进行处理,从远程资源服务器拉取图片并返回给用户。
实现方案
当前nginx方案的实现,是基于文件的重试,也就是所谓的伪静态,如果跨目录的服务器就很麻烦了,比如:
location /images/ {
root /data/upload;
try_files $uri $uri/ =404;
}
location /images2/ {
root /data/www;
try_files $uri $uri/ =404;
}
上面的我们无法同时索引两个目录下的结构。即我假设我访问/images/logo.png无法同时查找/data/upload/logo.png及/data/www/logo.png是否存在。
当前实现方案从
try_files变成try_paths也就是当碰到该选项时,将当前的几个访问地址重新进入路由
例:
[[http.server.location]]
rate_limit = "4m/s"
rule = "/root/logo.png"
file_server = { browse = true }
proxy_pass = ""
try_paths = "/data/upload/logo.png /data/www/logo.png /root/README.md"
[[http.server.location]]
rule = "/data/upload"
file_server = { browse = true }
[[http.server.location]]
rule = "/data/www"
file_server = { browse = true }
除非碰到返回100或者200状态码的,否则将执行到最后一个匹配路由。
源码实现
- 要能循环遍历路由
- 当try_paths时要避免递归死循环
- 当try_paths时可能会调用自己本身,需要能重复调用
以下主要源码均在reverse/http.rs
- 实现循环
主要的处理函数为deal_match_location,函数的参数为
#[async_recursion]
async fn deal_match_location(
req: &mut Request<Body>,
// 缓存客户端请求
cache: &mut HashMap<
LocationConfig,
(Sender<Request<Body>>, Receiver<ProtResult<Response<Body>>>),
>,
// 该Server的配置选项
server: Arc<ServerConfig>,
// 已处理的匹配路由
deals: &mut HashSet<usize>,
// 已处理的TryPath匹配路由
try_deals: &mut HashSet<usize>,
) -> ProtResult<Response<Body>>
当前在Rust中的异步递归会报如下错误
recursion in an `async fn` requires boxing
a recursive `async fn` must be rewritten to return a boxed `dyn Future`
consider using the `async_recursion` crate: https://crates.io/crates/async_recursion
所以需要添加#[async_recursion]或者改成Box返回。
参数其中多定义了两组HashSet用来存储已处理的路由及已处理的TryPath路由。
将循环获取合适的location,如果未找到直接返回503错误。
let path = req.path().clone();
let mut l = None;
let mut now = usize::MAX;
for idx in 0..server.location.len() {
if deals.contains(&idx) {
continue;
}
if server.location[idx].is_match_rule(&path, req.method()) {
l = Some(&server.location[idx]);
now = idx;
break;
}
}
if l.is_none() {
return Ok(Response::status503()
.body("unknow location to deal")
.unwrap()
.into_type());
}
当该路由存在try_paths的情况时:
// 判定该try是否处理过, 防止死循环
if !try_deals.contains(&now) && l.try_paths.is_some() {
let try_paths = l.try_paths.as_ref().unwrap();
try_deals.insert(now);
let ori_path = req.path().clone();
for val in try_paths.list.iter() {
deals.clear();
// 重写path好方便做数据格式化
req.set_path(ori_path.clone());
let new_path = Helper::format_req(req, &**val);
// 重写path好方便后续处理无感
req.set_path(new_path);
if let Ok(res) = Self::deal_match_location(
req,
cache,
server.clone(),
deals,
try_deals,
)
.await
{
if !res.status().is_client_error() && !res.status().is_server_error() {
return Ok(res);
}
}
}
return Ok(Response::builder()
.status(try_paths.fail_status)
.body("not valid to try")
.unwrap()
.into_type());
}
其中会将req中的path进行格式化的重写以方便处理:
// 重写path好方便做数据格式化
req.set_path(ori_path.clone());
let new_path = Helper::format_req(req, &**val);
// 重写path好方便后续处理无感
req.set_path(new_path);
如果不存在try_paths将正常的按照路由的处理逻辑,该文件服务器或者反向代理,并标记该路由已处理。
deals.insert(now);
let clone = l.clone_only_hash();
if cache.contains_key(&clone) {
let mut cache_client = cache.remove(&clone).unwrap();
if !cache_client.0.is_closed() {
println!("do req data by cache");
let _send = cache_client.0.send(req.replace_clone(Body::empty())).await;
match cache_client.1.recv().await {
Some(res) => {
if res.is_ok() {
log::trace!("cache client receive response");
cache.insert(clone, cache_client);
}
return res;
}
None => {
log::trace!("cache client close response");
return Ok(Response::status503()
.body("already lose connection")
.unwrap()
.into_type());
}
}
}
} else {
log::trace!("do req data by new");
let (res, sender, receiver) = l.deal_request(req).await?;
if sender.is_some() && receiver.is_some() {
cache.insert(clone, (sender.unwrap(), receiver.unwrap()));
}
return Ok(res);
}
小结
try_files在nginx中提供了更多的可能,也方便了伪静态文件服务器的处理。我们在其中的基础上稍微改造成try_paths来适应处理提供多路由映射的可能性。

点击 [关注],[在看],[点赞] 是对作者最大的支持
37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现的更多相关文章
- 【转】Spring Boot干货系列:(一)优雅的入门篇
转自Spring Boot干货系列:(一)优雅的入门篇 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做 ...
- Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用
Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用 原创 2017-04-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章介绍了一些基础,但都是静 ...
- Spring Boot干货系列:(七)默认日志框架配置
Spring Boot干货系列:(七)默认日志框架配置 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候, ...
- Spring Boot干货系列:(五)开发Web应用JSP篇
Spring Boot干货系列:(五)开发Web应用JSP篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 上一篇介绍了Spring Boot中使用Thymeleaf模板引擎,今天 ...
- Spring Boot干货系列:(四)Thymeleaf篇
Spring Boot干货系列:(四)Thymeleaf篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boo ...
- Spring Boot干货系列:(一)优雅的入门篇
Spring Boot干货系列:(一)优雅的入门篇 2017-02-26 嘟嘟MD 嘟爷java超神学堂 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社 ...
- Spring Boot干货系列:(十二)Spring Boot使用单元测试(转)
前言这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求 Service层单元测试 Controller层单元测试 新断言assertThat使用 单元 ...
- (转)Spring Boot干货系列:(七)默认日志logback配置解析
转:http://tengj.top/2017/04/05/springboot7/ 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候,是带着下面几个问题来查资料的, ...
- (转)Spring Boot干货系列:(四)开发Web应用之Thymeleaf篇
转:http://tengj.top/2017/03/13/springboot4/ 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boot对Web开发的支持. 正文 Sp ...
- 【WEB API项目实战干货系列】- 导航篇(十足干货分享)
在今天移动互联网的时代,作为攻城师的我们,谁不想着只写一套API就可以让我们的Web, Android APP, IOS APP, iPad APP, Hybired APP, H5 Web共用共同的 ...
随机推荐
- HTML网页/KRPano项目一键打包EXE工具(HTML网页打包成单个windows可执行文件exe)
HTML一键打包EXE工具使用说明 工具简介 HTML一键打包EXE工具(HTML封装EXE,桌件)能把任意HTML项目(网址)一键打包为单个EXE文件,可以脱离浏览器和服务器,直接双击即可运行.支持 ...
- 「loj - 6179」Pyh 的求和
link. 我们想要求出 \(\varphi(ij)=\varphi(i)\varphi(j)C\) 中的常数.先研究 \(i=p^a\),\(j=p^b\) 的情况,即 \(\varphi(p^{a ...
- 从零开始FastDFS整合Nginx(转)
转自 https://www.cnblogs.com/chiangchou/p/fastdfs.html#_labelTop Linux环境:Centos7.0 安装过程 原博客有几处纰漏,下文已 ...
- 连接远程MySQL报错问题-Datagrip
前言: 记录:DataGrip连接远程服务器MySQL数据库报错问题. 问题: 1.Communications link failure--会话连接失败 原因分析: 1.端口被防火墙了 2.MySQ ...
- c语言代码练习4(改进)
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <string.h> #include <wi ...
- 深入理解 python 虚拟机:原来虚拟机是这么实现闭包的
深入理解 python 虚拟机:原来虚拟机是这么实现闭包的 在本篇文章当中主要从虚拟机层面讨论函数闭包是如何实现的,当能够从设计者的层面去理解闭包就再也不用死记硬背一些闭包的概念了,因为如果你理解闭包 ...
- cv2 数学基础---矩阵微分
矩阵微分基础知识 定义 重要结论 应用 定义 (1) 向量对标量求导 矩阵对标量求导 我们可以看到上述求导过程实际上就是不同函数对变量求导,然后按照向量或者矩阵的形式排列,注意这里结果的结构应该与函数 ...
- CF1295D Same GCDs
前置知识: 辗转相除法 欧拉函数 首先,根据辗转相除法求 \(\gcd\) 的公式,可得 \(\gcd(a+x,m)=\gcd((a+x)\mod m,m)\). 则题目可以转化为:求有多少 \(x\ ...
- Python 利用pandas 和 matplotlib绘制柱状图
当你需要展示数据时,图表是一个非常有用的工具.Python 中的 pandas 和 matplotlib 库提供了丰富的功能,可以帮助你轻松地绘制各种类型的图表.本文将介绍如何使用这两个库,绘制一个店 ...
- 极速指南:在 SpringBoot 中快速集成腾讯云短信功能
前言 今天分享一个SpringBoot集成腾讯云短信的功能,平常除了工作,很多xdm做自己的小项目都可能用到短信,但自己去看文档挺费劲的,我这边就帮你节省时间,直接把步骤给你列出来,照做就行. 实战 ...