请求和错误处理

在ZKWeb中处理Http请求会使用Http请求处理器(IHttpRequestHandler),
上一篇介绍的控制器只是其中的一个处理器.
自己实现处理器可以处理更复杂的url, 例如返回静态文件等.

Http请求的整体流程图

Http请求的整体流程图

流程的伪代码请看最下面.

Http请求处理器

这是IHttpRequestHandler的一个简单的例子,
访问/example/handler/xxx的时候会返回the path is xxx.

[ExportMany]
public class HttpRequestHandlerExample : IHttpRequestHandler {
    public const string Prefix = "/example/handler/";
    public void OnRequest() {
        var request = HttpManager.CurrentContext.Request;
        var response = HttpManager.CurrentContext.Response;
        if (request.Path.StartsWith(Prefix)) {
            new PlainResult("the path is " + request.Path.Substring(Prefix.Length))
                .WriteResponse(response);
            response.End();
        }
    }
}

需要注意的是, 请求处理器会按注册顺序的相反顺序调用,
越后面注册的处理器调用的越前,
如果前面的处理器调用了response.End(), 就不会再调用后面的处理器了.

Http请求预处理器

有的情况下, 您可能想给所有Http请求添加一个自定义的Header,
但是如果使用IHttpRequestHandler, 又很难保证注册的处理器是最后注册的.
ZKWeb针对这种情况提供了另外一个接口IHttpRequestPreHandler.

[ExportMany]
public class AddVersionHeaderHandler : IHttpRequestPreHandler {
    public void OnRequest() {
        var response = HttpManager.CurrentContext.Response;
        response.AddHeader("X-ZKWeb-Version", Application.FullVersion);
    }
}

IHttpRequestPreHandler可以保证在所有IHttpRequestHandler前调用,
注意IHttpRequestPreHandler中不应该写入Response和调用response.End().

Http请求的后处理器

同样的, 如果您想在所有Http请求处理后执行一些自定义的操作,
可以使用IHttpRequestPostHandler接口, 这个接口的使用跟上面一样,
只是它会保证在所有IHttpRequestHandler之后执行.

Http请求包装器

有时候您可能会想同时使用预处理器和后处理器, 也就是想把请求嵌套在using里面,
同时使用上面的IHttpRequestPreHandlerIHttpRequestPostHandler可能比较困难.
这时候可以使用IHttpRequestHandlerWrapper.

这个接口的使用跟Owin, Asp.Net Core的中间件比较相似.

[ExportMany]
public class ExampleWrapper : IHttpRequestHandlerWrapper {
    public Action WrapHandlerAction(Action action) {
        return () => {
            using (SomeScope()) {
                action();
            }
        };
    }
}

如果您只考虑控制器, 也可以使用前一篇提到的Action过滤器.

Http错误处理器

ZKWeb提供了一个专门的错误处理器,
注册IHttpRequestErrorHandler接口即可全局处理请求中的错误.

[ExportMany]
public class HttpErrorHandlerExample : IHttpRequestErrorHandler {
    public void OnError(Exception ex) {
        var httpEx = ex as HttpException; // HttpException in ZKWebStandard.Web
        if (httpEx != null && httpEx.StatusCode == 404) {
            var response = HttpManager.CurrentContext.Response;
            new PlainResult("custom 404 page").WriteResponse(response);
            response.End();
        }
    }
}

如果有多个错误处理器, 会按注册顺序的相反顺序调用,
如果其中一个调用了response.End()则不继续调用.

Http请求的整体流程

上面的处理器的整体调用流程是:

try {
    try {
        按注册顺序枚举IHttpRequestPreHandler {
            调用OnRequest函数
        }
        处理函数 = new Action ({
            按注册顺序的相反顺序枚举IHttpRequestHandler {
                调用OnRequest函数 <= 控制器会在这里调用
            }
            抛出404错误
        })
        按注册顺序枚举IHttpRequestHandlerWrapper {
            处理函数 = wrapper.WrapHandlerAction(处理函数)
        }
        调用处理函数
    } finally {
        按注册顺序枚举IHttpRequestPostHandler {
            调用OnRequest函数
        }
    }
} catch (代表请求结束的例外) {
    处理请求完成
} catch (Exception ex) {
    try {
        按注册顺序的相反顺序枚举IHttpRequestErrorHandler {
            调用OnError函数
        }
    } catch (代表请求结束的例外) {
        处理错误完成
    }
}

ZKWeb默认注册的IHttpRequestHandler就是ControllerManager,
而默认注册的IHttpRequestErrorHandler则会记录错误日志到App_Data\logs下.