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 服務在服務器上下載后再轉發。

代理非常規服務接口時,一定要多測試。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM