自定义 ocelot 中间件输出自定义错误信息
Intro
ocelot 中默认的 Response 中间件在出错的时候只会设置 StatusCode 没有具体的信息,想要展示自己定义的错误信息的时候就需要做一些自定义了,对 ocelot 中的 Response 中间件做了一些小改动,实现了输出自定义错误信息的功能。
Implement
实现起来其实也很简单,原来的有错误的时候,只设置了 Response 的 StatusCode,我们只需要加一下输出错误信息就可以了,错误信息的格式完全可以自定义,实现代码如下:
public class CustomResponseMiddleware : Ocelot.Middleware.OcelotMiddleware
{private readonly RequestDelegate _next;private readonly IHttpResponder _responder;private readonly IErrorsToHttpStatusCodeMapper _codeMapper;public CustomResponseMiddleware(RequestDelegate next,IHttpResponder responder,IErrorsToHttpStatusCodeMapper codeMapper,IOcelotLoggerFactory loggerFactory): base(loggerFactory.CreateLogger<UrlBasedAuthenticationMiddleware>()){_next = next;_responder = responder;_codeMapper = codeMapper;}public async Task Invoke(HttpContext httpContext){await _next.Invoke(httpContext);if (httpContext.Response.HasStarted)return;var errors = httpContext.Items.Errors();if (errors.Count > 0){Logger.LogWarning($"{errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{httpContext.Request.Path}, request method: {httpContext.Request.Method}");var statusCode = _codeMapper.Map(errors);var error = string.Join(",", errors.Select(x => x.Message));httpContext.Response.StatusCode = statusCode;// output errorawait httpContext.Response.WriteAsync(error);}else{Logger.LogDebug("no pipeline errors, setting and returning completed response");var downstreamResponse = httpContext.Items.DownstreamResponse();await _responder.SetResponseOnHttpContext(httpContext, downstreamResponse);}}
}
相比之前的中间件,主要变化就是对于 Error 的处理,感觉这里 ocelot 可以抽象一下,增加一个接口 ErrorResponser
之类的,现在的 responder 没有直接把错误信息直接传进去造成一些不变,加一个 ErrorResponder
只处理 Error 相关的逻辑,把错误信息直接传进去,这样用户也就可以更为灵活的注册自己的服务来无侵入的修改发生错误时的行为
Sample
要使用这个中间件,就要自己定义 ocelot 中间件的配置,把默认的 Response 中间件替换成自己的中间件即可,示例如下:
app.UseOcelot((ocelotBuilder, ocelotConfiguration) =>
{// this sets up the downstream context and gets the configapp.UseDownstreamContextMiddleware();// This is registered to catch any global exceptions that are not handled// It also sets the Request Id if anything is set globallyocelotBuilder.UseExceptionHandlerMiddleware();// This is registered first so it can catch any errors and issue an appropriate response//ocelotBuilder.UseResponderMiddleware();ocelotBuilder.UseMiddleware<CustomResponseMiddleware>();ocelotBuilder.UseDownstreamRouteFinderMiddleware();ocelotBuilder.UseMultiplexingMiddleware();ocelotBuilder.UseDownstreamRequestInitialiser();ocelotBuilder.UseRequestIdMiddleware();// 自定义中间件,模拟没有权限的情况ocelotBuilder.Use((ctx, next) =>{ctx.Items.SetError(new UnauthorizedError("No permission"));return Task.CompletedTask;});//ocelotBuilder.UseMiddleware<UrlBasedAuthenticationMiddleware>();ocelotBuilder.UseLoadBalancingMiddleware();ocelotBuilder.UseDownstreamUrlCreatorMiddleware();ocelotBuilder.UseHttpRequesterMiddleware();
}).Wait();
除了上面的 Response 中间件,为了测试方便,我还加了一个中间件,直接设置了一个 Error 来方便测试,随便访问一个 Path 来测试一下是不是会有错误信息,可以看到正如预期的结果一样,输出了我们自定义的错误信息
More
完整示例可以从 Github 上获取 https://github.com/WeihanLi/AspNetCorePlayground/tree/master/OcelotDemo
Reference
https://github.com/WeihanLi/AspNetCorePlayground/blob/master/OcelotDemo/OcelotMiddleware/CustomResponseMiddleware.cs
https://github.com/WeihanLi/AspNetCorePlayground/tree/master/OcelotDemo
https://github.com/ThreeMammals/Ocelot/blob/17.0.0/src/Ocelot/Responder/HttpContextResponder.cs
https://github.com/ThreeMammals/Ocelot/blob/17.0.0/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs