IHostedService中通过HttpClient请求swagger.json超时如何解决?
问题根因
- 你现在的写法触发了ASP.NET Core启动流程的时序问题:
IHostedService.StartAsync的执行阶段早于Kestrel服务器启动端口监听的阶段,此时向本地端口发送HTTP请求,当前应用本身还没做好接收请求的准备,请求无法得到响应,最终触发超时报错。
最优解决方案(针对获取当前应用自身的swagger.json场景)
完全不需要发起HTTP请求,直接调用Swagger内置的文档生成器在本地生成文档即可,避免跨网络请求的开销和时序问题,注意需要提前安装Swashbuckle.AspNetCoreNuget包,引用Swashbuckle.AspNetCore.Swagger命名空间即可使用ISwaggerProvider,修改后的代码如下:
public class InitializeCacheService : IHostedService { private readonly IMemoryCache _cache; public static readonly string SwaggerJsonCacheKey = "swagger.json"; private readonly ISwaggerProvider _swaggerProvider; private readonly IServiceProvider _serviceProvider; public InitializeCacheService(IMemoryCache cache, ISwaggerProvider swaggerProvider, IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _swaggerProvider = swaggerProvider; _cache = cache; } public Task StartAsync(CancellationToken cancellationToken) { if (!_cache.TryGetValue(SwaggerJsonCacheKey, out SwaggerDocumentCache cacheEntry)) { // 直接本地生成v1版本的swagger文档,不需要发HTTP请求 var swaggerDoc = _swaggerProvider.GetSwagger("v1"); // 转成你需要的SwaggerDocumentCache格式即可,示例用System.Text.Json序列化 var json = JsonSerializer.Serialize(swaggerDoc); cacheEntry = JsonSerializer.Deserialize<SwaggerDocumentCache>(json); // 建议根据实际需求调整过期时间,3秒过期太频繁会导致频繁生成 var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromHours(24)); _cache.Set(SwaggerJsonCacheKey, cacheEntry, cacheEntryOptions); } return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } }
如果你确实需要通过HTTP请求获取swagger文档(比如目标是外部服务)
不要阻塞应用启动流程,将加载逻辑放到后台异步执行,等待应用完全启动后再执行,示例如下:
public class InitializeCacheService : IHostedService { private IMemoryCache _cache; public static readonly string swaggerJson = "swagger.json"; private readonly HttpClient _httpClient; private readonly IServiceProvider _serviceProvider; private readonly IHostApplicationLifetime _appLifetime; public InitializeCacheService(IMemoryCache cache, HttpClient client, IServiceProvider serviceProvider, IHostApplicationLifetime appLifetime) { _serviceProvider = serviceProvider; _httpClient = client; _cache = cache; _appLifetime = appLifetime; } public Task StartAsync(CancellationToken cancellationToken) { // 注册应用启动完成后的回调,此时Kestrel已经开始监听端口 _appLifetime.ApplicationStarted.Register(async () => { if (!_cache.TryGetValue(swaggerJson, out SwaggerDocumentCache cacheEntry)) { _httpClient.BaseAddress = new Uri("https://localhost:44397"); var response = await _httpClient.GetAsync("swagger/v1/swagger.json", cancellationToken); response.EnsureSuccessStatusCode(); using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); cacheEntry = await JsonSerializer.DeserializeAsync<SwaggerDocumentCache>(responseStream, cancellationToken: cancellationToken); var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromHours(24)); _cache.Set(swaggerJson, cacheEntry, cacheEntryOptions); } }); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } }
内容的提问来源于stack exchange,提问作者Rasik
相关产品推荐
相关产品推荐

