Nancy在OnError之后执行AfterRequest操作

时间:2014-04-03 11:11:24

标签: nancy

我想将一些请求计时添加到对Nancy模块的请求的响应头中。我已经在RequestStartup中添加了一些前/后请求处理程序,并添加了头文件没有问题(下面的示例),一切都很好。我还在ApplicationStartup中添加了一个OnError处理程序,以捕获错误并返回一个很好的格式化Json响应。

pipelines.BeforeRequest += ctx =>
    {
        ctx.Items.Add("X-RequestStart", DateTime.Now.ToString());
        return null;
    };

pipelines.AfterRequest += ctx =>
    {
        var now = DateTime.Now;
        try
        {
            //Not got around to forcing the culture on the datetime parsing yet...
            var startTime = DateTime.Parse(ctx.Items["X-RequestStart"].ToString());

            ctx.Response.Headers.Add("X-RequestStart", startTime.ToString(CultureInfo.InvariantCulture));
            ctx.Response.Headers.Add("X-RequestComplete", now.ToString(CultureInfo.InvariantCulture));
            ctx.Response.Headers.Add("X-RequestDuration", (now - startTime).ToString());
        }
        catch (Exception)
        {
            ctx.Response.Headers.Add("X-RequestComplete", now.ToString(CultureInfo.InvariantCulture));
        }
    };

pipelines.OnError += (ctx, exception) =>
    {
        return ErrorResponse.FromException(exception);
    };

然而,我注意到的是,当我抛出错误时,不执行AfterRequest操作 - 因此我没有错误响应的时序头。我已经尝试将请求前/后处理移动到应用程序启动,但这也没有效果。

问题实际上分为两部分,首先,是否可以让框架在执行OnError操作后执行AfterRequest操作,或者是以防止此操作的方式设置管道,其次,应该请求操作之前/之后是RequestStartup或ApplicationStartup的一部分吗? ApplicationStartup对于错误处理似乎是明智的,而RequestStartup似乎对于与响应头进行交互是合理的,因为它应该基于每个请求,但是我不确定是否存在这样的约定,或者我的假设是否不正确。

1 个答案:

答案 0 :(得分:0)

不幸的是,这在NancyFx中似乎不可能。我详细了解了源代码,特别是DefaultRequestDispatcherDispatch捕获在路由处理期间抛出的任何异常,调用ResolveErrorResult,然后从协商器获取响应。似乎没有可扩展点来修改以这种方式生成的响应。

在我看来,这是开发人员应该考虑解决的疏忽。

public async Task<Response> Dispatch(NancyContext context, CancellationToken cancellationToken)
{
    // TODO - May need to make this run off context rather than response .. seems a bit icky currently
    var resolveResult = this.Resolve(context);

    context.Parameters = resolveResult.Parameters;
    context.ResolvedRoute = resolveResult.Route;

    try
    {
        context.Response = await ExecuteRoutePreReq(context, cancellationToken, resolveResult.Before)
            .ConfigureAwait(false);

        if(context.Response == null)
        {
            context.Response = await this.routeInvoker.Invoke(resolveResult.Route, cancellationToken,
                resolveResult.Parameters, context)
                .ConfigureAwait(false);

            if (context.Request.Method.Equals("HEAD", StringComparison.OrdinalIgnoreCase))
            {
                context.Response = new HeadResponse(context.Response);
            }
        }

        await this.ExecutePost(context, cancellationToken, resolveResult.After, resolveResult.OnError)
            .ConfigureAwait(false);
    }
    catch(Exception ex)
    {
        context.Response = this.ResolveErrorResult(context, resolveResult.OnError, ex);

        if (context.Response == null)
        {
            throw;
        }
    }

    return context.Response;
}

private Response ResolveErrorResult(NancyContext context, Func<NancyContext, Exception, dynamic> resolveResultOnError, Exception exception)
{
    if (resolveResultOnError != null)
    {
        var flattenedException = exception.FlattenInnerExceptions();

        var result = resolveResultOnError.Invoke(context, flattenedException);
        if (result != null)
        {
            return this.negotiator.NegotiateResponse(result, context);
        }
    }

    return null;
}