解決Netty解碼http請(qǐng)求獲取URL亂碼問(wèn)題
Netty解碼http請(qǐng)求獲取URL亂碼
解決方案
獲取URI時(shí),使用URLDecoder進(jìn)行解碼
? ? public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { ? ? ? ? FullHttpRequest fhr = (FullHttpRequest) msg; ? ? ? ? String uri = URLDecoder.decode(fhr.uri().trim().replace("/", "") ? ? ? ? ? ? ? ? .replace("\\", ""), "UTF-8"); ? ? }
原因
1、URLEncoder.encode和URLDecoder.decode
URL只能使用英文字母、阿拉伯?dāng)?shù)字和某些標(biāo)點(diǎn)符號(hào),不能使用其他文字和符號(hào),即
只有字母和數(shù)字[0-9a-zA-Z]、一些特殊符號(hào)$-_.+!*'()[不包括雙引號(hào)]、以及某些保留字(空格轉(zhuǎn)換為+),才可以不經(jīng)過(guò)編碼直接用于URL,如果URL中有漢字,就必須編碼后使用。
URLDecoder
類包含一個(gè)decode(String s,String enc)靜態(tài)方法,它可以將application/x-www-form-urlencoded MIME字符串轉(zhuǎn)成編碼前的字符串;URLEncoder
類包含一個(gè)encode(String s,String enc)靜態(tài)方法,它可以將中文字符及特殊字符用轉(zhuǎn)換成application/x-www-form-urlencoded MIME字符串。
2、使用URLEncoder.encode編碼
public static String urlEncode(String urlToken) { ? ? String encoded = null; ? ? try { ?? ? ? ?//用URLEncoder.encode方法會(huì)把空格變成加號(hào)(+),encode之后在替換一下 ? ? ? ? encoded = URLEncoder.encode(urlToken, "UTF-8").replace("+", "%20"); ? ? } catch (UnsupportedEncodingException e) { ? ? ? ? logger.error("URLEncode error {}", e); ? ? } ? ? return encoded; }
3、使用URLEncoder.encode解碼
public static String urlEncode(String urlToken) { ? ? String decoded = null; ? ? try { ?? ? ? ?decoded =URLDecoder.decode(urlToken, "UTF-8");? ? ? } catch (UnsupportedEncodingException e) { ? ? ? ? logger.error("URLEncode error {}", e); ? ? } ? ? return decoded; }
Netty---編解碼(原理)
1.ByteToMessageDecoder
用于將ByteBuf解碼成為POJO對(duì)象
重要字段:
ByteBuf cumulation; ? ? //緩存 private Cumulator cumulator = MERGE_CUMULATOR; //累計(jì)器 private boolean singleDecode; ? private boolean first; //是否第一次解碼 private boolean firedChannelRead; //狀態(tài)碼 private byte decodeState = STATE_INIT; private int discardAfterReads = 16; //解碼次數(shù)閾值,用來(lái)刪除已讀數(shù)據(jù) private int numReads; //解碼次數(shù)
介紹一下累計(jì)器:Cumulator類是干什么的
它的本類中的內(nèi)部類,而且還是一個(gè)接口,只提供了方法。它的實(shí)現(xiàn),只有匿名類,所以就是開(kāi)頭的靜態(tài)兩個(gè)字段了。
public interface Cumulator { ? ? ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in); }
也就是我們默認(rèn)使用的cumulator->MEGRE_CUMULATOR,我們看看它是如何實(shí)現(xiàn)的cumulator接口
public static final Cumulator MERGE_CUMULATOR = new Cumulator() { ? ? //參數(shù):ByteBuf的分配器,本類中的ByteBuf,傳遞過(guò)來(lái)的ByteBuf ? ? @Override ? ? public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) { ? ? ? ? if (!cumulation.isReadable() && in.isContiguous()) { ? ? ? ? ? ? 累加的不可讀(比如空緩存),且新的是連續(xù)的 ? ? ? ? ? ? cumulation.release(); //釋放 ? ? ? ? ? ? return in; ? ? ? ? } ? ? ? ? try { ? ? ? ? ? ? final int required = in.readableBytes(); //返回可讀區(qū)域 ? ? ? ? ? ? //可讀區(qū)域,大于累加器中的可寫(xiě)區(qū)域, 或者累加器只能讀 ? ? ? ? ? ? if (required > cumulation.maxWritableBytes() || ? ? ? ? ? ? ? ? ? ? (required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1) || ? ? ? ? ? ? ? ? ? ? cumulation.isReadOnly()) { ? ? ? ? ? ? ? ? return expandCumulation(alloc, cumulation, in); //擴(kuò)充累計(jì)器 ? ? ? ? ? ? } ? ? ? ? ? ? //寫(xiě)入到累計(jì)器中 ? ? ? ? ? ? cumulation.writeBytes(in, in.readerIndex(), required); ? ? ? ? ? ? in.readerIndex(in.writerIndex()); //調(diào)整in的讀指針到寫(xiě)的位置,那么可讀區(qū)域?yàn)? ? ? ? ? ? ? return cumulation; ? ? ? ? } finally { ? ? ? ? ? ? in.release(); ?//釋放ByteBuf ? ? ? ? } ? ? } };
這個(gè)類的實(shí)現(xiàn)方法,很重要,因?yàn)橄旅娴腃hannelRead()方法的核心就是調(diào)用上面的方法,
重要方法:channelRead()
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ? ? if (msg instanceof ByteBuf) { //判斷傳入的 是否是ByteBuf對(duì)象 ? ? ? ? CodecOutputList out = CodecOutputList.newInstance(); ? ? ? ? try { ? ? ? ? ? ? first = cumulation == null; ?//如果為null,說(shuō)明是第一次 ? ? ? ? ? ? cumulation = cumulator.cumulate(ctx.alloc(), ? ? ? ? ? ? ? ? ? ? first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg); //判斷解碼器是否緩存了沒(méi)有解碼完成的半包信息 ? ? ? ? ? ? callDecode(ctx, cumulation, out); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //如果為空,說(shuō)明第一次解析,或者上一次的已經(jīng)解析完成。 ? ? ? ? }... ? ? ? ? } finally { ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? if (cumulation != null && !cumulation.isReadable()) { //不為空,不可讀,要釋放 ? ? ? ? ? ? ? ? ? ? numReads = 0; ? ? ? ? ? ? ? ? ? ? cumulation.release(); ? ? ? ? ? ? ? ? ? ? cumulation = null; ? ? ? ? ? ? ? ? } else if (++numReads >= discardAfterReads) {//讀取數(shù)據(jù)的次數(shù)大于閾值,則嘗試丟棄已讀數(shù)據(jù) ? ? ? ? ? ? ? ? ? ? numReads = 0; ? ? ? ? ? ? ? ? ? ? discardSomeReadBytes(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? int size = out.size(); ? ? ? ? ? ? ? ? firedChannelRead |= out.insertSinceRecycled(); //有被添加或者設(shè)置,表示已經(jīng)讀過(guò)了 ? ? ? ? ? ? ? ? fireChannelRead(ctx, out, size); ? //嘗試傳遞數(shù)據(jù) ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? out.recycle(); ? ? ? ? ? ? } ? ? ? ? } ? ? } else { ? ? ? ? ctx.fireChannelRead(msg); ?//其他類型進(jìn)行傳遞 ? ? } }
先看ctx.alloc()方法就得到的什么,它對(duì)應(yīng)上面cumulator()的第一個(gè)參數(shù),返回的自然是Bytebuf的分配器
public ByteBufAllocator alloc() { ? ? return channel().config().getAllocator(); //返回ByteBufAllocator,要嘛是池化的,要嘛是非池化 }
如何對(duì)msg中的信息,進(jìn)行轉(zhuǎn)移到本地的cumulator中,
之后調(diào)用callDecode進(jìn)行解碼
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { ? ? try { ? ? ? ? while (in.isReadable()) {//可讀 ? ? ? ? ? ? int outSize = out.size(); ?//數(shù)量 ? ? ? ? ? ? if (outSize > 0) { //一個(gè)一個(gè)的把解析出來(lái)的結(jié)果,傳遞下去 ? ? ? ? ? ? ? ? fireChannelRead(ctx, out, outSize); //傳遞 ? ? ? ? ? ? ? ? out.clear(); ?//已經(jīng)傳播 的,要清理掉。 ? ? ? ? ? ? ? ? if (ctx.isRemoved()) { ?//上下文被移除了,就不處理了 ? ? ? ? ? ? ? ? ? ? break; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? outSize = 0; ? ? ? ? ? ? } ? ? ? ? ? ? //繼續(xù)編解碼, ? ? ? ? ? ? int oldInputLength = in.readableBytes(); ? ? ? ? ? ? decodeRemovalReentryProtection(ctx, in, out); //解碼 ?★ ? ? ? ? ? ? if (ctx.isRemoved()) { ? ? ? ? ? ? ? ? break; ? ? ? ? ? ? } ? ? ? ? ? ? if (outSize == out.size()) { //沒(méi)有新生成的消息, ? ? ? ? ? ? ? ? if (oldInputLength == in.readableBytes()) { //沒(méi)有讀取數(shù)據(jù) ? ? ? ? ? ? ? ? ? ? break; ? ? ? ? ? ? ? ? } else { ?continue; ?} ? ? ? ? ? ? } ? ? ? ? ? ? ? if (oldInputLength == in.readableBytes()) { //解碼器沒(méi)有讀取數(shù)據(jù) ? ? ? ? ? ? ? ?... } ? ? ? ? ? ? ? if (isSingleDecode()) { //是否每次只解碼一條,就返回 ? ? ? ? ? ? ? ? break; ? ? ? ? ... }
這個(gè)方法具體的邏輯就是解碼+傳播解碼出的pojo,傳播pojo就是調(diào)用context.fire..方法,沒(méi)什么好看的,我們之前的pipline講解的時(shí)候,已經(jīng)講過(guò)了事件傳播的邏輯,這里我們重點(diǎn)看解碼方法
decodeRemovalReentryProtection(),它其實(shí)也沒(méi)有實(shí)現(xiàn)解碼,功能,我們前面說(shuō)過(guò),本類只是一個(gè)抽象類,具體的解碼要交給它的子類,實(shí)現(xiàn)類,比如我們之前 章節(jié),解碼器的使用部分,我們自定義的Handler繼承這個(gè)類,它的里面才真正實(shí)現(xiàn)了解碼的功能。!
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) ? ? ? ? throws Exception { ? ? decodeState = STATE_CALLING_CHILD_DECODE; //狀態(tài),調(diào)用子類 解碼 ? ? try { ? ? ? ? decode(ctx, in, out); //調(diào)用子類解碼 ? ? } finally { ? ? ? ? boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING; ? ? ? ? decodeState = STATE_INIT; //處理完了,設(shè)置為初始化 ? ? ? ? if (removePending) { ? ? ? ? ? ? fireChannelRead(ctx, out, out.size()); ? ? ? ? ? ? out.clear(); ? ? ? ? ? ? handlerRemoved(ctx); ? ? ? ? } ? ? } }
再來(lái)看,丟棄已讀部分的ByteBuf
protected final void discardSomeReadBytes() { ? ? if (cumulation != null && !first && cumulation.refCnt() == 1) { ? ? ? ? cumulation.discardSomeReadBytes(); ? ? } }
它其實(shí)是一個(gè)入口,具體的實(shí)現(xiàn)是在AbstractByteBuf中
public ByteBuf discardSomeReadBytes() { ? ? if (readerIndex > 0) { ? ? ? ? if (readerIndex == writerIndex) { ? ? ? ? ? ? ensureAccessible(); ? ? ? ? ? ? adjustMarkers(readerIndex); ? ? ? ? ? ? writerIndex = readerIndex = 0; ? ? ? ? ? ? return this; ? ? ? ? } ? ? ? ? ? if (readerIndex >= capacity() >>> 1) { ? ? ? ? ? ? setBytes(0, this, readerIndex, writerIndex - readerIndex); ? ? ? ? ? ? writerIndex -= readerIndex; ? ? ? ? ? ? adjustMarkers(readerIndex); ? ? ? ? ? ? readerIndex = 0; ? ? ? ? ? ? return this; ? ? ? ? } ? ? } ? ? ensureAccessible(); ? ? return this; }
2.FixedLengthFrameDecoder
它是ByteToMessageDecoder的子類,也就是實(shí)現(xiàn)了具體的decode,解決半包,粘包問(wèn)題,通過(guò)固定長(zhǎng)度的手法。
它的字段只有一個(gè),frameLength,固定的長(zhǎng)度大小,
方法也就是構(gòu)造方法+decoder()
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { ? ? Object decoded = decode(ctx, in); ? ? if (decoded != null) { ? ? ? ? out.add(decoded); ? ? } }
調(diào)用重載的方法,簡(jiǎn)單判斷一下長(zhǎng)度,然后讀取
protected Object decode( ? ? ? ? @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception { ? ? if (in.readableBytes() < frameLength) { ? ? ? ? return null; ? ? } else { ? ? ? ? return in.readRetainedSlice(frameLength); //AbstracByteBuf實(shí)現(xiàn)的方法 ? ? } }
3.MessageToByteEncoder
位于outbound中,功能是將pojo編碼成為Byte[]組,
兩個(gè)字段:
private final TypeParameterMatcher matcher; ?//類型參數(shù)匹配器,針對(duì)范型的 private final boolean preferDirect;
第一個(gè)字段更重要,是以前沒(méi)見(jiàn)過(guò)的類型,用來(lái)處理范型進(jìn)行匹配的,主要運(yùn)用在構(gòu)造方法中。
3.1 TypeParameterMatcher
先看字段,就一個(gè)成員Noop,匿名類,實(shí)現(xiàn)的是自己!也就實(shí)現(xiàn)了match方法,返回true。邏輯簡(jiǎn)單。
private static final TypeParameterMatcher NOOP = new TypeParameterMatcher() { ? ? @Override ? ? public boolean match(Object msg) { ? ? ? ? return true; ? ? } };
常用方法:
get(),跟回傳進(jìn)來(lái)的Class對(duì)象,判斷是哪個(gè)類型,如果是Object,就是上面NOOP,
public static TypeParameterMatcher get(final Class<?> parameterType) { ? ? final Map<Class<?>, TypeParameterMatcher> getCache = ? ? ? ? ? ? InternalThreadLocalMap.get().typeParameterMatcherGetCache(); ? ? ? TypeParameterMatcher matcher = getCache.get(parameterType); //緩存中獲取 ? ? if (matcher == null) { //未擊中 ? ? ? ? if (parameterType == Object.class) { ? ? ? ? ? ? matcher = NOOP; ? ? ? ? } else { ? ?//內(nèi)部類,封裝Class,match匹配的時(shí)候,利用反射,判斷是否是這個(gè)類的實(shí)例 ? ? ? ? ? ? matcher = new ReflectiveMatcher(parameterType); ? ? ? ? } ? ? ? ? getCache.put(parameterType, matcher); //放入緩存中 ? ? } ? ? ? return matcher; }
內(nèi)部類,和上面的NOOP邏輯相似
private static final class ReflectiveMatcher extends TypeParameterMatcher { ? ? private final Class<?> type; ? ? ReflectiveMatcher(Class<?> type) { this.type = type; } ? ? @Override ?//判斷 msg是否是type的實(shí)現(xiàn)類 ? ? public boolean match(Object msg) { ? ? ? ? return type.isInstance(msg); ? ? } }
3.2 write()方法
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { ? ? ByteBuf buf = null; ? ? try { ? ? ? ? if (acceptOutboundMessage(msg)) { //類型匹配 ? ? ? ? ? ? @SuppressWarnings("unchecked") ? ? ? ? ? ? I cast = (I) msg; ?//類型轉(zhuǎn)換 ? ? ? ? ? ? buf = allocateBuffer(ctx, cast, preferDirect); //分配空間 ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? encode(ctx, cast, buf); //調(diào)用子類編碼方法 ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? ReferenceCountUtil.release(cast); //釋放 ? ? ? ? ? ? } ? ? ? ? ? ? ? if (buf.isReadable()) { ?//可讀 ? ? ? ? ? ? ? ? ctx.write(buf, promise); //傳播 ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? buf.release(); ? ? ? ? ? ? ? ? ctx.write(Unpooled.EMPTY_BUFFER, promise); ? ? ? ? ? ? } ? ? ? ? ? ? buf = null; ? ? ? ? } else { ? ? ? ? ? ? ctx.write(msg, promise); ? ? ? ? } ? ? } ...釋放 }
if中的方法,就會(huì)調(diào)用上方的matcher進(jìn)行匹配
public boolean acceptOutboundMessage(Object msg) throws Exception { ? ? return matcher.match(msg); }
然后分配一個(gè)空間,作為ByteBuf
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg, ? ? ? ? ? ? ? ? ? ? ? ? ? ?boolean preferDirect) throws Exception { ? ? if (preferDirect) { //是否是直接內(nèi)存 ? ? ? ? return ctx.alloc().ioBuffer(); ? ? } else { ? ? ? ? return ctx.alloc().heapBuffer(); ? ? } }
再調(diào)用子類,實(shí)現(xiàn)類的encode()方法,進(jìn)行編碼,同樣也就是調(diào)用ByteBuf的寫(xiě)入方法,將對(duì)象寫(xiě)進(jìn)去。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot2.X整合Spring-Cache緩存開(kāi)發(fā)的實(shí)現(xiàn)
本文主要介紹了SpringBoot2.X整合Spring-Cache緩存開(kāi)發(fā)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07從java源碼分析線程池(池化技術(shù))的實(shí)現(xiàn)原理
這篇文章主要介紹了從java源碼分析線程池(池化技術(shù))的實(shí)現(xiàn)原理,池化技術(shù)是一種編程技巧,當(dāng)程序出現(xiàn)高并發(fā)時(shí),能夠明顯的優(yōu)化程序,降低系統(tǒng)頻繁創(chuàng)建銷(xiāo)毀連接等額外開(kāi)銷(xiāo),下文更多的相關(guān)介紹需要的小伙伴可以參考一下2022-04-04IDEA 中使用 ECJ 編譯出現(xiàn) java.lang.IllegalArgumentException的錯(cuò)誤問(wèn)題
這篇文章主要介紹了IDEA 中使用 ECJ 編譯出現(xiàn) java.lang.IllegalArgumentException問(wèn)題 ,本文內(nèi)容簡(jiǎn)短給大家介紹的好,需要的朋友可以參考下2020-05-05Java利用數(shù)組隨機(jī)抽取幸運(yùn)觀眾如何實(shí)現(xiàn)
這篇文章主要介紹了Java利用數(shù)組隨機(jī)抽取幸運(yùn)觀眾如何實(shí)現(xiàn),需要的朋友可以參考下2014-02-02Maven安裝與配置及Idea配置Maven的全過(guò)程
Maven是一個(gè)項(xiàng)目管理工具,可以對(duì)Java項(xiàng)目進(jìn)行自動(dòng)化的構(gòu)建和依賴管理,下面這篇文章主要給大家介紹了關(guān)于Maven安裝與配置及Idea配置Maven的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02SpringTask實(shí)現(xiàn)定時(shí)任務(wù)方法講解
通過(guò)重寫(xiě)Schedu lingConfigurer方法實(shí)現(xiàn)對(duì)定時(shí)任務(wù)的操作,單次執(zhí)行、停止、啟動(dòng)三個(gè)主要的基本功能,動(dòng)態(tài)的從數(shù)據(jù)庫(kù)中獲取配置的定時(shí)任務(wù)cron信息,通過(guò)反射的方式靈活定位到具體的類與方法中2023-02-02Java數(shù)據(jù)庫(kù)操作庫(kù)DButils類的使用方法與實(shí)例詳解
這篇文章主要介紹了JDBC數(shù)據(jù)庫(kù)操作庫(kù)DButils類的使用方法詳解,需要的朋友可以參考下2020-02-02servlet實(shí)現(xiàn)文件下載的步驟及說(shuō)明詳解
這篇文章主要為大家詳細(xì)介紹了servlet實(shí)現(xiàn)文件下載的步驟及說(shuō)明,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09