ASP.NET Azure 应用服务上的核心 3.1 应用程序 运行 抛出 1.6 MB json 负载的 EPIPE 错误

ASP.NET Core 3.1 app running on Azure App Service throwing EPIPE errors for 1.6 MB json payload

我有一个简单的 ASP.NET Core 3.1 应用程序部署在 Azure 应用程序服务上,配置了 .NET Core 3.1 运行时。我的一个端点预计会收到一个简单的 JSON 负载,其中包含一个“数据”属性,这是一个文件的 base64 编码字符串。它可能会很长,当 JSON 有效负载为 1.6 MB 时,我 运行 进入以下问题。

在我的本地工作站上,当我从 Postman 调用我的 API 时,一切都按预期工作,到达控制器操作方法中的断点,数据已填充,一切都很好 - 只有当我部署时(通过 Azure DevOps CICD Pipelines)应用到 Azure 应用服务。每当尝试从 Postman 调用已部署的 API 时,都不会收到任何 HTTP 响应,而是:“错误:写入 EPIPE”。

我已经尝试修改 web.config 以包含 maxRequestLength 和 maxAllowedContentLength 属性:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
      <system.web>
          <httpRuntime maxRequestLength="204800" ></httpRuntime>
          </system.web>
    <system.webServer>
            <security>
        <requestFiltering>          
            <requestLimits maxAllowedContentLength="419430400" />
        </requestFiltering>
    </security>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\MyApp.API.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>

在应用的代码中,我添加了 Startup.cs:

services.Configure<IISServerOptions>(options => {
    options.MaxRequestBodySize = int.MaxValue;
});

在 Program.cs 中,我添加了:

.UseKestrel(options => { options.Limits.MaxRequestBodySize = int.MaxValue; })

在控制器中,我尝试了这两个属性:[DisableRequestSizeLimit]、[RequestSizeLimit(40000000)]

但是,到目前为止没有任何工作 - 我很确定它必须是在应用服务本身上配置的东西,而不是在我的代码中,因为本地一切都在工作。然而,到目前为止,web.config

没有任何帮助

这与我的应用服务中的事实有关,我必须在配置中允许传入的客户端证书 - 事实证明,客户端证书和大型负载在 IIS 中不能很好地混合(显然已有十多年了现在):https://docs.microsoft.com/en-us/archive/blogs/waws/posting-a-large-file-can-fail-if-you-enable-client-certificates

None 上述博客 post 中提出的解决方法解决了我的问题,所以我不得不解决:我创建了一个 Azure 函数(仍然使用 .NET Core 3.1 作为运行时stack) 和 Consumption Plan,它能够接收大量有效负载和传入的客户端证书(我猜它不使用 IIS?)。

在我的原始后端中,我将原始 API 的路由添加到应用服务的“证书排除路径”,以免卡住等待并最终因“错误:写入 EPIPE”而超时。

我已使用托管标识在我的应用服务和新的 Azure 函数之间进行身份验证(通过函数中的系统分配标识)。

Azure 函数获取收到的证书,并将其添加到 JSON body 中的新“证书”属性,紧挨着原始“数据”[=31] =],所以我的自定义 SSL 验证可以保留在 App Service 上,但证书不是从 X-ARR-ClientCert header 中获取的,而是从接收到的有效负载的“证书”中获取的 属性。

函数:

#r "Newtonsoft.Json"
using System.Net;
using System.IO;
using System.Net.Http;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System.Security.Cryptography.X509Certificates;

private static HttpClient httpClient = new HttpClient();

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    var requestBody = string.Empty;
    using (var streamReader =  new StreamReader(req.Body))
    {
        requestBody = await streamReader.ReadToEndAsync();
    }

    dynamic deserializedPayload = JsonConvert.DeserializeObject(requestBody);
    var data = deserializedPayload?.data;
    
    var originalUrl = $"https://original-backend.azurewebsites.net/api/inbound";
    var certificateString = string.Empty;

    StringValues cert;
    if (req.Headers.TryGetValue("X-ARR-ClientCert", out cert))
    {
        certificateString = cert;
    }

    var newPayload = new {
        data = data,
        certificate = certificateString
    };

    var response = await httpClient.PostAsync(
        originalUrl,
        new StringContent(JsonConvert.SerializeObject(newPayload), Encoding.UTF8, "application/json"));

    var responseContent = await response.Content.ReadAsStringAsync();

    try
    {
        response.EnsureSuccessStatusCode();
        return new OkObjectResult(new { message = "Forwarded request to the original backend" });
    }
    catch (Exception e)
    {
        return new ObjectResult(new { response = responseContent, exception = JsonConvert.SerializeObject(e)})
        {
            StatusCode = 500
        };
    }
}