Kestrel abandona exceções do seu parser HTTP/1.1 no .NET 11
O parser de requisições HTTP/1.1 do Kestrel no .NET 11 substitui BadHttpRequestException por um struct de resultado, cortando o overhead de requisições malformadas em até 40%.
Toda requisição HTTP/1.1 malformada que atingia o Kestrel costumava lançar uma BadHttpRequestException. Essa exceção alocava um stack trace, desenrolava a pilha de chamadas, e era capturada em algum lugar mais acima, tudo por uma requisição que nunca produziria uma resposta válida. No .NET 11, o parser muda para um caminho de código sem throws, e a diferença é mensurável: 20-40% mais throughput em cenários com tráfego malformado frequente.
Por que exceções eram caras
Lançar uma exceção no .NET não é grátis. O runtime captura um stack trace, percorre a pilha de chamadas procurando um catch correspondente, e aloca o objeto de exceção no heap. Para uma requisição bem formada isso nunca dispara, então você não percebe. Mas scanners de portas, clientes mal configurados, e tráfego malicioso podem empurrar milhares de requisições ruins por segundo. Cada uma pagava o imposto completo de exceção.
// Before (.NET 10 and earlier): every parse failure threw
try
{
ParseRequestLine(buffer);
}
catch (BadHttpRequestException ex)
{
Log.ConnectionBadRequest(logger, ex);
return;
}
Em caminhos quentes, try/catch com throws frequentes se torna um gargalo de throughput.
A abordagem do struct de resultado
O parser do .NET 11 retorna um struct de resultado leve em vez disso:
// After (.NET 11): no exception on parse failure
var result = ParseRequestLine(buffer);
if (result.Status == ParseStatus.Error)
{
Log.ConnectionBadRequest(logger, result.ErrorReason);
return;
}
O struct carrega um campo Status (Success, Incomplete, ou Error) e uma string de razão de erro quando relevante. Sem alocação no heap, sem desenrolar a pilha, sem overhead do bloco catch. Requisições válidas não veem nenhuma mudança porque já tomavam o caminho de sucesso.
Quando isto importa
Se seu servidor fica atrás de um balanceador de carga que faz health-check com TCP cru ou se você expõe o Kestrel diretamente à internet, você está sendo atingido por requisições malformadas constantemente. Implantações honeypot, gateways de API lidando com protocolos mistos, e qualquer serviço exposto a scans de portas todos se beneficiam.
A melhoria é inteiramente interna ao Kestrel. Não há mudança de API, nem flag de configuração, nem opt-in. Atualize para .NET 11 e o parser é mais rápido por padrão.
Outras vitórias de desempenho no .NET 11
Essa não é a única redução de alocação no .NET 11 Preview. O middleware de logging HTTP agora faz pool de suas instâncias de ResponseBufferingStream, cortando alocações por requisição quando o logging do corpo de resposta está habilitado. Combinada com a mudança do parser, .NET 11 continua o padrão da equipe de runtime de transformar caminhos quentes pesados em exceções em fluxos de resultado baseados em struct.
Se você quiser ver o impacto na sua própria carga de trabalho, rode um benchmark antes/depois com Bombardier ou wrk enquanto injeta uma porcentagem de requisições malformadas. A mudança do parser é transparente, mas os números devem falar por si.