ApsNet.Core 中的通用 passthrough/forwarding 表单数据
Generic passthrough/forwarding of form data in ApsNet.Core
我正在尝试创建一个网络钩子来接收来自 Twilio phone 号码的消息。但是,我不仅需要一个 webhook 来修改数据并立即 return 一个结果给 Twilio,我需要这个 webhook 将 Twilio 的消息传递到内部 API,等待响应,然后 然后 return Twilio 的结果。
这是我想出的一些通用代码,我希望它能起作用。
public async Task<HttpResponseMessage> ReceiveAndForwardSms(HttpContent smsContent)
{
var client = new HttpClient();
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", smsContent);
return response;
}
此代码的问题是 Twilio 在进入函数之前立即 returns 一个 415 错误代码 (Unsupported Media Type
)。
当我尝试接受“正确类型”(Twilio.AspNet.Common.SmsRequest
) 时,我无法将 SmsRequest
填回表单编码对象并通过 client.PostAsync()
发送它...
例如:
public async Task<HttpResponseMessage> ReceiveAndForwardSms([FromForm]SmsRequest smsRequest)
{
var client = new HttpClient();
var stringContent = new StringContent(smsRequest.ToString());
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", stringContent);
return response;
}
- 我能做些什么来“屏蔽”函数的接受类型或保持第一个函数的通用性吗?
- 如何将此 SmsRequest 推回到“表单编码”对象中,以便我可以在我的消费服务中以同样的方式接受它?
TLDR
您的选择是:
- 使用现有的反向代理,如 NGINX、HAProxy、F5
- 使用 YARP 将反向代理功能添加到 ASP.NET 核心项目
- 在控制器中接受 webhook 请求,将 headers 和数据映射到新的
HttpRequestMessage
并将其发送到您的私人服务,然后将您的私人服务的响应映射到响应回到 Twilio。
听起来您要构建的是 reverse proxy。在 Web 应用程序前面放置一个反向代理是很常见的,用于 SSL 终止、缓存、基于主机名或 URL 的路由等。
反向代理将接收 Twilio HTTP 请求,然后将其转发到正确的私有服务。专用服务响应反向代理转发回 Twilio。
我建议使用现有的反向代理而不是自己构建此功能。如果你真的想自己构建它,这里有一个我能够开始工作的示例:
在您的反向代理项目中,添加一个控制器:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace ReverseProxy.Controllers;
public class SmsController : Controller
{
private static readonly HttpClient HttpClient;
private readonly ILogger<SmsController> logger;
private readonly string twilioWebhookServiceUrl;
static SmsController()
{
// don't do this in production!
var insecureHttpClientHandler = new HttpClientHandler();
insecureHttpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true;
HttpClient = new HttpClient(insecureHttpClientHandler);
}
public SmsController(ILogger<SmsController> logger, IConfiguration configuration)
{
this.logger = logger;
twilioWebhookServiceUrl = configuration["TwilioWebhookServiceUrl"];
}
public async Task Index()
{
using var serviceRequest = new HttpRequestMessage(HttpMethod.Post, twilioWebhookServiceUrl);
foreach (var header in Request.Headers)
{
serviceRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
serviceRequest.Content = new FormUrlEncodedContent(
Request.Form.ToDictionary(
kv => kv.Key,
kv => kv.Value.ToString()
)
);
var serviceResponse = await HttpClient.SendAsync(serviceRequest);
Response.ContentType = "application/xml";
var headersDenyList = new HashSet<string>()
{
"Content-Length",
"Date",
"Transfer-Encoding"
};
foreach (var header in serviceResponse.Headers)
{
if(headersDenyList.Contains(header.Key)) continue;
logger.LogInformation("Header: {Header}, Value: {Value}", header.Key, string.Join(',', header.Value));
Response.Headers.Add(header.Key, new StringValues(header.Value.ToArray()));
}
await serviceResponse.Content.CopyToAsync(Response.Body);
}
}
这将接受 Twilio webhook 请求,并将所有 headers 和内容转发到私有 Web 服务。请注意,即使我能够破解它直到它起作用,它可能不安全且性能不佳。您可能需要做更多的工作才能使其成为生产级代码。使用风险自负。
在你的私人服务的ASP.NET核心项目中,使用TwilioController
接受请求:
using Microsoft.AspNetCore.Mvc;
using Twilio.AspNet.Common;
using Twilio.AspNet.Core;
using Twilio.TwiML;
namespace Service.Controllers;
public class SmsController : TwilioController
{
private readonly ILogger<SmsController> logger;
public SmsController(ILogger<SmsController> logger)
{
this.logger = logger;
}
public IActionResult Index(SmsRequest smsRequest)
{
logger.LogInformation("SMS Received: {SmsId}", smsRequest.SmsSid);
var response = new MessagingResponse();
response.Message($"You sent: {smsRequest.Body}");
return TwiML(response);
}
}
与其使用反向代理控制器中的脆弱代码代理请求,我建议在您的反向代理项目中安装 YARP,这是一个 ASP.NET 基于核心的反向代理库。
dotnet add package Yarp.ReverseProxy
然后在appsettings.json中添加如下配置:
{
...
"ReverseProxy": {
"Routes": {
"SmsRoute" : {
"ClusterId": "SmsCluster",
"Match": {
"Path": "/sms"
}
}
},
"Clusters": {
"SmsCluster": {
"Destinations": {
"SmsService1": {
"Address": "https://localhost:7196"
}
}
}
}
}
}
此配置会将任何对路径 /Sms 的请求转发给您的私人 ASP.NET 核心服务,在我的本地机器上是 运行ning在 https://localhost:7196.
您还需要更新 Program.cs 文件以开始使用 YARP:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
当你现在 运行 这两个项目时,对 /sms 的 Twilio webhook 请求被转发到你的私人服务,你的私人服务将响应,你的反向代理服务会将响应转发回 Twilio。
使用 YARP,您可以通过配置甚至以编程方式做更多的事情,所以如果您有兴趣,我会查看 YARP docs.
如果您已经拥有像 NGINX、HAProxy、F5 等反向代理,配置它来转发您的请求可能比使用 YARP 更容易。
我正在尝试创建一个网络钩子来接收来自 Twilio phone 号码的消息。但是,我不仅需要一个 webhook 来修改数据并立即 return 一个结果给 Twilio,我需要这个 webhook 将 Twilio 的消息传递到内部 API,等待响应,然后 然后 return Twilio 的结果。
这是我想出的一些通用代码,我希望它能起作用。
public async Task<HttpResponseMessage> ReceiveAndForwardSms(HttpContent smsContent)
{
var client = new HttpClient();
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", smsContent);
return response;
}
此代码的问题是 Twilio 在进入函数之前立即 returns 一个 415 错误代码 (Unsupported Media Type
)。
当我尝试接受“正确类型”(Twilio.AspNet.Common.SmsRequest
) 时,我无法将 SmsRequest
填回表单编码对象并通过 client.PostAsync()
发送它...
例如:
public async Task<HttpResponseMessage> ReceiveAndForwardSms([FromForm]SmsRequest smsRequest)
{
var client = new HttpClient();
var stringContent = new StringContent(smsRequest.ToString());
HttpResponseMessage response = await client.PostAsync(Environment.GetEnvironmentVariable("requestUriBase") + "/api/SmsHandler/PostSms", stringContent);
return response;
}
- 我能做些什么来“屏蔽”函数的接受类型或保持第一个函数的通用性吗?
- 如何将此 SmsRequest 推回到“表单编码”对象中,以便我可以在我的消费服务中以同样的方式接受它?
TLDR 您的选择是:
- 使用现有的反向代理,如 NGINX、HAProxy、F5
- 使用 YARP 将反向代理功能添加到 ASP.NET 核心项目
- 在控制器中接受 webhook 请求,将 headers 和数据映射到新的
HttpRequestMessage
并将其发送到您的私人服务,然后将您的私人服务的响应映射到响应回到 Twilio。
听起来您要构建的是 reverse proxy。在 Web 应用程序前面放置一个反向代理是很常见的,用于 SSL 终止、缓存、基于主机名或 URL 的路由等。 反向代理将接收 Twilio HTTP 请求,然后将其转发到正确的私有服务。专用服务响应反向代理转发回 Twilio。
我建议使用现有的反向代理而不是自己构建此功能。如果你真的想自己构建它,这里有一个我能够开始工作的示例:
在您的反向代理项目中,添加一个控制器:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
namespace ReverseProxy.Controllers;
public class SmsController : Controller
{
private static readonly HttpClient HttpClient;
private readonly ILogger<SmsController> logger;
private readonly string twilioWebhookServiceUrl;
static SmsController()
{
// don't do this in production!
var insecureHttpClientHandler = new HttpClientHandler();
insecureHttpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true;
HttpClient = new HttpClient(insecureHttpClientHandler);
}
public SmsController(ILogger<SmsController> logger, IConfiguration configuration)
{
this.logger = logger;
twilioWebhookServiceUrl = configuration["TwilioWebhookServiceUrl"];
}
public async Task Index()
{
using var serviceRequest = new HttpRequestMessage(HttpMethod.Post, twilioWebhookServiceUrl);
foreach (var header in Request.Headers)
{
serviceRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
serviceRequest.Content = new FormUrlEncodedContent(
Request.Form.ToDictionary(
kv => kv.Key,
kv => kv.Value.ToString()
)
);
var serviceResponse = await HttpClient.SendAsync(serviceRequest);
Response.ContentType = "application/xml";
var headersDenyList = new HashSet<string>()
{
"Content-Length",
"Date",
"Transfer-Encoding"
};
foreach (var header in serviceResponse.Headers)
{
if(headersDenyList.Contains(header.Key)) continue;
logger.LogInformation("Header: {Header}, Value: {Value}", header.Key, string.Join(',', header.Value));
Response.Headers.Add(header.Key, new StringValues(header.Value.ToArray()));
}
await serviceResponse.Content.CopyToAsync(Response.Body);
}
}
这将接受 Twilio webhook 请求,并将所有 headers 和内容转发到私有 Web 服务。请注意,即使我能够破解它直到它起作用,它可能不安全且性能不佳。您可能需要做更多的工作才能使其成为生产级代码。使用风险自负。
在你的私人服务的ASP.NET核心项目中,使用TwilioController
接受请求:
using Microsoft.AspNetCore.Mvc;
using Twilio.AspNet.Common;
using Twilio.AspNet.Core;
using Twilio.TwiML;
namespace Service.Controllers;
public class SmsController : TwilioController
{
private readonly ILogger<SmsController> logger;
public SmsController(ILogger<SmsController> logger)
{
this.logger = logger;
}
public IActionResult Index(SmsRequest smsRequest)
{
logger.LogInformation("SMS Received: {SmsId}", smsRequest.SmsSid);
var response = new MessagingResponse();
response.Message($"You sent: {smsRequest.Body}");
return TwiML(response);
}
}
与其使用反向代理控制器中的脆弱代码代理请求,我建议在您的反向代理项目中安装 YARP,这是一个 ASP.NET 基于核心的反向代理库。
dotnet add package Yarp.ReverseProxy
然后在appsettings.json中添加如下配置:
{
...
"ReverseProxy": {
"Routes": {
"SmsRoute" : {
"ClusterId": "SmsCluster",
"Match": {
"Path": "/sms"
}
}
},
"Clusters": {
"SmsCluster": {
"Destinations": {
"SmsService1": {
"Address": "https://localhost:7196"
}
}
}
}
}
}
此配置会将任何对路径 /Sms 的请求转发给您的私人 ASP.NET 核心服务,在我的本地机器上是 运行ning在 https://localhost:7196.
您还需要更新 Program.cs 文件以开始使用 YARP:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
当你现在 运行 这两个项目时,对 /sms 的 Twilio webhook 请求被转发到你的私人服务,你的私人服务将响应,你的反向代理服务会将响应转发回 Twilio。
使用 YARP,您可以通过配置甚至以编程方式做更多的事情,所以如果您有兴趣,我会查看 YARP docs.
如果您已经拥有像 NGINX、HAProxy、F5 等反向代理,配置它来转发您的请求可能比使用 YARP 更容易。