ASP.NET Core에서 요청 시간 제한 미들웨어

작성자: Tom Dykstra

앱은 요청에 시간 제한 제한을 선택적으로 적용할 수 있습니다. ASP.NET Core 서버는 요청 처리 시간이 시나리오에 따라 크게 다르므로 기본적으로 이 작업을 수행하지 않습니다. 예를 들어 WebSocket, 정적 파일 및 비용이 많이 드는 API를 호출하려면 각각 다른 시간 제한 시간이 필요합니다. 따라서 ASP.NET Core는 엔드포인트당 시간 제한과 전역 시간 제한을 구성하는 미들웨어를 제공합니다.

시간 제한에 도달하면 in CancellationTokenHttpContext.RequestAborted 이 .로 true설정됩니다IsCancellationRequested. Abort() 는 요청에 대해 자동으로 호출되지 않으므로 애플리케이션은 여전히 성공 또는 실패 응답을 생성할 수 있습니다. 앱이 예외를 처리하지 않고 응답을 생성하는 경우 기본 동작은 상태 코드 504를 반환하는 것입니다.

이 문서에서는 시간 제한 미들웨어를 구성하는 방법을 설명합니다. 제한 시간 미들웨어는 모든 유형의 ASP.NET Core 앱(최소 API, 컨트롤러가 있는 Web API, MVC 및 Razor Pages)에서 사용할 수 있습니다. 샘플 앱은 최소 API이지만, 보여지는 시간 제한 기능은 다른 앱 유형에서도 지원됩니다.

요청 시간 제한은 네임스페이스에 Microsoft.AspNetCore.Http.Timeouts 있습니다.

참고: 앱이 디버그 모드에서 실행되면 시간 제한 미들웨어가 트리거되지 않습니다. 이 동작은 시간 제한과 Kestrel 동일합니다. 시간 제한을 테스트하려면 디버거를 연결하지 않고 앱을 실행합니다.

앱에 미들웨어 추가

를 호출 AddRequestTimeouts하여 요청 시간 제한 미들웨어를 서비스 컬렉션에 추가합니다.

UseRequestTimeouts를 호출하여 요청 처리 파이프라인에 미들웨어를 추가합니다.

참고 항목

  • 명시적으로 호출 UseRoutingUseRequestTimeouts 하는 앱에서 다음을 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.

정책에서 대리자 사용

클래스에는 RequestTimeoutPolicyWriteTimeoutResponse 시간 제한이 트리거될 때 응답을 사용자 지정하는 데 사용할 수 있는 속성이 있습니다.

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.

참고 항목