netty自定義url過濾器拋引用異常
問題描述
使用netty構造服務端,使用瀏覽器在位址列輸入訪問地址時,除了會發送目標url之外,瀏覽器還會額外發送一個url–>/favicon.ico,以前是在一個業務處理的hanlder中使用if判斷uri,如果uri是這個的話,就不去執行業務邏輯,但是這樣的話會造成業務程式碼與判斷邏輯的耦合。所以使用一個新的handler去處理,利用netty的執行連完成後序的業務。
handler的構造執行順序如下
HttpRequestDecoder(netty自帶)–>FilterurlHandler–>HttpNettyRequestDecoder(二次解碼器,自定義)–>serviceHandler(業務handler)–>HttpNettyResponseEncoder(二次編碼器,自定義)–>HttpResponseEncoder(netty自帶)
其中filterurlhandler,serviceHandler繼承SimpleChannelInboundHandler
但是當瀏覽器訪問的時候,比如url【/queryapi?index=1】,瀏覽器可以收到netty服務端返回的資訊,但是netty的控制檯會丟擲異常
postman模擬傳送
控制檯異常如下
而且根據異常的堆疊資訊也可以看出,裡面沒有涉及到自己編寫的業務程式碼。
排查
首先是filterUnuseHandler,通過程式碼執行,可以看到,該handler已經生效了(注意紅圈中的FullRequest物件的記憶體地址,稍微會用到),並且通過呼叫firechannelRead方法通知下一個符合入參的handler
上述firechannelRead通知的handler是二次解碼器HttpNettyRequestDecoder,該類是繼承SimpleChannelInboundHandler。
根據執行鏈進入到MessageToMessageDecoder.channelRead方法
HttpNettyRequestDecoder繼承的就是這個類,並且實現了decode方法
會過來繼續看MessageToMessageDecoder.channelRead方法
idea斷點資訊
根據程式碼可知,cast是msg的一個備份,在decode二次解碼之後,在finally中被釋放
同時,在channelRead方法的finally塊中,繼續執行責任鏈,注意入參,此時已經變成了HttpNettyRequest(二次解碼後我們自定義的物件),因為我們在二次解碼器中完成解碼之後,需要將二次解碼物件加入到list中(所以這個意思就是尋找下一個handler,並且該handler的一個入參的型別是HttpNettyRequest)
firechannelRead的執行順序還是和上面說的一樣
最終進入到業務handler中,執行程式碼(注意,此時原先的那個request物件依然存在在記憶體中,DefaultFullHttpRequest@2416,沒有被釋放)
業務程式碼具體執行就不說了,業務類是繼承SimpleChannelInboundHandler,最終業務handler中生成了業務訊息,需要將其編碼為HttpRespone,但是此時是HttpNettyResponse(我們自定義的物件)。
編碼過程(HttpNettyResponse–>HttpResponse)與request的二次解碼過程類似,也是netty在業務程式碼執行完之後,獲得了HttpNettyResponse物件,需找handler,該handler的入參是HttpNettyResponse,進行二次編碼,最終程式碼回到FilterUnuseUrl中
注意紅圈內,原先的request物件依舊存在
執行下一步,退出了重寫方法MessageReceived。由於之前說過,FilterUnuseUrl是繼承SimpleChannelInboundHandler類的,所以返回到父類中
這個finally是不是很熟悉,之前也看到過。同時看finally中釋放的msg的地址,就是我們一開始說留意的那個request物件,所以在finally中,request物件又被釋放
所以request物件被釋放了兩次,所以會丟擲異常。
解決辦法
過濾url的handler不要繼承SimpleChannelInboundHandler,直接繼承ChannelHandlerAdaptor即可。因為request的第一次被釋放引用是在MessageToMessage中,暨二次解碼完畢後,但是這是沒法改的,所以直接繼承介面卡類,避免二次釋放request
最後,不知道這算不算netty的一個bug,就是不能二次解碼器繼承MessageToMessageDecoder,同時另外一個類繼承SimpleChannelInboudHandler,會拋非法引用異常