天天看點

NancyFX 第十二章 通道截攔

   所有的好的Web架構都有一套好的通道截攔的機制,Nancy在我看來是處理最好的。那什麼是請求通道那?下面的圖可能說的比較清楚些:

NancyFX 第十二章 通道截攔

   正如名稱中描述的,一個典型的Web請求在到達最終響應前會穿過一定數量的子產品,然後反向通過這些子產品到達浏覽器。

   請求到底要經過多少類型的子產品需要根據架構而定。有的多,有的少。越多的處理,響應的就越慢。

   定位一個輕量級和子產品化的Web架構,它的通道中有很少的環節,使得開發人員可以更好的控制子產品如何、在什麼情況下可以與架構進行互動。

   當我們談論通道截攔的時候,主要是指Nancy公開的幾個主要回調點(鈎子函數),使得你可以在請求鍊的回調點加入你自己的子產品或代碼段。

   到目前為止,你可以見到了Nancy衆多的功能 -- 身份驗證就是一個好的例子。 -- 你可能會想在通道進行中會需要很多代碼量來支撐這個功能。

   事實是,Nancy基本上沒有在身份驗證方面編寫什麼代碼,即使你調用身份驗證的方法,在通道中也不會添加什麼代碼處理。隻有在你添加了this.RequiresAuthentication();行後,請求通道才會進行身份驗證截攔。

   驗證部分也就是注冊了路由子產品的Before 和 After 管道,讓我們看下面的驗證校驗頁面:

using Nancy;
using Nancy.Responses;
using Nancy.Security;
namespace nancybook.modules
{
    public class AuthRoutes : NancyModule
    {
       public AuthRoutes() : base("/auth")
       {
           Before += ctx =>
           {
             return (this.Context.CurrentUser == null)
                  ? new HtmlResponse(HttpStatusCode.Unauthorized)
                  : null;
           };
           Get[@"/"] = _ => View["auth/index"];
        }
    }
}      

   上面的處理其實和 RequiresAuthentication 的功能是一樣的。

    通過這一段代碼,你可以很清晰的看到兩種結果。首先可能會傳回null, 這不會影響到處理程序,其實是不會有什麼影響。

    再者就是傳回一個響應對象(例子中是 403 Unauthorized),程序不會再往下執行,而是直接傳回給用戶端。

  當然你可能已經意識到這不僅能用于身份驗證,還可以做一些資源的檢查,其他條件的校驗或者傳回異常資訊等不一樣的結果。

    假如你想提供某種形式的前端緩存,例如 你可以攔截 Before管道,判斷請求是否已經緩存,如果已經緩存就傳回緩存的版本。隻有在沒有緩存或緩存過期的時候通路路由子產品的處理程式。

     你也可以像下面提供After處理:

After += ctx =>
{

//... Code here ...
}      

    在啟用After管道的時候,你已經可以通路到相應對象了(路由處理程式生成的),通過Nancy上下文就可以對它進行随意的修改。

    比如你可以檢查一些環境變量,在傳回到浏覽器前可以修改已經生成的200 ok 的相應為403響應。

   我想,你可以更加傾向于使用After管道來緩存用戶端請求,下次相同請求就可以查找預先的緩存,而不是再一次到資料庫中讀取。

   值得注意的是附加在路由子產品上的代碼會在該子產品的每次請求中調用,這也就意味着你的代碼要耗時長一點,用戶端的請求響應速度會被減緩。

應用級别的挂接

    你也可以在整個應用級别上注冊Before或After管道,而不隻是在子產品基礎上。啟用應用級别的意味着每個子產品、每個路由都會觸發注冊的挂接,無論怎麼定義。另外,如果你注冊了一個應用級的挂接,并在一個特殊的子產品上也單獨做了注冊挂接,兩個挂接都會被調用,應用級的優先。

    這也意味着在應用級别的挂接上傳回一個響應對象,子產品級别的挂接就不再會被調用。同理,子產品級别的挂接傳回了響應對象,子產品的處理也就不會被調用。

    你可以定義一個bootstrapper ,重載ApplicationStartup 或RequestStartup 方法來注冊應用級别的挂接。這兩個方法會暴露一個Pipelines 參數,它包含BeforeRequest 或AfterRequest 屬性,可以像使用Before 或After挂接一樣來看待。

    你也将會使用到OnError屬性,可以用來在應用系統中實作一個異常處理程式。舉個例子,在你的資料庫檢索代碼中,如果檢索不到一條用戶端請求的記錄,你可以會抛出一個Database Object not found 的自定義異常。你可以使用自定義的bootstrapper

    來攔截這個異常,然後傳回一個404 檔案未找到異常,就類似下面:

using System.Text;
using Nancy;
using Nancy.Authentication.Forms;
using Nancy.Bootstrapper;
using Nancy.Conventions;
using Nancy.Session;
using Nancy.TinyIoc;
namespace nancybook
{
    public class CustomBootstrapper : DefaultNancyBootstrapper
    {
        protected override void ApplicationStartup(
         TinyIoCContainer container,
         IPipelines pipelines)
       {
           base.ApplicationStartup(container, pipelines);
           // Add an error handler to catch our entity not found exceptions
           pipelines.OnError += (context, exception) =>
           {
           // If we've raised an EntityNotFound exception in our data layer
           if (exception is EntityNotFoundException)
           return new Response()
           {
              StatusCode = HttpStatusCode.NotFound,
              ContentType = "text/html",
              Contents = (stream) =>
              {
                  var errorMessage = Encoding.UTF8.GetBytes("Entity not
                  found");
                  stream.Write(errorMessage, 0, errorMessage.Length);
              }
           };
           // If none of the above handles our exception, then pass it on as  a 500       
throw exception; 
           }; 
       } 
   }
}      

   輸出緩存

using System;
using System.Collections.Specialized;
using System.Runtime.Caching;
using Nancy;
namespace nancybook
{
    public class CacheService
    {
       private static readonly NameValueCollection _config = new
          NameValueCollection();
       private readonly MemoryCache _cache = new MemoryCache("NancyCache",
          _config);
       private readonly CacheItemPolicy _standardPolicy = new CacheItemPolicy
       {
           Priority = CacheItemPriority.NotRemovable,
           SlidingExpiration = TimeSpan.FromSeconds(30) // This can be changed
                  
       };
       public void AddItem(string itemKey, Response itemToAdd)
       {
           _cache.Add(new CacheItem(itemKey, itemToAdd), _standardPolicy);
       }
       public Response GetItem(string itemKey)
       {
           return (Response)_cache.Get(itemKey);
       }
   }
}      
protected override void ConfigureApplicationContainer(TinyIoCContainer
container)
{
    base.ConfigureApplicationContainer(container);
    container.Register<CacheService>().AsSingleton();
}      
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using nancybook.Models;
using Nancy;
namespace nancybook.modules
{
    public class CachingRoutes : NancyModule
    {

        readonly CacheService _myCache;
        public CachingRoutes(CacheService myCache) : base("/caching")
        {
            _myCache = myCache;
            Get["/"] = x =>
            {
               var cacheData = new CacheDemo() {WhenRequested = DateTime.Now};
              return View["caching/index.html", cacheData];
            };
           Before += ctx =>
           {
              string key = ctx.Request.Path;
              var cacheObject = _myCache.GetItem(key);
              return cacheObject;
           };

            After += ctx =>
           {
                if(ctx.Response.StatusCode != HttpStatusCode.OK)
                {
                  return;
                }
                string key = ctx.Request.Path;
                if(_myCache.GetItem(key) == null)
                   _myCache.AddItem(key, ctx.Response);
           };
       }
   }
}      
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nancy Demo | Caching Example</title>
<link href="~/content/bootstrap.min.css" rel="stylesheet"
type="text/css"/>
</head>
<body>
<div class="container">
<div class="page-header">
<h1 style="display: inline-block">Nancy Demo <small>Caching
Example</small></h1>
<h1 style="display: inline-block" class="pull-right"><small><a
href="~/" title="Click to return to demo home page">home <span
class="glyphicon glyphicon-home"></span></a></small></h1>
</div>
<h4>This page was requested at <strong class="textsuccess">@Model.WhenRequested</strong></h4>
<br/><br/>
<p class="lead">This example uses before and after filters attached
directly to the module servicing this request.</p>
<p>If you observe the time this page was created when refreshing it,
you'll see the page is handled by an output cache; this cache has a sliding
window of 30 seconds.</p>
<p>
As long as you’re refreshing the page, you'll reset the timer and the
cache will continue to wait 30 seconds after the last request before
expiring. If you request the page then
leave it for 30 seconds before re-requesting it, you'll see you get a
new copy.
</p>93
</div>
<script src="~/scripts/jquery-2.1.3.min.js"></script>
<script src="~/scripts/bootstrap.min.js"></script>
</body>
</html>      

CacheDemo.cs

using System;
namespace nancybook.Models
{
    public class CacheDemo
    {
        public DateTime WhenRequested { get; set; }
    }
}      

總結