经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Rust » 查看文章
37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现
来源:cnblogs  作者:问蒙服务框架  时间:2023/12/27 11:06:53  对本文有异议

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、前端路由处理:

  1. location / {
  2. try_files $uri $uri/ /index.html;
  3. # $uri指请求的uri路径,$uri/表示请求的uri路径加上一个/,例如访问example.com/path,则会依次尝试访问/path,/path/index.html,/index.html
  4. # /index.html表示如果仍未匹配到则重定向到index.html
  5. }

这种场景多用于单页应用,例如vue.js等前端框架的路由管理。当用户在浏览器中访问一个非根路径的路径时,由于这些路径都是由前端路由管理的,nginx无法直接返回正确的静态文件,因此需要将请求重定向到统一的路径,这里是/index.html,由前端路由控制页面的展示。
2、图片服务器:

  1. location /images/ {
  2. root /data/www;
  3. error_page 404 = /fetch_image.php;
  4. try_files $uri $uri/ =404;
  5. }
  6. location /fetch_image.php {
  7. fastcgi_pass 127.0.0.1:9000;
  8. set $path_info "";
  9. fastcgi_param PATH_INFO $path_info;
  10. fastcgi_param SCRIPT_FILENAME /scripts/fetch_image.php;
  11. include fastcgi_params;
  12. }

这种场景多用于图片服务器,当用户访问图片时,先尝试在本地文件系统中查找是否有该文件,如果找到就返回;如果没有找到则会转发到fetch_image.php进行处理,从远程资源服务器拉取图片并返回给用户。

实现方案

当前nginx方案的实现,是基于文件的重试,也就是所谓的伪静态,如果跨目录的服务器就很麻烦了,比如:

  1. location /images/ {
  2. root /data/upload;
  3. try_files $uri $uri/ =404;
  4. }
  5. location /images2/ {
  6. root /data/www;
  7. try_files $uri $uri/ =404;
  8. }

上面的我们无法同时索引两个目录下的结构。即我假设我访问/images/logo.png无法同时查找/data/upload/logo.png/data/www/logo.png是否存在。

当前实现方案从try_files变成try_paths也就是当碰到该选项时,将当前的几个访问地址重新进入路由

例:

  1. [[http.server.location]]
  2. rate_limit = "4m/s"
  3. rule = "/root/logo.png"
  4. file_server = { browse = true }
  5. proxy_pass = ""
  6. try_paths = "/data/upload/logo.png /data/www/logo.png /root/README.md"
  7. [[http.server.location]]
  8. rule = "/data/upload"
  9. file_server = { browse = true }
  10. [[http.server.location]]
  11. rule = "/data/www"
  12. file_server = { browse = true }

除非碰到返回100或者200状态码的,否则将执行到最后一个匹配路由。

源码实现

    1. 要能循环遍历路由
    1. 当try_paths时要避免递归死循环
    1. 当try_paths时可能会调用自己本身,需要能重复调用

以下主要源码均在reverse/http.rs

  • 实现循环
    主要的处理函数为deal_match_location,函数的参数为
  1. #[async_recursion]
  2. async fn deal_match_location(
  3. req: &mut Request<Body>,
  4. // 缓存客户端请求
  5. cache: &mut HashMap<
  6. LocationConfig,
  7. (Sender<Request<Body>>, Receiver<ProtResult<Response<Body>>>),
  8. >,
  9. // 该Server的配置选项
  10. server: Arc<ServerConfig>,
  11. // 已处理的匹配路由
  12. deals: &mut HashSet<usize>,
  13. // 已处理的TryPath匹配路由
  14. try_deals: &mut HashSet<usize>,
  15. ) -> ProtResult<Response<Body>>

当前在Rust中的异步递归会报如下错误

  1. recursion in an `async fn` requires boxing
  2. a recursive `async fn` must be rewritten to return a boxed `dyn Future`
  3. consider using the `async_recursion` crate: https://crates.io/crates/async_recursion

所以需要添加#[async_recursion]或者改成Box返回。

参数其中多定义了两组HashSet用来存储已处理的路由及已处理的TryPath路由。

将循环获取合适的location,如果未找到直接返回503错误。

  1. let path = req.path().clone();
  2. let mut l = None;
  3. let mut now = usize::MAX;
  4. for idx in 0..server.location.len() {
  5. if deals.contains(&idx) {
  6. continue;
  7. }
  8. if server.location[idx].is_match_rule(&path, req.method()) {
  9. l = Some(&server.location[idx]);
  10. now = idx;
  11. break;
  12. }
  13. }
  14. if l.is_none() {
  15. return Ok(Response::status503()
  16. .body("unknow location to deal")
  17. .unwrap()
  18. .into_type());
  19. }

当该路由存在try_paths的情况时:

  1. // 判定该try是否处理过, 防止死循环
  2. if !try_deals.contains(&now) && l.try_paths.is_some() {
  3. let try_paths = l.try_paths.as_ref().unwrap();
  4. try_deals.insert(now);
  5. let ori_path = req.path().clone();
  6. for val in try_paths.list.iter() {
  7. deals.clear();
  8. // 重写path好方便做数据格式化
  9. req.set_path(ori_path.clone());
  10. let new_path = Helper::format_req(req, &**val);
  11. // 重写path好方便后续处理无感
  12. req.set_path(new_path);
  13. if let Ok(res) = Self::deal_match_location(
  14. req,
  15. cache,
  16. server.clone(),
  17. deals,
  18. try_deals,
  19. )
  20. .await
  21. {
  22. if !res.status().is_client_error() && !res.status().is_server_error() {
  23. return Ok(res);
  24. }
  25. }
  26. }
  27. return Ok(Response::builder()
  28. .status(try_paths.fail_status)
  29. .body("not valid to try")
  30. .unwrap()
  31. .into_type());
  32. }

其中会将req中的path进行格式化的重写以方便处理:

  1. // 重写path好方便做数据格式化
  2. req.set_path(ori_path.clone());
  3. let new_path = Helper::format_req(req, &**val);
  4. // 重写path好方便后续处理无感
  5. req.set_path(new_path);

如果不存在try_paths将正常的按照路由的处理逻辑,该文件服务器或者反向代理,并标记该路由已处理。

  1. deals.insert(now);
  2. let clone = l.clone_only_hash();
  3. if cache.contains_key(&clone) {
  4. let mut cache_client = cache.remove(&clone).unwrap();
  5. if !cache_client.0.is_closed() {
  6. println!("do req data by cache");
  7. let _send = cache_client.0.send(req.replace_clone(Body::empty())).await;
  8. match cache_client.1.recv().await {
  9. Some(res) => {
  10. if res.is_ok() {
  11. log::trace!("cache client receive response");
  12. cache.insert(clone, cache_client);
  13. }
  14. return res;
  15. }
  16. None => {
  17. log::trace!("cache client close response");
  18. return Ok(Response::status503()
  19. .body("already lose connection")
  20. .unwrap()
  21. .into_type());
  22. }
  23. }
  24. }
  25. } else {
  26. log::trace!("do req data by new");
  27. let (res, sender, receiver) = l.deal_request(req).await?;
  28. if sender.is_some() && receiver.is_some() {
  29. cache.insert(clone, (sender.unwrap(), receiver.unwrap()));
  30. }
  31. return Ok(res);
  32. }

小结

try_files在nginx中提供了更多的可能,也方便了伪静态文件服务器的处理。我们在其中的基础上稍微改造成try_paths来适应处理提供多路由映射的可能性。

点击 [关注][在看][点赞] 是对作者最大的支持

原文链接:https://www.cnblogs.com/wmproxy/p/wmproxy37.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号