「造個輪子」——設計 HTTP 請求全域性上下文

crossoverJie發表於2018-10-08

前言

本次 Cicada 已經更新到了

「造個輪子」——設計 HTTP 請求全域性上下文
v1.0.3

主要是解決了兩個 issue,#9(Boss執行緒數好像設定有誤 ) #8(怎麼返回純字串內容不要JSON格式?)

所以本次的主要更新為:

  • Cicada 採用合理的執行緒分配來處理接入請求執行緒以及 IO 執行緒。
  • 支援多種響應方式(以前只有 json,現在支援 text)。
  • 為了滿足上者引入了 context
  • 優雅停機。

其中我覺得最核心也最有用的就是這個 Context,併為此重構了大部分程式碼。

多種響應方式

在起初 Cicada 預設只能響應 json,這一點確實不夠靈活。加上後續也打算支援模板解析,所以不如直接在 API 中加入可讓使用者自行選擇不同的響應方式。

因此調整後的 API 如下。

想要輸出 text/plain 時。

@CicadaAction("textAction")
public class TextAction implements WorkAction {
    @Override
    public void execute(CicadaContext context, Param param) throws Exception {
        String url = context.request().getUrl();
        String method = context.request().getMethod();
        context.text("hello world url=" + url + " method=" + method);
    }
}
複製程式碼

而響應輸出 application/json 時只需要把需要響應的物件寫入到 json() 方法中.

「造個輪子」——設計 HTTP 請求全域性上下文

因此原有的業務 action 中也加入了一個上下文的引數:

/**
 * abstract execute method
 * @param context current context
 * @param param request params
 * @throws Exception throw exception
 */
void execute(CicadaContext context ,Param param) throws Exception;
複製程式碼

下面就來看看這個 Context 是如何完成的。

Cicada Context

先看看有了這個上下文之後可以做什麼。

比如有些場景下我們需要拿到本次請求中的頭資訊,這時就可以通過這個 Context 物件直接獲取。

當然不止是頭資訊:

  • 獲取請求頭。
  • 設定響應頭。
  • 設定 cookie
  • 獲取請求 URL
  • 獲取請求的 method(get/post)等。

其實通過這些特點可以看出這些資訊其實都和一次 請求、響應 密切相關,並且各個請求之間的資訊應互不影響。

這樣的特性是不是非常熟悉,沒錯那就是 ThreadLocal,它可以將每個執行緒的資訊儲存起來互不影響。

ThreadLocal 的原理本次不做過多分析,只談它在 Cicada 中的應用。

CicadaContext.class

先來看看 CicadaContext 這個類的主要成員變數以及方法。

「造個輪子」——設計 HTTP 請求全域性上下文

成員變數是兩個介面 CicadaRequest、CicadaResponse,名稱就能看出肯定是存放請求和響應資料的。

HttpDispatcher.class

想要存放本次請求的上下文自然是在真正請求分發的地方 HttpDispatcher

「造個輪子」——設計 HTTP 請求全域性上下文

這裡改的較大的就是兩個紅框處,第一部分是做上下文初始化及賦值。

第二部分自然就是解除安裝上下文。

先看初始化。

CicadaRequest cicadaRequest = CicadaHttpRequest.init(defaultHttpRequest) ;

首先是將 request 初始化:

CicadaHttpRequest 自然是實現了 CicadaRequest 介面:

「造個輪子」——設計 HTTP 請求全域性上下文

這裡只儲存了請求的 URL、method 等資訊,後續要加的請求頭也存放在此處即可。

Response 也是同理的。

「造個輪子」——設計 HTTP 請求全域性上下文

這兩個具體的實現類都私有化了建構函式,防止外部破壞了整體性。

接著將當前請求的上下文儲存到了 CicadaContext 中。

CicadaContext.setContext(new CicadaContext(cicadaRequest,cicadaResponse));
複製程式碼

而這個函式本質使用的則是 ThreadLocal 來存放 CicadaContext

    public static void setContext(CicadaContext context){
        ThreadLocalHolder.setCicadaContext(context) ;
    }
    
    private static final ThreadLocal<CicadaContext> CICADA_CONTEXT= new ThreadLocal() ;
    
    /**
     * set cicada context
     * @param context current context
     */
    public static void setCicadaContext(CicadaContext context){
        CICADA_CONTEXT.set(context) ;
    }
複製程式碼

處理業務及響應

接著就是處理業務,呼叫不同的 API 做不同響應。

context.text() 來說:

「造個輪子」——設計 HTTP 請求全域性上下文

其實就是設定了對應的響應方式、以及把響應內容寫入了 CicadaResponsehttpContent 中。

業務處理完後呼叫 responseContent() 進行響應:

responseContent(ctx,CicadaContext.getResponse().getHttpContent());
複製程式碼

其實就是在上下文中拿到的響應方式及響應內容返回給客戶端。

解除安裝上下文

最後有點非常重要,那就是 解除安裝上下文

如果這裡不做處理,之後隨著請求的增多,ThreadLocal 裡存放的資料也越來越多,最終肯定會導致記憶體溢位。

所以 CicadaContext.removeContext() 就是為了及時刪除當前上下文。

優雅停機

最後還新增了一個停機的方法。

「造個輪子」——設計 HTTP 請求全域性上下文

其實也就是利用 Hook 函式實現的。

由於目前 Cicada 開的執行緒,佔用的資源都不是特別多,所以只是關閉了 Netty 所使用的執行緒。

如果後續新增了自身的執行緒等資源,那也可以全部放到這裡來進行釋放。

總結

Cicada 已經更新了 4 個版本,雛形都有了。

後續會重點實現模板解析和註解請求路由完成,把 MVC 中的 view 完成就差不多了。

還沒有了解的朋友可以點選下面連結進入主頁瞭解下?。

github.com/TogetherOS/…

「造個輪子」——設計 HTTP 請求全域性上下文

相關文章