ПО промежуточного слоя времени ожидания запросов в ASP.NET Core

Автор: Том Дикстра (Tom Dykstra)

Приложения могут применять ограничения времени ожидания выборочно к запросам. ASP.NET основные серверы по умолчанию этого не делают, так как время обработки запросов зависит от сценария. Например, WebSockets, статические файлы и вызовы дорогостоящих API требуют другого ограничения времени ожидания. Поэтому ASP.NET Core предоставляет ПО промежуточного слоя, которое настраивает время ожидания для каждой конечной точки, а также глобальное время ожидания.

Если превышено ограничение времени ожидания, CancellationToken задано HttpContext.RequestAbortedIsCancellationRequested значение truein. Abort() не вызывается автоматически по запросу, поэтому приложение может по-прежнему создавать ответ на успех или сбой. Поведение по умолчанию, если приложение не обрабатывает исключение и создает ответ, является возврат кода состояния 504.

В этой статье объясняется, как настроить ПО промежуточного слоя времени ожидания. ПО промежуточного слоя времени ожидания можно использовать во всех типах приложений ASP.NET Core: минимальный API, веб-API с контроллерами, MVC и Razor Pages. Пример приложения — это минимальный API, но при каждом иллюстрируемом компоненте времени ожидания также поддерживается в других типах приложений.

Время ожидания запроса находятся в Microsoft.AspNetCore.Http.Timeouts пространстве имен.

Примечание. При запуске приложения в режиме отладки по промежуточному по промежуточному слоям времени ожидания не активируется. Это поведение совпадает с Kestrel временем ожидания. Чтобы проверить время ожидания, запустите приложение без подключения отладчика.

Добавление ПО промежуточного слоя в приложение

Добавьте ПО промежуточного слоя времени ожидания запроса в коллекцию служб путем вызова AddRequestTimeouts.

Добавьте ПО промежуточного слоя в конвейер обработки запросов путем вызова UseRequestTimeouts.

Примечание.

  • В приложениях, которые явно вызываются UseRouting, UseRequestTimeouts необходимо вызывать после UseRouting.

Добавление ПО промежуточного слоя в приложение не запускает автоматическое запуск времени ожидания. Ограничения времени ожидания должны быть явно настроены.

Настройка одной конечной точки или страницы

Для минимальных приложений API настройте конечную точку для ожидания путем вызова WithRequestTimeoutили применения атрибута [RequestTimeout] , как показано в следующем примере:

using Microsoft.AspNetCore.Http.Timeouts;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRequestTimeouts();

var app = builder.Build();
app.UseRequestTimeouts();

app.MapGet("/", async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch (TaskCanceledException)
    {
        return Results.Content("Timeout!", "text/plain");
    }

    return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout(TimeSpan.FromSeconds(2));
// Returns "Timeout!"

app.MapGet("/attribute",
    [RequestTimeout(milliseconds: 2000)] async (HttpContext context) => {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
        }
        catch (TaskCanceledException)
        {
            return Results.Content("Timeout!", "text/plain");
        }

        return Results.Content("No timeout!", "text/plain");
    });
// Returns "Timeout!"

app.Run();

Для приложений с контроллерами примените [RequestTimeout] атрибут к методу действия или классу контроллера. Для Razor приложений Pages примените атрибут к классу Razor страницы.

Настройка нескольких конечных точек или страниц

Создайте именованные политики , чтобы указать конфигурацию времени ожидания, которая применяется к нескольким конечным точкам. Добавление политики путем вызова AddPolicy:

builder.Services.AddRequestTimeouts(options => {
    options.DefaultPolicy =
        new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
    options.AddPolicy("MyPolicy", TimeSpan.FromSeconds(2));
});

Время ожидания можно указать для конечной точки по имени политики:

app.MapGet("/namedpolicy", async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch (TaskCanceledException)
    {
        return Results.Content("Timeout!", "text/plain");
    }

    return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout("MyPolicy");
// Returns "Timeout!"

Атрибут [RequestTimeout] также можно использовать для указания именованной политики.

Настройка глобальной политики времени ожидания по умолчанию

Укажите политику для глобальной конфигурации времени ожидания по умолчанию:

builder.Services.AddRequestTimeouts(options => {
    options.DefaultPolicy =
        new RequestTimeoutPolicy { Timeout = TimeSpan.FromMilliseconds(1500) };
    options.AddPolicy("MyPolicy", TimeSpan.FromSeconds(2));
});

Время ожидания по умолчанию применяется к конечным точкам, у которых нет указанного времени ожидания. Следующий код конечной точки проверка времени ожидания, хотя он не вызывает метод расширения или не применяет атрибут. Применяется глобальная конфигурация времени ожидания, поэтому код проверка времени ожидания:

app.MapGet("/", async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch
    {
        return Results.Content("Timeout!", "text/plain");
    }

    return Results.Content("No timeout!", "text/plain");
});
// Returns "Timeout!" due to default policy.

Указание кода состояния в политике

Класс RequestTimeoutPolicy имеет свойство, которое может автоматически задать код состояния при активации времени ожидания.

builder.Services.AddRequestTimeouts(options => {
    options.DefaultPolicy = new RequestTimeoutPolicy {
        Timeout = TimeSpan.FromMilliseconds(1000),
        TimeoutStatusCode = 503
    };
    options.AddPolicy("MyPolicy2", new RequestTimeoutPolicy {
        Timeout = TimeSpan.FromMilliseconds(1000),
        WriteTimeoutResponse = async (HttpContext context) => {
            context.Response.ContentType = "text/plain";
            await context.Response.WriteAsync("Timeout from MyPolicy2!");
        }
    });
});
app.MapGet("/", async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch (TaskCanceledException)
    {
        throw;
    }

    return Results.Content("No timeout!", "text/plain");
});
// Returns status code 503 due to default policy.

Использование делегата в политике

Класс RequestTimeoutPolicy имеет WriteTimeoutResponse свойство, которое можно использовать для настройки ответа при активации времени ожидания.

builder.Services.AddRequestTimeouts(options => {
    options.DefaultPolicy = new RequestTimeoutPolicy {
        Timeout = TimeSpan.FromMilliseconds(1000),
        TimeoutStatusCode = 503
    };
    options.AddPolicy("MyPolicy2", new RequestTimeoutPolicy {
        Timeout = TimeSpan.FromMilliseconds(1000),
        WriteTimeoutResponse = async (HttpContext context) => {
            context.Response.ContentType = "text/plain";
            await context.Response.WriteAsync("Timeout from MyPolicy2!");
        }
    });
});
app.MapGet("/usepolicy2", async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch (TaskCanceledException)
    {
        throw;
    }

    return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout("MyPolicy2");
// Returns "Timeout from MyPolicy2!" due to WriteTimeoutResponse in MyPolicy2.

Отключение времени ожидания

Чтобы отключить все тайм-ауты, включая глобальное время ожидания по умолчанию, используйте [DisableRequestTimeout] атрибут или DisableRequestTimeout метод расширения:

app.MapGet("/disablebyattr", [DisableRequestTimeout] async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch
    {
        return Results.Content("Timeout!", "text/plain");
    }

    return Results.Content("No timeout!", "text/plain");
});
// Returns "No timeout!", ignores default timeout.
app.MapGet("/disablebyext", async (HttpContext context) => {
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    }
    catch
    {
        return Results.Content("Timeout!", "text/plain");
    }

    return Results.Content("No timeout!", "text/plain");
}).DisableRequestTimeout();
// Returns "No timeout!", ignores default timeout.

Отмена времени ожидания

Чтобы отменить время ожидания, которое уже запущено, используйте DisableTimeout() метод IHttpRequestTimeoutFeatureв . Время ожидания невозможно отменить после истечения срока действия.

app.MapGet("/canceltimeout", async (HttpContext context) => {
    var timeoutFeature = context.Features.Get<IHttpRequestTimeoutFeature>();
    timeoutFeature?.DisableTimeout();

    try
    {
        await Task.Delay(TimeSpan.FromSeconds(10), context.RequestAborted);
    } 
    catch (TaskCanceledException)
    {
        return Results.Content("Timeout!", "text/plain");
    }

    return Results.Content("No timeout!", "text/plain");
}).WithRequestTimeout(TimeSpan.FromSeconds(1));
// Returns "No timeout!" since the default timeout is not triggered.

См. также