在最新的 ASP.NET Core 8.0 中提供了一个新的功能:请求超时中间件,它可以让我们为 HTTP 请求设置超时限制,从而提高应用程序的健壮性和响应能力。
什么是请求超时中间件?
请求超时中间件可以在请求处理时间过长时取消请求,从而避免资源的浪费和性能的下降。它的原理是在请求开始时创建一个定时器,如果在指定的时间内请求没有完成,那么中间件就会通过设置响应的状态码和取消令牌来终止请求。
为什么要使用请求超时中间件?
使用请求超时中间件有以下几个好处:
提高应用程序的可靠性:如果请求处理时间过长,可能会导致应用程序的吞吐量降低,内存占用增加,甚至出现死锁或异常。通过设置合理的超时限制,可以避免这些问题,保证应用程序的正常运行。
提高用户体验:如果请求处理时间过长,用户可能会感到不安,甚至放弃等待。通过设置合理的超时限制,可以及时向用户反馈请求的状态,从而提高用户的满意度。
如何配置请求超时中间件?
在配置请求超时中间件之前,我们创建一个简单的 Web API 项目。
首先,我们创建一个简单的Animal
类:
// 定义一个表示动物的类
public class Animal
{
// 动物的名称,必填
public required string Name { get; set; }
// 动物的种类,必填
public required string Species { get; set; }
// 动物的年龄,可选
public int? Age { get; set; }
}
然后,我们添加一个服务类:
// 定义一个提供动物信息的服务类
public class AnimalService : IAnimalService
{
// 定义一个异步方法,用于获取动物信息
public async Task<Animal> GetAnimalAsync(CancellationToken cancellationToken)
{
// 模拟一个耗时的操作,延迟两秒钟
await Task.Delay(2000, cancellationToken);
// 返回一个动物对象
return new()
{
Name = "Tom",
Species = "Cat",
Age = 5
}; ;
}
}
在这个类中,我们定义了一个异步方法,该方法使用Task.Delay
方法模拟一个耗时的操作,然后返回一个动物对象。
接下来,我们添加请求超时中间件:
// 创建一个 Web 应用程序的构建器
var builder = WebApplication.CreateBuilder(args);
// 添加控制器服务
builder.Services.AddControllers();
// 添加请求超时中间件服务
builder.Services.AddRequestTimeouts();
// 添加动物服务
builder.Services.AddTransient<IAnimalService, AnimalService>();
// 创建一个 Web 应用程序
var app = builder.Build();
// 使用请求超时中间件
app.UseRequestTimeouts();
// 映射控制器
app.MapControllers();
// 运行应用程序
app.Run();
在Program.cs
中,我们先调用AddRequestTimeouts
方法,来添加请求超时中间件服务,再使用UseRequestTimeouts
方法将中间件添加到管道中。
这里我们需要注意几点:
将中间件添加到应用不会自动触发超时,必须显式配置超时限制;
UseRequestTimeouts
方法调用必须放在UseRouting
方法之后;在调试模式下运行时,超时中间件不会触发,它与
Kestrel
超时相同,若要测试请运行未附加调试器的应用。
为控制器配置请求超时中间件
如果我们希望控制某个端点的请求超时,则可以使用RequestTimeout
属性来装饰它。
// 定义一个控制器类
[ApiController]
[Route("[controller]")]
public class AnimalController : ControllerBase
{
// 注入动物服务
private readonly IAnimalService _animalService;
public AnimalController(IAnimalService animalService)
{
_animalService = animalService;
}
// 创建一个端点,用于获取动物信息
[HttpGet("GetAnimal")]
// 使用属性设置请求超时为 1 秒
[RequestTimeout(milliseconds: 1000)]
public async Task<Animal> GetAnimalAsync()
// 调用服务的方法,并传递取消令牌
=> await _animalService.GetAnimalAsync(HttpContext.RequestAborted);
}
上面的代码中,我们创建一个控制器并注入服务,然后创建一个端点 GetAnimalAsync
用于调用服务的方法,并传递一个CancellationToken
。对于GetAnimalAsync
端点,我们使用RequestTimeout
属性将超时设置为 1 秒。
如果希望控制器内的所有端点都具有相同的请求超时,则我们可以使用RequestTimeout
属性来装饰控制器本身。
// 定义一个控制器类,并使用属性设置请求超时为 1 秒
[ApiController]
[Route("[controller]")]
[RequestTimeout(milliseconds: 1000)]
public class AnimalController : ControllerBase
{
// 注入动物服务
private readonly IAnimalService _animalService;
public AnimalController(IAnimalService animalService)
{
_animalService = animalService;
}
// 创建一个端点,用于获取动物信息
[HttpGet("GetAnimal")]
public async Task<Animal> GetAnimalAsync()
// 调用服务的方法,并传递取消令牌
=> await _animalService.GetAnimalAsync(HttpContext.RequestAborted);
}
这样,就可以为控制器中的所有端点应用相同的超时策略,而无需为每个单独设置。
为最小 API 配置请求超时中间件
对于最小的 API,我们有两个选项来配置超时 - 可以使用属性或扩展方法来设置。
使用属性[RequestTimeout]
:
// 创建一个端点,用于获取动物信息
app.MapGet("/GetAnimal",
// 使用属性设置请求超时为 1 秒
[RequestTimeout(milliseconds: 1000)] async (HttpContext context, IAnimalService animalService) =>
{
// 调用服务的方法,并传递取消令牌
return await animalService.GetAnimalAsync(context.RequestAborted);
});
我们创建一个终结点,它接受一个HttpContext
和一个IAnimalService
。在端点内部,我们调用服务的方法,并传递一个CancellationToken
。我们还使用RequestTimeout
属性装饰端点,并将超时设置为 1 秒。
使用扩展WithRequestTimeout():
// 创建一个端点,用于获取动物信息
app.MapGet("/GetAnimal",
async (HttpContext context, IAnimalService animalService) =>
{
// 调用服务的方法,并传递取消令牌
return await animalService.GetAnimalAsync(context.RequestAborted);
})
// 使用扩展方法设置请求超时为 1 秒
.WithRequestTimeout(TimeSpan.FromMilliseconds(1000));
我们从端点中删除超时属性,在末尾添加WithRequestTimeout
方法。并传递了一个TimeSpan
,表示请求应超时的持续时间。
点个关注吧👇
使用配策略置请求超时中间件
我们首先创建命名策略:
// 添加请求超时中间件服务
builder.Services.AddRequestTimeouts(options =>
{
// 使用 AddPolicy 方法创建一个命名策略,名称为 OneSecondTimeout,超时为 1 秒
options.AddPolicy("OneSecondTimeout", TimeSpan.FromMilliseconds(1000));
});
在AddRequestTimeouts
方法中,可以使用AddPolicy
方法来创建一个命名策略。设置策略名称和超时时间后。我们就可以通过将策略名称传递给RequestTimeout
属性或WithRequestTimeout
方法使用。
为控制器配置请求超时策略
// 创建一个端点,用于获取动物信息
[HttpGet("GetAnimal")]
// 使用属性设置请求超时为 OneSecondTimeout 策略
[RequestTimeout("OneSecondTimeout")]
public async Task<Animal> GetAnimalAsync()
// 调用服务的方法,并传递取消令牌
=> await _animalService.GetAnimalAsync(HttpContext.RequestAborted);
// 定义一个控制器类,并使用属性设置策略
[ApiController]
[Route("[controller]")]
// 使用属性设置请求超时为 OneSecondTimeout 策略
[RequestTimeout("OneSecondTimeout")]
public class AnimalController : ControllerBase
{
}
为最小 API 配置请求超时策略
// 创建一个端点,用于获取动物信息
app.MapGet("/GetAnimal",
// 使用属性设置请求超时为 OneSecondTimeout 策略
[RequestTimeout("OneSecondTimeout")] async (HttpContext context, IAnimalService animalService) =>
{
// 调用服务的方法,并传递取消令牌
return await animalService.GetAnimalAsync(context.RequestAborted);
});
// 创建一个端点,用于获取动物信息
app.MapGet("/GetAnimal",
async (HttpContext context, IAnimalService animalService) =>
{
// 调用服务的方法,并传递取消令牌
return await animalService.GetAnimalAsync(context.RequestAborted);
})
// 使用扩展方法设置请求超时为 OneSecondTimeout 策略
.WithRequestTimeout("OneSecondTimeout");
设置全局默认请求超时策略
我们还可以通过DefaultPolicy
属性来设置全局超时策略,它将应用于未定义超时的所有端点。
// 添加请求超时中间件服务
builder.Services.AddRequestTimeouts(options =>
{
// 使用 DefaultPolicy 属性设置全局超时策略,超时为 1.5 秒
options.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromMilliseconds(1500)
};
// 使用 AddPolicy 方法创建一个命名策略,名称为 OneSecondTimeout,超时为 1 秒
options.AddPolicy("OneSecondTimeout", TimeSpan.FromMilliseconds(1000));
});
可以使用下面的代码来测试它:
// 创建一个端点,用于获取动物信息
[HttpGet("GetAnimal")]
// 使用属性设置请求超时为 OneSecondTimeout 策略
[RequestTimeout("OneSecondTimeout")]
public async Task<Animal> GetAnimalAsync()
// 调用服务的方法,并传递取消令牌
=> await _animalService.GetAnimalAsync(HttpContext.RequestAborted);
// 创建另一个端点,用于获取动物信息
[HttpGet("GetAnimalWithDefaultTimeout")]
public async Task<Animal> GetAnimalWithDefaultTimeoutAsync()
// 调用服务的方法,并传递取消令牌
=> await _animalService.GetAnimalAsync(HttpContext.RequestAborted);
我们在控制器中添加两个端点,由于已经配置了全局超时策略,因此第二个端点不做任何其他配置。对GetAnimal
调用将在一秒后超时,对GetAnimalWithDefaultTimeout
的调用将在 1.5 秒后因全局策略而超时。同样的策略也可以应用于最小的 API。
设置请求超时状态码
当达到超时限制时,HttpContext.RequestAborted
中的CancellationToken
会将IsCancellationRequested
设置为true,
Abort()
不会在请求中自动调用,因此应用程序仍可能生成成功或失败响应。默认行为将是返回状态代码 504。请求超时中间件允许我们设置特定的默认状态码。
// 添加请求超时中间件服务
builder.Services.AddRequestTimeouts(options =>
{
// 使用 DefaultPolicy 属性设置全局超时策略,超时为 1.5 秒
options.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromMilliseconds(1500),
// 使用 TimeoutStatusCode 属性设置超时时返回的状态码为 500(内部服务器错误)
TimeoutStatusCode = (int)HttpStatusCode.InternalServerError
};
// 使用 AddPolicy 方法创建一个命名策略,名称为 OneSecondTimeout,超时为 1 秒
options.AddPolicy("OneSecondTimeout", TimeSpan.FromMilliseconds(1000));
});
如上面所示,我们将RequestTimeoutPolicy
实例的TimeoutStatusCode
属性设置为HttpStatusCode.InternalServerError
。通过执行此操作,每个超时的请求都将返回状态代码 500。
禁用特定端点的请求超时
对于一些特殊的端点,我们希望从默认超时策略中排除,这很容易做到。
对于控制器,我们必须使用DisableRequestTimeout
属性来覆盖默认超时策略。
// 创建一个端点,用于获取动物信息
[HttpGet("GetAnimal")]
// 使用属性禁用请求超时
[DisableRequestTimeout]
public async Task<Animal> GetAnimalAsync()
// 调用服务的方法,并传递取消令牌
=> await _animalService.GetAnimalAsync(HttpContext.RequestAborted);
对于最小的 API,我们有两个选项来配置超时,可以使用DisableRequestTimeout
属性或DisableRequestTimeout
扩展方法来设置。
// 创建一个端点,用于获取动物信息
app.MapGet("/GetAnimal",
// 使用属性禁用请求超时
[DisableRequestTimeout] async (HttpContext context, IAnimalService animalService) =>
{
// 调用服务的方法,并传递取消令牌
return await animalService.GetAnimalAsync(context.RequestAborted);
})
// 创建一个端点,用于获取动物信息
app.MapGet("/GetAnimal",
async (HttpContext context, IAnimalService animalService) =>
{
// 调用服务的方法,并传递取消令牌
return await animalService.GetAnimalAsync(context.RequestAborted);
})
// 使用扩展方法禁用请求超时
.DisableRequestTimeout();
请求超时中间件可以帮助我们的应用程序在处理时间不确定的情况下保持稳定和高效。它的配置非常简单,适用于任何 API 或控制器。灵活的设置选项,让我们可以通过自定义不同的策略组合来完全控制应用程序的请求超时行为。
参考资料:
https://learn.microsoft.com/zh-cn/aspnet/core/performance/timeouts?view=aspnetcore-8.0
👇感谢阅读,点赞+分享+收藏+关注👇
文章出自猿惑豁微信公众号