Yarp 让系统内调度更灵活

简介

Yarp 是微软团队开发的一个反向代理组件, 除了常规的 http 和 https 转换通讯,它最大的特点是可定制化,很容易根据特定场景开发出需要的定制代理通道。

详细介绍:https://devblogs.microsoft.com/dotnet/announcing-yarp-1-0-release

源码仓库:https://github.com/microsoft/reverse-proxy

文档地址 :https://microsoft.github.io/reverse-proxy/

 

基础使用

1、创建 ASP.NET Core  空项目

使用 Visual Studio :

 
使用 .NET CLI 命令行创建:
dotnet new web -o MyProxy

2、 修改代码 Program.cs 文件

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapGet("/Ping", () => "Hello World!");
app.MapReverseProxy();
app.Run();

3、修改配置文件 appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "routeAll": {
        "ClusterId": "clusterBaidu",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "clusterBaidu": {
        "Destinations": {
          "baidu": {
            "Address": "https://www.baidu.com/"
          }
        }
      }
    }
  }
}

这里的配置是将所有的请求都转发到百度。

在 Program.cs 里,还注册了一个 Get 路由 Ping 。

4、启动项目

能够看到在浏览器访问程序监听的端口号后,显示的是百度的页面。打开 F12 ,看到请求头也是本地的,并不是百度的域名。

测试手动注册的路由 Ping :

能够显示正常。

5、问题整理

(1) Yarp 是不是只能做这种简单的转发?

不是,往下有配置文件说明。

(2) JSON 配置文件里有什么要注意的地方吗?

有。在这个演示的配置文件中 ReverseProxy:Clusters:cluster1:Destinations:destination1:Address 对应的值是:https://www.baidu.com/ ,如果去掉 www ,在项目启动后会跳转到百度首页,不是代理转发。去掉末尾的 / 符合没有任何影响。

(3) Yarp 会影响到程序中注册的路由吗?

不会影响到程序内部注册的路由。在 Program.cs 中无论 app.MapReverseProxy(); 在上还是在下,在访问 Ping 的时候,都是返回 Hello World!

var app = builder.Build();
app.MapReverseProxy();
app.MapGet("/Ping", () => "Hello World!");
app.Run();

 

进阶探索

1、多地址代理

 修改配置文件 appsettings.json ,实现默认路由跳转百度,当访问 /movie 是访问 b站。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "routeBaidu": {
        "ClusterId": "clusterBaidu",
        "Match": {
          "Path": "{**catch-all}"
        }
      },
      "routeBiliBili": {
        "ClusterId": "clusterBiliBili",
        "Match": {
          "Path": "/movie/{**catch-all}"
        }
      }
    },
    "Clusters": {
      "clusterBaidu": {
        "Destinations": {
          "baidu": {
            "Address": "https://www.baidu.com/"
          }
        }
      },
      "clusterBiliBili": {
        "Destinations": {
          "bilibili": {
            "Address": "https://www.bilibili.com/"
          }
        }
      }
    }
  }
}

 测试结果:

在后面输入路由 /movie 后能够跳转到b站。但是b站网页没有完整显示,图片都没有,这是网站上的策略问题,对于数据接口没有这些问题。

详细的配置文件说明,可以查看 https://microsoft.github.io/reverse-proxy/articles/config-files.html

2、规则匹配

网页上太多资源,为了方便测试,启用两个 api 接口。地址分别是:http://localhost:5241/ 和 https://localhost:7184/

两个 api 接口中分别注册 /test 路由。

// http://localhost:5241/
app.MapGet("/test", () => "Welcome to Api111!");

// https://localhost:7184/
app.MapGet("/test", () => "Welcome to Api222!");

启动两个 api 程序。

C:\Users\Test>curl http://localhost:5241/test
Welcome to Api111!

C:\Users\Test>curl https://localhost:7184/test
Welcome to Api222!

修改 MyProxy 项目的配置文件 appsettings.json 

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "routeOne": {
        "ClusterId": "clusterOne",
        "Match": {
          "Path": "/test/{**catch-all}",
          "QueryParameters": [
            {
              "Name": "number",
              "Values": [ "1" ]
            }
          ]
        }
      },
      "routeTwo": {
        "ClusterId": "clusterTwo",
        "Match": {
          "Path": "/test/{**catch-all}",
          "QueryParameters": [
            {
              "Name": "number",
              "Values": [ "2" ]
            }
          ]
        }
      },
      "routeBaidu": {
        "ClusterId": "clusterBaidu",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "clusterOne": {
        "Destinations": {
          "apiOne": {
            "Address": "http://localhost:5241/"
          }
        }
      },
      "clusterTwo": {
        "Destinations": {
          "apiTwo": {
            "Address": "https://localhost:7184/"
          }
        }
      },
      "clusterBaidu": {
        "Destinations": {
          "baidu": {
            "Address": "https://www.baidu.com/"
          }
        }
      }
    }
  }
}

Path :监听路由地址。

QueryParameters:匹配参数。

QueryParameters:Name:参数名。

QueryParameters:Values:参数值。

MyProxy 的监听端口是 http://localhost:5024/ 访问结果如下

C:\Users\Test>curl http://localhost:5024/ping
Hello World!

C:\Users\Test>curl http://localhost:5024/test
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /test was not found on this server.</p>
</body></html>

C:\Users\Test>curl http://localhost:5024/test?number=1
Welcome to Api111!

C:\Users\Test>curl http://localhost:5024/test?number=2
Welcome to Api222!

能够根据参数以及参数值导向对应的地址。

3、问题整理

(1)为什么访问 /movie 不能正常显示网页。

因为 b站某些接口开启了防盗链,还有跨域检测。

(2)在根据参数匹配中,如果匹配的路由一样,监听的参数一样,参数值也一样会怎么样?

访问该路由地址会报错。

(3)路由匹配的优先级?

程序内注册的路由优先级最高,其次才是 Yarp 在配置文件里加载的。

 

小试牛刀

最近的工作是做企业内数据安全方面的。推动公司数据安全体系,通过技术手段提升公司信息安全。

有一个很老的OA系统,十几年了, .NET Framework 2.0 写的。漏洞一大堆,包括不限于xss、sql注入等,权限只到表单级别。浏览器上按下 F12 能查看到表单链接,直接复制出去,别人也能访问。

在这个系统上要做安全,我想的是在中间加代理,正好适合使用 Yarp 来完成,也方便写业务处理代码。嗯,很真实, .NET Core 写的,方便写业务代码。

用户登录成功后,会记录下用户的 Host Cookie,每次访问的时候系统的时候,在 Yarp 这里都校验一下是否与用户登录时的匹配。

 

解决了两个问题:

1、从网络层捕获到所有的请求,方便后面做排查。参数、传值,出了事故可以找到责任人。

2、隔离真实的站点地址,杜绝弱安全等级网站暴露后被坏人攻击的风险。

 

踩坑集锦

1、non-ASCII 

项目要代理某网页,在使用下载功能的时候,接口返回 502 。

info: Yarp.ReverseProxy.Forwarder.HttpForwarder[48]
      ResponseHeaders: The destination returned a response that cannot be proxied back to the client.
      System.InvalidOperationException: Invalid non-ASCII or control character in header: 0x00E4
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowInvalidHeaderCharacter(Char ch)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ValidateHeaderValueCharacters(StringValues headerValues)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders.SetValueFast(String key, StringValues value)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
         at Yarp.ReverseProxy.Forwarder.HttpTransformer.CopyResponseHeaders(HttpHeaders source, IHeaderDictionary destination)
         at Yarp.ReverseProxy.Forwarder.HttpTransformer.TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse)
         at Yarp.ReverseProxy.Transforms.Builder.StructuredTransformer.TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse)
         at Yarp.ReverseProxy.Forwarder.HttpForwarder.SendAsync(HttpContext context, String destinationPrefix, HttpMessageInvoker httpClient, ForwarderRequestConfig requestConfig, HttpTransformer transformer)

去 GitHub 翻 Issues

下载接口能正常访问,文件流也能完整地拿到。重写了所有的响应头没有用。这种不开源的商业站点,也猜不到字符编码。

最后妥协了,用了一个 .NET 服务在服务器上下载后再转发。

代理非常规服务接口时,一定要多测试。

 

posted @ 2021-11-29 22:40  妙蛙草  阅读(5112)  评论(5编辑  收藏  举报