码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现


    合集 - wmproxy(44)
    1.用Rust手把手编写一个Proxy(代理), 动工2023-09-192.用Rust手把手编写一个Proxy(代理), UDP绑定篇2023-09-213.5. 用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备2023-09-284.6. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇2023-09-305.7. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP及TCP内网穿透原理及运行篇2023-10-046.8. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP改造篇之HPACK原理2023-10-087. 用Rust手把手编写一个Proxy(代理), 准备篇, 动手造轮子2023-09-168.9. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP2改造篇之HPACK示例, 了解http2头信息如何处理2023-10-099.10. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP内网穿透支持修改头信息2023-10-1010.11. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 实现健康检查2023-10-1211.12. 用Rust手把手编写一个wmproxy(代理,内网穿透等), TLS的双向认证信息及token验证2023-10-1412.13. 从零开始编写一个类nginx工具, HTTP中的压缩gzip,deflate,brotli算法2023-10-1713.14. 从零开始编写一个类nginx工具, HTTP文件服务器的实现过程及参数2023-10-1914.15. 从零开始编写一个类nginx工具, 如果将nginx.conf转成yaml,toml,json会怎么样2023-10-2015.16. 从零开始编写一个类nginx工具, 反向代理upstream源码实现2023-10-2316.17. 从零开始编写一个类nginx工具, Rust中一些功能的实现2023-10-2417.18. 从零开始编写一个类nginx工具, 主动式健康检查源码实现2023-10-2618.19. 从零开始编写一个类nginx工具, 配置数据的热更新原理及实现2023-10-2719.25. 干货系列从零用Rust编写正反向代理,序列化之serde是如何工作的2023-11-1020.27. 干货系列从零用Rust编写正反向代理,Rust中日志库的应用基础准备2023-11-1521.28. 干货系列从零用Rust编写正反向代理,项目日志的源码实现2023-11-1722.30. 干货系列从零用Rust编写正反向代理,HTTP的组装之旅(中间件)2023-11-2423.29. 干货系列从零用Rust编写正反向代理,异步回调(async trait)的使用2023-11-2124.26. 干货系列从零用Rust编写正反向代理,如何发布Rust项目到Docker2023-11-1325.32. 干货系列从零用Rust编写正反向代理,关于堆和栈以及如何解决stack overflow2023-12-0426.33. 干货系列从零用Rust编写正反向代理,关于HTTP客户端代理的源码实现2023-12-1227.34. 干货系列从零用Rust编写负载均衡及代理,异步测试在Rust中的实现2023-12-1628.35. 干货系列从零用Rust编写负载均衡及代理,代理服务器的源码升级改造2023-12-1929.36. 干货系列从零用Rust编写负载均衡及代理,内网穿透中内网代理的实现2023-12-22
    30.37. 干货系列从零用Rust编写负载均衡及代理,负载均衡中try_files实现2023-12-27
    31.38. 干货系列从零用Rust编写负载均衡及代理,负载均衡中ip通行与禁止01-0332.39. 干货系列从零用Rust编写负载均衡及代理,正则及格式替换01-0933.40. 干货系列从零用Rust编写负载均衡及代理,websocket的实现01-1134.41. 干货系列从零用Rust编写负载均衡及代理,websocket与tcp的映射,WS与TCP互转01-1635.42 干货系列从零用Rust编写负载均衡及代理,wmproxy中配置tcp转websocket01-1836.43 干货系列从零用Rust编写负载均衡及代理,内网穿透方案完整部署01-2237.44从零开始用Rust编写nginx,命令行参数的设计与解析及说明01-2438.45从零开始用Rust编写nginx,静态文件服务器竟然还有这些细节01-2639.46从零开始用Rust编写nginx,数据还能这么传,多层代理(IP多级代理)搭建01-3040.47从零开始用Rust编写nginx,配对还有这么多要求!负载均衡中的路径匹配02-0141.48从零开始用Rust编写nginx,搭建一个简单又好看官方网站02-0642.49从零开始用Rust编写nginx,我竟然在同一个端口上绑定了多少IP02-1943.50从零开始用Rust编写nginx,原来TLS证书还可以这么申请02-2644.51从零开始用Rust编写nginx,江湖救急,TLS证书快过期了02-29
    收起

    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状态码的,否则将执行到最后一个匹配路由。

    源码实现

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

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

    • 实现循环
      主要的处理函数为deal_match_location,函数的参数为
    #[async_recursion]
    async fn deal_match_location(
    req: &mut Request,
    // 缓存客户端请求
    cache: &mut HashMap<
    LocationConfig,
    (Sender>, Receiver>>),
    >,
    // 该Server的配置选项
    server: Arc,
    // 已处理的匹配路由
    deals: &mut HashSet<usize>,
    // 已处理的TryPath匹配路由
    try_deals: &mut HashSet<usize>,
    ) -> ProtResult>

    当前在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来适应处理提供多路由映射的可能性。

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

  • 相关阅读:
    H2数据库--转载
    有效备考浙大MEM的五个好习惯建议
    C++ Web 编程
    贪心算法解决多机调度问题——C++实现
    2022年Java秋招面试必看的 | RabbitMQ 面试题
    ora-39083 ora-01861
    kubernetes集群安装
    一行代码解决WINFORM因为系统放大导致的布局混乱
    【附源码】Python计算机毕业设计体育场馆预定网站
    瑞吉外卖08-文件上传下载
  • 原文地址:https://www.cnblogs.com/wmproxy/p/wmproxy37.html
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号