Netty 从netty角度分析http协议解析(二)Post方法协议解析HttpPostRequestDecoder

前面提到过,netty解析http协议,会将解析后的数据,分为以下部分:

  1. 一个HttpMessage
  2. 一个或者多个HttpContent
  3. 一个 LastHttpContent

Post 协议分为两种,一种是普通post协议,一种是 multipart/form-data类型。
具体官网例子:https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/upload

HttpPostRequestDecoder

类定义:

public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {

    static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024;

    private final InterfaceHttpPostRequestDecoder decoder;
}

HttpPostRequestDecoder 为netty中处理post请求类,里面有 一个成员变量 decoder,有两种实现:

    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
        ObjectUtil.checkNotNull(factory, "factory");
        ObjectUtil.checkNotNull(request, "request");
        ObjectUtil.checkNotNull(charset, "charset");

        // Fill default values
        if (isMultipart(request)) {
            decoder = new HttpPostMultipartRequestDecoder(factory, request, charset);
        } else {
            decoder = new HttpPostStandardRequestDecoder(factory, request, charset);
        }
    }

即content-type 为 multipart开头的post请求,以及普通标准的post请求。

对于http消息体,主要是放进 HttpContent 中,而主要就是将传入的ByteBuf存放在HttpContent中。

二者的解析,主要是在 方法offer中。

HttpPostStandardRequestDecoder

HttpPostStandardRequestDecoderoffer 方法:

    @Override
    public HttpPostStandardRequestDecoder offer(HttpContent content) {
        checkDestroyed();

        // Maybe we should better not copy here for performance reasons but this will need
        // more care by the caller to release the content in a correct manner later
        // So maybe something to optimize on a later stage
        ByteBuf buf = content.content();
        if (undecodedChunk == null) {
            undecodedChunk = buf.copy();
        } else {
            undecodedChunk.writeBytes(buf);
        }
        if (content instanceof LastHttpContent) {
            isLastChunk = true;
        }
        parseBody();
        if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
            undecodedChunk.discardReadBytes();
        }
        return this;
    }
  1. 将HttpContent内容,拷贝到undecodedChunk中。
  2. 调用 parseBody() 方法对数据进行解析。
    HttpPostStandardRequestDecoderparseBody 方法
    private void parseBody() {
        if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
            if (isLastChunk) {
                currentStatus = MultiPartStatus.EPILOGUE;
            }
            return;
        }
        parseBodyAttributes();
    }
  1. 设置当前状态,如果是最后一个chunk,则将当前状态设置为 MultiPartStatus.EPILOGUE
  2. 执行 parseBodyAttributes 解析参数。
    而解析的方式,则是通过LRCR&= 来进行解析的,简而言之,就是通过换行或者&来分割http属性,而通过=来进行值赋值。
    所以浏览器会进行http解码,以防影响http协议解析。

HttpPostStandardRequestDecoderparseBodyAttributes 部分方法:

            loop: while (sao.pos < sao.limit) {
                char read = (char) (sao.bytes[sao.pos++] & 0xFF);
                currentpos++;
                switch (currentStatus) {
                case DISPOSITION:// search '='
                    if (read == '=') {
                        currentStatus = MultiPartStatus.FIELD;
                        equalpos = currentpos - 1;
                        String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
                                charset);
                        currentAttribute = factory.createAttribute(request, key);
                        firstpos = currentpos;
                    } else if (read == '&') { // special empty FIELD
                        currentStatus = MultiPartStatus.DISPOSITION;
                        ampersandpos = currentpos - 1;
                        String key = decodeAttribute(
                                undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
                        currentAttribute = factory.createAttribute(request, key);
                        currentAttribute.setValue(""); // empty
                        addHttpData(currentAttribute);
                        currentAttribute = null;
                        firstpos = currentpos;
                        contRead = true;
                    }
                    break;
                case FIELD:// search '&' or end of line
                    if (read == '&') {
                        currentStatus = MultiPartStatus.DISPOSITION;
                        ampersandpos = currentpos - 1;
                        setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
                        firstpos = currentpos;
                        contRead = true;
                    } else if (read == HttpConstants.CR) {
                        if (sao.pos < sao.limit) {
                            read = (char) (sao.bytes[sao.pos++] & 0xFF);
                            currentpos++;
                            if (read == HttpConstants.LF) {
                                currentStatus = MultiPartStatus.PREEPILOGUE;
                                ampersandpos = currentpos - 2;
                                sao.setReadPosition(0);
                                setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
                                firstpos = currentpos;
                                contRead = false;
                                break loop;
                            } else {
                                // Error
                                sao.setReadPosition(0);
                                throw new ErrorDataDecoderException("Bad end of line");
                            }
                        } else {
                            if (sao.limit > 0) {
                                currentpos--;
                            }
                        }
                    } else if (read == HttpConstants.LF) {
                        currentStatus = MultiPartStatus.PREEPILOGUE;
                        ampersandpos = currentpos - 1;
                        sao.setReadPosition(0);
                        setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
                        firstpos = currentpos;
                        contRead = false;
                        break loop;
                    }
                    break;
                default:
                    // just stop
                    sao.setReadPosition(0);
                    contRead = false;
                    break loop;
                }
            }

如果解析到正确的一个键值对数据,会调用 addHttpData(currentAttribute) 方法将属性缓存起来。

    protected void addHttpData(InterfaceHttpData data) {
        if (data == null) {
            return;
        }
        List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
        if (datas == null) {
            datas = new ArrayList<InterfaceHttpData>(1);
            bodyMapHttpData.put(data.getName(), datas);
        }
        datas.add(data);
        bodyListHttpData.add(data);
    }

将数据加入到 bodyListHttpData 中。

在这里插入图片描述
content-type: application/x-www-form-urlencoded, 说明是url解码
http请求体为&为分隔符,且用=来赋值。

HttpPostMultipartRequestDecoder

处理 content-type 为 multipart 类型的post请求时,采用 HttpPostMultipartRequestDecoder 来解析处理。
前面处理都一致,核心处理方法 parseBody
HttpPostMultiparRequestDecoderparseBody 方法:

    private void parseBody() {
        if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
            if (isLastChunk) {
                currentStatus = MultiPartStatus.EPILOGUE;
            }
            return;
        }
        parseBodyMultipart();
    }

具体解析body部分:

    private void parseBodyMultipart() {
        if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) {
            // nothing to decode
            return;
        }
        InterfaceHttpData data = decodeMultipart(currentStatus);
        while (data != null) {
            addHttpData(data);
            if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
                break;
            }
            data = decodeMultipart(currentStatus);
        }
    }

整个解析过程较为复杂,结合http请求报文具体看:
在这里插入图片描述

  1. content-typemultipart/form-data; boundary=----WebKitFormBoundaryy1rBvHvbc0IdjF9h
    boundary 代表分隔符,具体看下面请求报文即可。
  2. 接下来是form类型参数
Content-Disposition: form-data; name="getform"

有 分隔符 + 一个回车换行在属性之间区分。

  1. 如果上传文件,http报文形式为:

在这里插入图片描述
如上图,仍然是以 boundary进行分割,有第二行进行说明新的 content-type。

总结

本文分析了netty解析post方法两个类:

  1. HttpPostStandardRequestDecoder:用于解析标准的post请求体,主要用&进行分割,=进行赋值。
  2. HttpPostMultipartRequestDecoder:用于解析content-type为multipart开头的http的post协议,后面必须带boundary进行分割。

关注博主公众号: 六点A君。
哈哈哈,一起研究Netty:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值