Netty解码http请求获取URL乱码

解决方案

获取URI时,使用URLDecoder进行解码

    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只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号,即

只有字母和数字[0-9a-zA-Z]、一些特殊符号$-_. !*'()[不包括双引号]、以及某些保留字(空格转换为 ),才可以不经过编码直接用于URL,如果URL中有汉字,就必须编码后使用。

  • URLDecoder类包含一个decode(String s,String enc)静态方法,它可以将application/x-www-form-urlencoded MIME字符串转成编码前的字符串;
  • URLEncoder类包含一个encode(String s,String enc)静态方法,它可以将中文字符及特殊字符用转换成application/x-www-form-urlencoded MIME字符串。

2、使用URLEncoder.encode编码

public static String urlEncode(String urlToken) {
    String encoded = null;
    try {
        //用URLEncoder.encode方法会把空格变成加号( ),encode之后在替换一下
        encoded = URLEncoder.encode(urlToken, "UTF-8").replace(" ", " ");
    } 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对象

重要字段:

ByteBuf cumulation;     //缓存
private Cumulator cumulator = MERGE_CUMULATOR; //累计器
private boolean singleDecode;  
private boolean first; //是否第一次解码
private boolean firedChannelRead;
//状态码
private byte decodeState = STATE_INIT;
private int discardAfterReads = 16; //解码次数阈值,用来删除已读数据
private int numReads; //解码次数

介绍一下累计器:Cumulator类是干什么的

它的本类中的内部类,而且还是一个接口,只提供了方法。它的实现,只有匿名类,所以就是开头的静态两个字段了。

public interface Cumulator {
    ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
}

也就是我们默认使用的cumulator->MEGRE_CUMULATOR,我们看看它是如何实现的cumulator接口

public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
    //参数:ByteBuf的分配器,本类中的ByteBuf,传递过来的ByteBuf
    @Override
    public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
        if (!cumulation.isReadable() && in.isContiguous()) {
            累加的不可读(比如空缓存),且新的是连续的
            cumulation.release(); //释放
            return in;
        }
        try {
            final int required = in.readableBytes(); //返回可读区域
            //可读区域,大于累加器中的可写区域, 或者累加器只能读
            if (required > cumulation.maxWritableBytes() ||
                    (required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1) ||
                    cumulation.isReadOnly()) {
                return expandCumulation(alloc, cumulation, in); //扩充累计器
            }
            //写入到累计器中
            cumulation.writeBytes(in, in.readerIndex(), required);
            in.readerIndex(in.writerIndex()); //调整in的读指针到写的位置,那么可读区域为0
            return cumulation;
        } finally {
            in.release();  //释放ByteBuf
        }
    }
};

这个类的实现方法,很重要,因为下面的ChannelRead()方法的核心就是调用上面的方法,

重要方法:channelRead()

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (msg instanceof ByteBuf) { //判断传入的 是否是ByteBuf对象
        CodecOutputList out = CodecOutputList.newInstance();
        try {
            first = cumulation == null;  //如果为null,说明是第一次
            cumulation = cumulator.cumulate(ctx.alloc(),
                    first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg); //判断解码器是否缓存了没有解码完成的半包信息
            callDecode(ctx, cumulation, out);                               //如果为空,说明第一次解析,或者上一次的已经解析完成。
        }...
        } finally {
            try {
                if (cumulation != null && !cumulation.isReadable()) { //不为空,不可读,要释放
                    numReads = 0;
                    cumulation.release();
                    cumulation = null;
                } else if (  numReads >= discardAfterReads) {//读取数据的次数大于阈值,则尝试丢弃已读数据
                    numReads = 0;
                    discardSomeReadBytes();
                }
                int size = out.size();
                firedChannelRead |= out.insertSinceRecycled(); //有被添加或者设置,表示已经读过了
                fireChannelRead(ctx, out, size);   //尝试传递数据
            } finally {
                out.recycle();
            }
        }
    } else {
        ctx.fireChannelRead(msg);  //其他类型进行传递
    }
}

先看ctx.alloc()方法就得到的什么,它对应上面cumulator()的第一个参数,返回的自然是Bytebuf的分配器

public ByteBufAllocator alloc() {
    return channel().config().getAllocator(); //返回ByteBufAllocator,要嘛是池化的,要嘛是非池化
}

如何对msg中的信息,进行转移到本地的cumulator中,

之后调用callDecode进行解码

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    try {
        while (in.isReadable()) {//可读
            int outSize = out.size();  //数量
            if (outSize > 0) { //一个一个的把解析出来的结果,传递下去
                fireChannelRead(ctx, out, outSize); //传递
                out.clear();  //已经传播 的,要清理掉。
                if (ctx.isRemoved()) {  //上下文被移除了,就不处理了
                    break;
                }
                outSize = 0;
            }
            //继续编解码,
            int oldInputLength = in.readableBytes();
            decodeRemovalReentryProtection(ctx, in, out); //解码  ★
            if (ctx.isRemoved()) {
                break;
            }
            if (outSize == out.size()) { //没有新生成的消息,
                if (oldInputLength == in.readableBytes()) { //没有读取数据
                    break;
                } else {  continue;  }
            }
 
            if (oldInputLength == in.readableBytes()) { //解码器没有读取数据
               ... }
 
            if (isSingleDecode()) { //是否每次只解码一条,就返回
                break;
        ...
}

这个方法具体的逻辑就是解码 传播解码出的pojo,传播pojo就是调用context.fire..方法,没什么好看的,我们之前的pipline讲解的时候,已经讲过了事件传播的逻辑,这里我们重点看解码方法

decodeRemovalReentryProtection(),它其实也没有实现解码,功能,我们前面说过,本类只是一个抽象类,具体的解码要交给它的子类,实现类,比如我们之前 章节,解码器的使用部分,我们自定义的Handler继承这个类,它的里面才真正实现了解码的功能。!

final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
        throws Exception {
    decodeState = STATE_CALLING_CHILD_DECODE; //状态,调用子类 解码
    try {
        decode(ctx, in, out); //调用子类解码
    } finally {
        boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
        decodeState = STATE_INIT; //处理完了,设置为初始化
        if (removePending) {
            fireChannelRead(ctx, out, out.size());
            out.clear();
            handlerRemoved(ctx);
        }
    }
}

再来看,丢弃已读部分的ByteBuf

protected final void discardSomeReadBytes() {
    if (cumulation != null && !first && cumulation.refCnt() == 1) {
        cumulation.discardSomeReadBytes();
    }
}

它其实是一个入口,具体的实现是在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的子类,也就是实现了具体的decode,解决半包,粘包问题,通过固定长度的手法。

它的字段只有一个,frameLength,固定的长度大小,

方法也就是构造方法 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);
    }
}

调用重载的方法,简单判断一下长度,然后读取

protected Object decode(
        @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
    if (in.readableBytes() < frameLength) {
        return null;
    } else {
        return in.readRetainedSlice(frameLength); //AbstracByteBuf实现的方法
    }
}

3.MessageToByteEncoder

位于outbound中,功能是将pojo编码成为Byte[]组,

两个字段:

private final TypeParameterMatcher matcher;  //类型参数匹配器,针对范型的
private final boolean preferDirect;

第一个字段更重要,是以前没见过的类型,用来处理范型进行匹配的,主要运用在构造方法中。

3.1 TypeParameterMatcher

先看字段,就一个成员Noop,匿名类,实现的是自己!也就实现了match方法,返回true。逻辑简单。

private static final TypeParameterMatcher NOOP = new TypeParameterMatcher() {
    @Override
    public boolean match(Object msg) {
        return true;
    }
};

常用方法:

get(),跟回传进来的Class对象,判断是哪个类型,如果是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 {    //内部类,封装Class,match匹配的时候,利用反射,判断是否是这个类的实例
            matcher = new ReflectiveMatcher(parameterType);
        }
        getCache.put(parameterType, matcher); //放入缓存中
    }
 
    return matcher;
}

内部类,和上面的NOOP逻辑相似

private static final class ReflectiveMatcher extends TypeParameterMatcher {
    private final Class<?> type;
    ReflectiveMatcher(Class<?> type) { this.type = type; }
    @Override  //判断 msg是否是type的实现类
    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;  //类型转换
            buf = allocateBuffer(ctx, cast, preferDirect); //分配空间
            try {
                encode(ctx, cast, buf); //调用子类编码方法
            } 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中的方法,就会调用上方的matcher进行匹配

public boolean acceptOutboundMessage(Object msg) throws Exception {
    return matcher.match(msg);
}

然后分配一个空间,作为ByteBuf

protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
                           boolean preferDirect) throws Exception {
    if (preferDirect) { //是否是直接内存
        return ctx.alloc().ioBuffer();
    } else {
        return ctx.alloc().heapBuffer();
    }
}

再调用子类,实现类的encode()方法,进行编码,同样也就是调用ByteBuf的写入方法,将对象写进去。 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持Devmax。

解决Netty解码http请求获取URL乱码问题的更多相关文章

  1. 通过AFNetworking 2.0上传iOS图像

    我一直在寻找新的AFNetworking2.0上传图像的例子.但是我正在撞墙,无法弄清楚代码有什么问题.所以这是我使用的代码TIA解决方法我最终使用了多部分请求

  2. ios – 在Objective-C中发送分块的HTTP 1.1请求

    我有以下问题:我正在创建一个非常大的SOAP请求(数据是一个编码为Base64字符串的视频),因此我不能将其作为原始SOAP请求发送,而是需要在HTTP1.1块中发送它.我似乎无法弄明白该怎么做.我在这里使用了代码:WhatarealternativestoNSURLConnectionforchunkedtransferencoding但它似乎没有做我认为应该做的事情–我可以看到请求作为单个请求

  3. ios – Netty Channel关闭检测

    我正在使用netty和ios构建服务器客户端应用程序,当用户在他/她的ios设备上关闭WiFi时,我遇到问题,netty服务器不了解它.服务器需要知道为该用户进行清理并将其设置为离线状态,但是当用户再次尝试连接时,服务器只是告诉他他/她已经在线.解决方法如果我正确地理解了您的问题:您想要监听服务器端的客户端通道关闭事件,并进行一些会话清理,在Netty有两种方式来收听频道封闭事件:1)如果您的服务

  4. ios – Swift2.0 HTTP请求无法正常工作

    参见英文答案>TransportsecurityhasblockedacleartextHTTP23个HelloStackoverflow,我将swift应用程序移动到Swift2.0后,我不断收到此错误:我看了下面的链接https://forums.developer.apple.com/thread/5835并将以下代码添加到我的info.plist中它仍然不起作用,任何人都有替代解决方案?解

  5. 如何在Swift语言中创建http请求

    概述:本文通过实例从同步和异步两种方式上回答了”如何在Swift语言中创建http请求“的问题。如果你对Objective-C比较了解的话,对于如何创建http请求你一定驾轻就熟了,而新语言Swift与其相比只有语法上的区别。但是,对才接触到这个崭新平台的初学者来说,他们仍然想知道“如何在Swift语言中创建http请求?”。在这里,我将作出一些建议来回答上述问题。常见的创建http请求的方式主要

  6. Swift HTTP请求集合

    )->Voidinprintln})带参数的get请求varrequest=HTTPTask()request.GET("http://google.com",parameters:["param":"param1","array":["firstarrayelement","second","third"],"num":23],arial;font-size:14px;line-height:21px">println("response:\(response.responSEObject!)")POS

  7. swift 自带HTTP请求

    )->Voidiniferror!=nil{println(error)}else{println(data)}}funcHTTPGet(url:String,callback:(String,String?)->Void){varrequest=NSMutableuRLRequest(URL:NSURL(string:url)!)HTTPsendRequest(request,callback)}funcHTTPsendRequest(request:NSMutableuRLRequest,callba

  8. Swift-网络请求http的基础学习

    swift发起网络请求自然有他自己的处理类NSURLRequest。这个跟android中httpClient的作用都是一样的。因此本篇博客只是记录一下这个过程,代码比较简单。这里封装了一个处理请求的httpController类。这个请求时异步处理的值得注意的是我写了一个delegate类来数据回调。viewcontroller顾名思义就是一个控制器,为了遵循MVC思想原则,我们不应该在控制器中写太多的逻辑代码,可以交给model层来出来,控制器负责调用就可以。这样代码更加易读。

  9. openstack swift和wsgi源码分析1 HTTP请求处理过程

    分析proxy-server代理服务的执行流程,其他的三个主要服务accountserver,containerserver,objectserver执行过程通proxyserver类似。入口函数调用run_wsgi,此函数完成以下工作:下面重点研究下process_request函数是如何把消息转化为HTTP的request对象这一过程。process_request函数,生成HttpProtocol对象,并执行init操作,注意,HttpProtocol对象自身没有init函数,所以会调用父类的父类的

  10. Swift: 用Alamofire做http请求,用ObjectMapper解析JSON

    ObjectMapper是一个双向的转化工具。最外面的一层是:所以对应的model定义是这样的:最重要的是先importObjectMapper。这样ObjectMapper就知道应该如何解析json字符串到对应的类对象中了。除了上面提到的,ObjectMapper还有很多其他的功能。最开始介绍使用Alamofire请求并成功返回之后,我们只是把字符串打印了出来。response.result.value取出了http请求之后返回的json串。不过Github奇葩的返回的结果就是一个JSONArray,居

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部