HttpPostRequestEncoder源码浅析

文章目录

HttpPostRequestEncoder

java 复制代码
// 实现ChunkedInput接口, 将使用 ChunkedWriteHandler 逐块读取,每次读取1个HttpContent分块
public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {

    // ...    
}

EncoderMode

java 复制代码
// 编码模式
public enum EncoderMode {

    // 传统模式适用于多数情况。已知其不兼容OAUTH认证,OAUTH场景请使用EncoderMode#RFC3986。
    // W3C表单规范建议在处理POST表单数据提交时采用此编码模式。
    RFC1738,

    // 此模式为较新的编码方案,专用于OAUTH认证。
    RFC3986,

    // HTML5规范禁止在multipart/form-data请求中使用混合模式。
    // 更具体地说,这意味着同名提交的多个文件将不会使用混合模式编码,而是会被视为独立字段处理。
    HTML5
}

属性

java 复制代码
@SuppressWarnings("rawtypes")
private static final Map.Entry[] percentEncodings;

static {
    percentEncodings = new Map.Entry[] {
        // * 被替换为 %2A
        new SimpleImmutableEntry<Pattern, String>(Pattern.compile("\\*"), "%2A"),
        // + 被替换为 %20
        new SimpleImmutableEntry<Pattern, String>(Pattern.compile("\\+"), "%20"),
        // ~ 被替换为 %7E
        new SimpleImmutableEntry<Pattern, String>(Pattern.compile("~"), "%7E")
    };
}

// 用于手动添加数据时,将对应的对象转为HttpData
private final HttpDataFactory factory;

// 必须指定1个请求对象
private final HttpRequest request;

// 默认使用的编码
private final Charset charset;

// 是否使用分块传输,默认不使用分块传输
private boolean isChunked;

// 待编码的HttpData数据
private final List<InterfaceHttpData> bodyListDatas;

// 已编码的HttpData数据
final List<InterfaceHttpData> multipartHttpDatas;

// request是否是multipart文件上传请求
private final boolean isMultipart;

// 如果request是文件上传请求,这个就是分隔线
String multipartDataBoundary;

// 若为multipart格式,全局multipart内部可能存在嵌套的multipart(mixed)结构,此为嵌套multipart的分隔线。
// 对此仅允许单层嵌套。
String multipartMixedBoundary;

// 用于检查标头是否已最终确定
private boolean headerFinalized;

// 编码模式
private final EncoderMode encoderMode;

// 已经是最后数据块,下一数据块将为空
private boolean isLastChunk;

// 最后数据块已发送
private boolean isLastChunkSent;

// 当前正在编码处理中的文件上传对象
private FileUpload currentFileUpload;

// 添加文件上传时,当前是否处于混合模式(Mixed Mode)
private boolean duringMixedMode;

// 整体内容主体大小
private long globalBodySize;

// 整体传输进度
private long globalProgress;

// 已编码的HttpData集合 的 迭代器
private ListIterator<InterfaceHttpData> iterator;

// 当前读取块时,正在处理的 currentBuffer
private ByteBuf currentBuffer;

// 当前读取块时,正在处理的 httpData对象
private InterfaceHttpData currentData;

// 在不作 文件上传时,标记这个currentBuffer是key还是value
private boolean isKey = true;

构造方法

java 复制代码
public HttpPostRequestEncoder(HttpRequest request, 
                              boolean multipart) {
    // 创建HttpData的工厂
    this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),  // 16384
         // 请求对象
         request, 
         // 是否需要作multipart文件上传
         multipart,
         // UTF-8
         HttpConstants.DEFAULT_CHARSET, 
         // RFC1738 传统模式
         EncoderMode.RFC1738);
}

public HttpPostRequestEncoder(HttpDataFactory factory, 
                              HttpRequest request, 
                              boolean multipart) {
    this(factory, 
         request, 
         multipart, 
         HttpConstants.DEFAULT_CHARSET, 
         EncoderMode.RFC1738);
}

public HttpPostRequestEncoder(
    HttpDataFactory factory, 
    HttpRequest request, 
    boolean multipart, 
    Charset charset,
    EncoderMode encoderMode) {

    this.request = checkNotNull(request, "request");
    this.charset = checkNotNull(charset, "charset");
    this.factory = checkNotNull(factory, "factory");

    // trace请求不允许使用该编码器
    if (HttpMethod.TRACE.equals(request.method())) {
        throw new ErrorDataEncoderException();
    }

    // 待编码的HttpData列表初始化
    bodyListDatas = new ArrayList<InterfaceHttpData>();

    // 默认初始化为 当前不是最后的分块
    isLastChunk = false;

    // 默认初始化为 最后的分块尚未发送完
    isLastChunkSent = false;

    // 默认初始化 是否作文件上传
    isMultipart = multipart;

    // 已编码的HttpData列表初始化
    multipartHttpDatas = new ArrayList<InterfaceHttpData>();

    // 编码模式
    this.encoderMode = encoderMode;

    // 如果要作文件上传请求
    if (isMultipart) {
        // 生成1个分割线字符串
        initDataMultipart();
    }
}

initDataMultipart

java 复制代码
private void initDataMultipart() {
    // 生成分隔线字符串
    multipartDataBoundary = getNewMultipartDelimiter();
}
getNewMultipartDelimiter
java 复制代码
private static String getNewMultipartDelimiter() {
    return Long.toHexString(PlatformDependent.threadLocalRandom().nextLong());
}

cleanFiles()

java 复制代码
public void cleanFiles() {
    // 将HttpDataFactory实例的requestFileDeleteMap属性中,
    // 该request作为key对应的HttpData集合移除掉,并且遍历该HttpData集合,逐个释放HttpData
    factory.cleanRequestHttpData(request);
}

isMultipart()

java 复制代码
public boolean isMultipart() {
    return isMultipart;
}

initMixedMultipart()

java 复制代码
private void initMixedMultipart() {
    // 生成嵌套部分的分隔线
    multipartMixedBoundary = getNewMultipartDelimiter();
}

getBodyListAttributes()

java 复制代码
public List<InterfaceHttpData> getBodyListAttributes() {
    // 返回 待编码的HttpData列表
    return bodyListDatas;
}

setBodyHttpDatas(List<InterfaceHttpData>)

java 复制代码
public void setBodyHttpDatas(List<InterfaceHttpData> datas)  {
    
    // 传入的 datas 不能为null
    ObjectUtil.checkNotNull(datas, "datas");
    
    // 整体内容主体大小 置为 0
    globalBodySize = 0;
    
    // 清空 待编码的HttpData列表
    bodyListDatas.clear();
    
    // 当前正在编码处理中的文件上传对象
    currentFileUpload = null;
    
    // 添加文件上传时,当前是否处于混合模式(Mixed Mode) 置为false
    duringMixedMode = false;
    
    // 清空 已编码的HttpData列表
    multipartHttpDatas.clear();
    
    // 逐个添加 传入的HttpData列表 
    for (InterfaceHttpData data : datas) {
        addBodyHttpData(data);
    }
}

addBodyAttribute(String name, String value)

java 复制代码
public void addBodyAttribute(String name, String value) {
    
    String svalue = value != null? value : "";
    
    // 使用HttpDataFactory创建Attribute属性对象
    Attribute data = factory.createAttribute(request, 
                                             checkNotNull(name, "name"), 
                                             svalue);
    // 添加 将刚刚创建的Attribute属性对象 
    addBodyHttpData(data);
}

addBodyFileUpload(String name, String filename, File file, String contentType, boolean isText)

java 复制代码
public void addBodyFileUpload(String name, 
                              String filename, 
                              File file, 
                              String contentType, 
                              boolean isText) {
    
    // name不能为null
    checkNotNull(name, "name");
    
    // file不能为null
    checkNotNull(file, "file");
    
    // 文件名如果为null,则默认为空字符串
    if (filename == null) {
        filename = "";
    }
    
    String scontentType = contentType;
    
    String contentTransferEncoding = null;
    
    // 如果contentType为空
    if (contentType == null) {
        // 如果是文本,则contentType设置为text/plain
        if (isText) {
            scontentType = HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE;
        } else {
            // 如果不是文本,则contentType设置为application/octet-stream
            scontentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
        }
    }
    
    // 如果不是文本
    if (!isText) {
        
        // HTTP multipart传输时允许的三种编码机制:7bit、8bit和binary
        //  - 7bit (默认):只能包含ASCII字符(0-127)。数据中的每一行必须以CRLF (\r\n)结束。这是最通用、兼容性最好的格式。
        //  - 8bit:可以包含非ASCII字符(扩展字节,128-255),但每一行仍然有长度限制(通常不超过998个字符),且必须以CRLF结束。它不在字节层面进行编码转换。
        //  - BINARY:可以包含任何数据,没有行长度限制,也不需要遵守CRLF换行规则。它是最自由的格式。
        
        // contentTransferEncoding 设置为 binary
        contentTransferEncoding = HttpPostBodyUtil.TransferEncodingMechanism
                                                  .BINARY.value();
    }
    
    // 创建 FileUpload 文件上传对象
    FileUpload fileUpload = factory.createFileUpload(
        request, 
        name, 
        filename, 
        scontentType,
        contentTransferEncoding, 
        null,  // charset参数
        file.length()
    );
    
    try {
        // 将 file 文件设置到 FileUpload对象 中
        fileUpload.setContent(file);
    } catch (IOException e) {
        throw new ErrorDataEncoderException(e);
    }
    
    // 将此 fileUpload 添加
    addBodyHttpData(fileUpload);
}

addBodyFileUploads(String name, File[] file, String[] contentType, boolean[] isText)

java 复制代码
public void addBodyFileUploads(String name, 
                               File[] file, 
                               String[] contentType, 
                               boolean[] isText) {
    
    if (file.length != contentType.length && file.length != isText.length) {
        throw new IllegalArgumentException("Different array length");
    }
    
    // 逐个添加 文件上传
    for (int i = 0; i < file.length; i++) {
        addBodyFileUpload(name, file[i], contentType[i], isText[i]);
    }
}

*addBodyHttpData(InterfaceHttpData)

java 复制代码
public void addBodyHttpData(InterfaceHttpData data)  {
    
    // 检查标头是否已最终确定, 如果已经确定,则不允许添加HttpData
    if (headerFinalized) {
        throw new ErrorDataEncoderException("Cannot add value once finalized");
    }
    
    // 将 待添加的 HttpData 添加到 bodyListDatas 待编码的HttpData数据集合中
    bodyListDatas.add(checkNotNull(data, "data"));
    
    // 如果不作 文件上传
    if (!isMultipart) {
        
        // 如果 data 是 Attribute
        if (data instanceof Attribute) {
            
            Attribute attribute = (Attribute) data;
            
            try {
                
                // name=value& with encoded name and attribute
                
                // 对key和value重新编码
                String key = encodeAttribute(attribute.getName(), charset);
                String value = encodeAttribute(attribute.getValue(), charset);
                
                // 重新创建Attribute对象
                Attribute newattribute = factory.createAttribute(request, key, value);
                
                // 将重新创建的Attribute对象添加到 已编码的HttpData数据 集合中
                multipartHttpDatas.add(newattribute);
                
                // 整体内容大小 添加 (按name=value&计算)的长度
                globalBodySize += newattribute.getName().length() 
                                     + 1 
                                     + newattribute.length() 
                                     + 1;
                
            } catch (IOException e) {
                throw new ErrorDataEncoderException(e);
            }
        } 
        else if (data instanceof FileUpload) {
            
			// 因为不作 文件上传,所以即便添加FileUpload,也只会把它当作Attribute处理
            
            FileUpload fileUpload = (FileUpload) data;
            
            // name=filename& with encoded name and filename
            
            // 同样 对key和value重新编码
            String key = encodeAttribute(fileUpload.getName(), charset);
            String value = encodeAttribute(fileUpload.getFilename(), charset);
            
            // 创建Attribute对象
            Attribute newattribute = factory.createAttribute(request, key, value);
            
            // 将重新创建的Attribute对象添加到 已编码的HttpData数据 集合中
            multipartHttpDatas.add(newattribute);
            
            // 整体内容大小 添加 (按name=value&计算)的长度
            globalBodySize += newattribute.getName().length() 
                                        + 1 
                                        + newattribute.length() 
                                        + 1;
        }
        
        // 结束
        return;
    }
    
    // 此时,就表示需要作 文件上传 处理
    
    // 如果data是 Attribute对象
    if (data instanceof Attribute) {
        
        // 如果 当前是否处于混合模式
        if (duringMixedMode) {
            
            // 创建1个 InternalAttribute
            InternalAttribute internal = new InternalAttribute(charset);
            
            // 添加分隔线
            internal.addValue("\r\n--" + multipartMixedBoundary + "--");
            
            // 将 刚创建的 InternalAttribute 添加到 已编码的HttpData数据(其实该if里面只干了这一件实事)
            multipartHttpDatas.add(internal);
            
            // 嵌套部分的分隔线 置为 null
            multipartMixedBoundary = null;
            
            // 当前正在编码处理中的文件上传对象 置为 null
            currentFileUpload = null;
            
            // 将 当前是否处于混合模式 置为 false
            duringMixedMode = false;
        }
        
        // 创建1个 InternalAttribute
        InternalAttribute internal = new InternalAttribute(charset);
        
        // 如果前面已经添加过了 HttpData 到 已编码的HttpData数据 集合中
        if (!multipartHttpDatas.isEmpty()) {
            // 此时,就需要添加 回车 + 换行
            internal.addValue("\r\n");
        }
        
        // 添加1个分隔线 + 回车 + 换行
        internal.addValue("--" + multipartDataBoundary + "\r\n");
        
        // 原本要添加的 HttpData 强转为 attribute
        Attribute attribute = (Attribute) data;
        
        // 添加 content-disposition: form-data; name="属性名" 加 \r\n
        internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION 
                          + ": " 
                          + HttpHeaderValues.FORM_DATA 
                          + "; "
                          + HttpHeaderValues.NAME 
                          + "=\"" 
                          + attribute.getName() 
                          + "\"\r\n");
        
        // 添加 content-length: xxx  加 \r\n
        internal.addValue(HttpHeaderNames.CONTENT_LENGTH 
                          + ": " 
                          + attribute.length() 
                          + "\r\n");
        
        // 如果 attribute还指定了编码集
        Charset localcharset = attribute.getCharset();
        if (localcharset != null) {
            // 添加content-type: text/plain; charset=指定的编码集 加 \r\n
            internal.addValue(HttpHeaderNames.CONTENT_TYPE 
                              + ": " 
                              + HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE 
                              + "; " 
                              + HttpHeaderValues.CHARSET 
                              + '='
                              + localcharset.name() 
                              + "\r\n");
        }
        
        // 在 头 和 内容之间 添加1个空行
        internal.addValue("\r\n");
        
        // 将 internal 添加到 已编码的HttpData数据 集合中
        multipartHttpDatas.add(internal);
        
        // 将 真正的数据 data 添加到 已编码的HttpData数据 集合中
        multipartHttpDatas.add(data);
        
        // 整体内容大小 添加 真正的数据data的长度 + internal头部的长度
        globalBodySize += attribute.length() + internal.size();
        
    } 
    else if (data instanceof FileUpload) {
        
        // 如果 data 是 FileUpload文件上传 对象,
        // 则强转为fileUpload
        FileUpload fileUpload = (FileUpload) data;
        
        // 创建1个 InternalAttribute
        InternalAttribute internal = new InternalAttribute(charset);
        
        // 如果前面已经添加过了 HttpData 到 已编码的HttpData数据 集合中
        if (!multipartHttpDatas.isEmpty()) {
            // 此时,就需要添加 回车 + 换行
            internal.addValue("\r\n");
        }
        
        boolean localMixed;
        
        // 如果 当前是否处于混合模式
        if (duringMixedMode) {
            
            // 当前添加的fileUpload的name与currentFileUpload又相同
            if (currentFileUpload != null 
                && currentFileUpload.getName().equals(fileUpload.getName())) {
                
                // 指示下面的代码需要处理混合分隔符
                localMixed = true;
            } 
            else {
                
                // 当前添加的fileUpload的name与currentField不相同了
                
                // internal 添加 --混合分隔线字符串-- 表示混合处理结束了
                internal.addValue("--" + multipartMixedBoundary + "--");
                
                // 将 internal 添加到 已编码的HttpData数据 集合中
                multipartHttpDatas.add(internal);
                
                // 将 multipartMixedBoundary混合分隔符 置为null
                multipartMixedBoundary = null;
                
                // 构建1个新的internal,开始添加1个新的文件上传对象
                internal = new InternalAttribute(charset);
                
                // 因为multipartHttpDatas已经有数据了,所以这里需要加上 \r\n
                internal.addValue("\r\n");
                
                // 指示下面的代码不需要处理混合分隔符
                localMixed = false;
                
                // 记录当前处理的fileUpload
                currentFileUpload = fileUpload;
                
                // 混合模式已经处理完毕,将此标记置为false
                duringMixedMode = false;
                
            }
        } 
        else {
            
            // 在 multipart/form-data 编码中,如果一个字段名对应多个文件(比如同一个 name 有多个文件),那么可以使用 multipart/mixed 嵌套。
            
            // 如果当前的编码模式 不为 HTML5(即编码模式为: RFC1738 或 RFC3986)
            // 并且 当前currentFileUpload 不为null(之前 已添加过 文件上传对象)
            // 并且 当前currentFileUpload的name属性 与 当前要添加的fileUpload的name属性相同
            // (第一次添加FileUpload文件上传对象不可能走这个if,因为currentFileUpload初始的时候就是null;
            // 这个显然是处理当前要添加的FileUpload文件上传对象与上1次添加的FileUpload文件上传对象的name属性名相同时,所要执行的逻辑;)
            // 注意,只有在这个if中(也就是只有在满足当前if中的条件),才会将duringMixed置为true,此处同时也会将localMixed置为true
            if (encoderMode != EncoderMode.HTML5 
                && currentFileUpload != null
                && currentFileUpload.getName().equals(fileUpload.getName())) {
                
                // 重新生成1个分隔符字符串,赋值到 multipartMixedBoundary
                initMixedMultipart();
                
                // 这里就是拿到 上次 已添加过的 文件上传对象 前面添加的internal
                // 因为 multipartHttpDatas 列表在添加每个数据部分时,通常先添加一个 InternalAttribute(作为头部),然后添加实际的数据(Attribute 或 FileUpload)
                // 现在,我们需要修改前一个文件的头部,将其从普通的 form-data 部分改为 multipart/mixed 部分的开始。所以,我们需要获取前一个文件的头部(即 InternalAttribute)并进行修改。
                InternalAttribute pastAttribute = 
                    (InternalAttribute) multipartHttpDatas.get(multipartHttpDatas.size() - 2);
                
                // 整体内容大小 先减去 上次 已添加过的 文件上传对象 前面添加的internal的长度
                // (因为我们将要修改前面添加的这个头部,修改后的大小会变化,所以先减去旧的大小。)
                globalBodySize -= pastAttribute.size();
                
                // 下面所有已经确定的字符串的长度就是139
                // 还需要加上 分隔符的长度
                //          混合分隔符的长度的2倍
                //          文件上传对象的fileName的长度
                //          文件上传对象的name的长度
                /*
                --分隔符 + "\r\n"
                content-disposition: form-data; name="属性名" + "\r\n"
                content-type: multipart/mixed; boundary=混合分隔符 + "\r\n\r\n"
                这里会空1行
                --混合分隔符 + "\r\n"
                content-disposition: attachment
                
                */
                StringBuilder replacement = new StringBuilder(
                        139 
                        + multipartDataBoundary.length() 
                        + multipartMixedBoundary.length() * 2 
                        + fileUpload.getFilename().length() 
                        + fileUpload.getName().length()
                    )
                    .append("--")
                    .append(multipartDataBoundary)
                    .append("\r\n")

                    .append(HttpHeaderNames.CONTENT_DISPOSITION)
                    .append(": ")
                    .append(HttpHeaderValues.FORM_DATA)
                    .append("; ")
                    .append(HttpHeaderValues.NAME)
                    .append("=\"")
                    .append(fileUpload.getName())
                    .append("\"\r\n")

                    .append(HttpHeaderNames.CONTENT_TYPE)
                    .append(": ")
                    .append(HttpHeaderValues.MULTIPART_MIXED)
                    .append("; ")
                    .append(HttpHeaderValues.BOUNDARY)
                    .append('=')
                    .append(multipartMixedBoundary)
                    .append("\r\n\r\n")

                    .append("--")
                    .append(multipartMixedBoundary)
                    .append("\r\n")

                    .append(HttpHeaderNames.CONTENT_DISPOSITION)
                    .append(": ")
                    .append(HttpHeaderValues.ATTACHMENT);

                // 如果当前添加的fileUpload文件上传对象的filename不能为空
                if (!fileUpload.getFilename().isEmpty()) {
                    // 添加 ; filename="文件名"
                    replacement.append("; ")
                        .append(HttpHeaderValues.FILENAME)
                        .append("=\"")
                        .append(currentFileUpload.getFilename())
                        .append('"');
                }

                // 再加上换行符
                replacement.append("\r\n");

                // 将前面的添加的文件上传的前的attribute的 第2个添加的内容 替换为 上面构造的replacement
                pastAttribute.setValue(replacement.toString(), 1);
                // 将前面的添加的文件上传的前的attribute的 第3个添加的内容 清空
                pastAttribute.setValue("", 2);

                // 重新更新 整体的内容大小
                globalBodySize += pastAttribute.size();

                // 指示下面的代码需要处理混合分隔符
                localMixed = true;
                
                // 标记 当前处于mixed混合模式
                duringMixedMode = true;
                
            } else {
                
                // 指示下面的代码不需要处理混合分隔符
                localMixed = false;
                
                // 记录当前正在操作的fileUpload文件上传对象
                currentFileUpload = fileUpload;
                
                // 标记 当前不处于mixed混合模式
                duringMixedMode = false;
            }
        }

        // 需要处理混合分隔符的情况
        if (localMixed) {
            
            // internal 添加 --混合分隔符 + "\r\n"
            internal.addValue("--" + multipartMixedBoundary + "\r\n");

            // 如果 fileUpload对象的filename为空
            if (fileUpload.getFilename().isEmpty()) {
                // internal 添加 content-disposition: attachment + "\r\n"
                internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION 
                                  + ": "
                                  + HttpHeaderValues.ATTACHMENT 
                                  + "\r\n");
            } else {
                
                // 如果 fileUpload对象的filename不为空
                // internal 添加 content-disposition: attachment; filename="file1.txt" + "\r\n"
                internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION 
                                  + ": "
                                  + HttpHeaderValues.ATTACHMENT 
                                  + "; "
                                  + HttpHeaderValues.FILENAME 
                                  + "=\"" 
                                  + fileUpload.getFilename() 
                                  + "\"\r\n");
            }
        } else {
            
            // internal 先添加 --分隔符 + "\r\n"
            internal.addValue("--" + multipartDataBoundary + "\r\n");

            // 如果fileUpload文件上传对象的filename是空的话,
            if (fileUpload.getFilename().isEmpty()) {
                // internal 添加 Content-Disposition: form-data; name="files"; + "\r\n"
                internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION 
                                  + ": " 
                                  + HttpHeaderValues.FORM_DATA 
                                  + "; "
                                  + HttpHeaderValues.NAME 
                                  + "=\"" 
                                  + fileUpload.getName() 
                                  + "\"\r\n");
            } else {
                // 如果fileUpload文件上传对象的filename不是空的话
                // internal 添加 Content-Disposition: form-data; name="files"; filename="file1.txt" + "\r\n"
                internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION 
                                  + ": " 
                                  + HttpHeaderValues.FORM_DATA 
                                  + "; "
                                  + HttpHeaderValues.NAME 
                                  + "=\"" + fileUpload.getName() 
                                  + "\"; "
                                  + HttpHeaderValues.FILENAME 
                                  + "=\"" + fileUpload.getFilename() 
                                  + "\"\r\n");
            }
        }
        
        // 以下部分是需要作 文件上传 时,公共处理逻辑
        
        // 添加 content-length: 长度 + "\r\n"
        internal.addValue(HttpHeaderNames.CONTENT_LENGTH 
                          + ": " 
                          + fileUpload.length() 
                          + "\r\n");
        
        // 添加 content-type: 内容类型
        internal.addValue(HttpHeaderNames.CONTENT_TYPE 
                          + ": " 
                          + fileUpload.getContentType());
        
        // 获取fileUpload文件上传对象的contenTransferEncoding属性
        String contentTransferEncoding = fileUpload.getContentTransferEncoding();
        // 如果fileUpload文件上传对象的contenTransferEncoding属性值为 binary
        if (contentTransferEncoding != null
            && contentTransferEncoding.equals(HttpPostBodyUtil.TransferEncodingMechanism
                                                              .BINARY.value()
                                             )
           ) {
            // internal 继续添加 "\r\n" + content-transfer-encoding: binary + "\r\n\r\n"
            // 注意, 这里最后面换行了2次,相当于在最下面空了1行,这些头部内容和真正内容有1空行
            internal.addValue("\r\n" 
                              + HttpHeaderNames.CONTENT_TRANSFER_ENCODING 
                              + ": "
                              + HttpPostBodyUtil.TransferEncodingMechanism
                                                .BINARY.value() 
                              + "\r\n\r\n");
        } 
        // 如果fileUpload文件上传对象的contenTransferEncoding属性值不是binary, 并且设置了编码集
        else if (fileUpload.getCharset() != null) {
            // internal 继续添加 ; charset=字符集 + "\r\n\r\n"
            // 注意, 这里最后面换行了2次,相当于在最下面空了1行,这些头部内容和真正内容有1空行
            internal.addValue("; " 
                              + HttpHeaderValues.CHARSET 
                              + '=' 
                              + fileUpload.getCharset().name() 
                              + "\r\n\r\n");
        } 
        else {
            // 注意, 这里最后面换行了2次,相当于在最下面空了1行,这些头部内容和真正内容有1空行
            internal.addValue("\r\n\r\n");
        }
        
        // 先将internal 添加到 已编码的HttpData集合 中
        multipartHttpDatas.add(internal);
        
        // 再把 真正的内容data 添加到 已编码的HttpData集合 中
        multipartHttpDatas.add(data);
        
        // 更新 整体内容大小 为加 真正的数据data的长度 + internal头部的长度
        globalBodySize += fileUpload.length() + internal.size();
    }
}

encodeAttribute(String s, Charset charset)

java 复制代码
private String encodeAttribute(String s, Charset charset) {
    
    // s 为 null时的处理
    if (s == null) {
        // 返回 空字符串
        return "";
    }
    
    try {
        
        // 对 s 进行URL编码
        String encoded = URLEncoder.encode(s, charset.name());
        
        // 如果编码模式 为 RFC3986
        if (encoderMode == EncoderMode.RFC3986) {
            // URL编码之后, 还需要作相应的 字符串替换
            // * 被替换为 %2A
            // + 被替换为 %20
            // ~ 被替换为 %7E
            for (Map.Entry<Pattern, String> entry : percentEncodings) {
                String replacement = entry.getValue();
                encoded = entry.getKey().matcher(encoded).replaceAll(replacement);
            }
        }
        
        // 返回编码处理后的字符串
        return encoded;
    } catch (UnsupportedEncodingException e) {
        throw new ErrorDataEncoderException(charset.name(), e);
    }
}

*finalizeRequest()

java 复制代码
public HttpRequest finalizeRequest() throws ErrorDataEncoderException {
    
    // 如果 头部编码 还未完成
    if (!headerFinalized) {
        
        // 如果需要作 文件上传
        if (isMultipart) {
            
            // 构建1个 InternalAttribute
            InternalAttribute internal = new InternalAttribute(charset);
            
            // 如果当前还在处于 混合模式
            if (duringMixedMode) {
                // internal 添加 "\r\n" + --混合分隔符--
                internal.addValue("\r\n--" + multipartMixedBoundary + "--");
            }
            
            // internal 添加 "\r\n--" + 分隔符-- +"\r\n"
            internal.addValue("\r\n--" + multipartDataBoundary + "--\r\n");
            
            // 将internal 添加到 已编码的HttpData集合 中
            multipartHttpDatas.add(internal);
            
            // 混合分隔符 置为null
            multipartMixedBoundary = null;
            
            // 当前currentFileUpload置为null
            currentFileUpload = null;
            
            // 当前还在处于 混合模式 置为false
            duringMixedMode = false;
            
            // 更新 整体内容长度
            globalBodySize += internal.size();
        }
        
        // 标记 头部编码 已完成
        headerFinalized = true;
        
    } else {
        // 如果 头部编码 已完成, 则当前方法不允许再调用
        throw new ErrorDataEncoderException("Header already encoded");
    }

    // 获取请求头信息
    HttpHeaders headers = request.headers();
    
    // 获取content-type头
    List<String> contentTypes = headers.getAll(HttpHeaderNames.CONTENT_TYPE);
    
    // 获取transfer-encoding头
    List<String> transferEncoding = headers.getAll(HttpHeaderNames.TRANSFER_ENCODING);
    
    if (contentTypes != null) {
        
        // 移除全部的 content-type头
        headers.remove(HttpHeaderNames.CONTENT_TYPE);
        
        // 遍历之前的 content-type头
        for (String contentType : contentTypes) {
            
            String lowercased = contentType.toLowerCase();
            
            // 忽略 multipart/form-data、application/x-www-form-urlencoded 这些值
            // 只保留其它值
            if (lowercased.startsWith(HttpHeaderValues
                                      .MULTIPART_FORM_DATA.toString()) 
                || lowercased.startsWith(HttpHeaderValues
                                      .APPLICATION_X_WWW_FORM_URLENCODED.toString())) {
                // ignore
            } else {
                headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
            }
            
        }
    }
    
    // 如果要作 文件上传
    if (isMultipart) {
        
        // 请求头 添加 content-type: multipart/form-data; boundary=分隔符
        String value = HttpHeaderValues.MULTIPART_FORM_DATA 
                            + "; " 
                            + HttpHeaderValues.BOUNDARY 
                            + '='
                            + multipartDataBoundary;
        
        headers.add(HttpHeaderNames.CONTENT_TYPE, value);
        
    } else {

        // 请求头 添加 content-type: application/x-www-form-urlencoded
        headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues
                    .APPLICATION_X_WWW_FORM_URLENCODED);
    }

    // 根据大小判断是否需要分块传输
    long realSize = globalBodySize;
    
    // 如果不作 文件上传, 大小 - 1(移除最后面的&)
    if (!isMultipart) {
        realSize -= 1; 
    }
    
    // 获取 已编码的HttpData集合 的 迭代器
    iterator = multipartHttpDatas.listIterator();

    // 设置 content-length 请求头
    headers.set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(realSize));
    
    // 如果大小超过了 8096(默认)或者 明确需要文件上传
    if (realSize > HttpPostBodyUtil.chunkSize || isMultipart) {
        
        // 标记要使用分块传输(此时,只会返回1个HttpRequest,后续需要将当前encoder通过ChunkedWriterHandler写出去)
        isChunked = true;
        
        // 移除 transfer-encoding中的chunked值,其他值保留
        if (transferEncoding != null) {
            headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
            for (CharSequence v : transferEncoding) {
                if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(v)) {
                    // ignore
                } else {
                    headers.add(HttpHeaderNames.TRANSFER_ENCODING, v);
                }
            }
        }
        // 移除 content-length 请求头,transfer-encoding添加chunked值
        HttpUtil.setTransferEncodingChunked(request, true);

        // 返回包装后的 HttpRequest
        return new WrappedHttpRequest(request);
        
    } else {
        
        // 说明大小 小于 8096(默认)并且 明确不需要文件上传 

        // 则获取1个分块就可以处理完成
        HttpContent chunk = nextChunk();
        
        // 如果request本身就是FullHttpRequest
        if (request instanceof FullHttpRequest) {
            
            FullHttpRequest fullRequest = (FullHttpRequest) request;
            
            // 拿到chunk的内容
            ByteBuf chunkContent = chunk.content();
            
            // 将chunk的内容覆盖掉原FullHttpRequest中的内容
            if (fullRequest.content() != chunkContent) {
                fullRequest.content().clear().writeBytes(chunkContent);
                // 释放chunkContent
                chunkContent.release();
            }
            
            return fullRequest;
            
        } else {
            
            // 返回1个WrappedFullHttpRequest(直接将reques和chunk组合成FullHttpRequest)
            return new WrappedFullHttpRequest(request, chunk);
            
        }
    }
}

isChunked()

java 复制代码
public boolean isChunked() {
    // 返回是否需要 分块传输
    return isChunked;
}

readChunk(ByteBufAllocator)

java 复制代码
@Override
public HttpContent readChunk(ByteBufAllocator allocator) {
    
    // 是否 最后数据块已发送
    if (isLastChunkSent) {
        
        // 如果是,则返回null
        return null;
        
    } else {
        
        // 读取下1个chunk(HttpContent类型)
        HttpContent nextChunk = nextChunk();
        
        // 将读取到的chunk的可读字节数添加到 整体传输进度 中(但其实还未真正传输完成)
        globalProgress += nextChunk.content().readableBytes();
        
        // 返回读取到的chunk
        return nextChunk;
    }
}

nextChunk()

java 复制代码
private HttpContent nextChunk() {
    
    // 如果 已经是最后数据块(下一数据块将为空)
    if (isLastChunk) {
        
        // 标记 最后的数据块已发送 为true
        isLastChunkSent = true;
        
        // 紧接着,添加最后的 空内容(这个就是 最后的数据块,它必定是空内容,但不为null哦)
        return LastHttpContent.EMPTY_LAST_CONTENT;
    }
    
    // 计算size,即 默认大小(8096) 减去 currentBuffer的大小
    //(如果currentBuffer为null,则 减去 0,即直接返回 默认大小8096)
    int size = calculateRemainingSize();
    
    // 如果currentBuffer 大于等于 默认大小(8096)
    if (size <= 0) {

        // 将 currentBuffer中的数据 按默认大小(8096)处理后,返回
        ByteBuf buffer = fillByteBuf();
        
        // 直接将buffer构建为 HttpContent返回
        // (也就是,如果够了8096个字节,则直接从currentBuffer中读取8096个字节)
        return new DefaultHttpContent(buffer);
    }

    // 走到这里,此时 currentBuffer的可读字节数 小于 默认大小(8096),或者 currentBuffer为null
    
    // 如果 currentData 不为 null
    // (既然currentBuffer中的可读字节数不够,那么如果还有currentData,
    //   就继续从currentData中获取数据添加到curretnBuffer中)
    if (currentData != null) {
        
        HttpContent chunk;
        
        // 如果要作 文件上传
        if (isMultipart) {
            // 从 currentData中获取size大小的数据 添加到 currentBuffer 中
            chunk = encodeNextChunkMultipart(size);
        } else {
            chunk = encodeNextChunkUrlEncoded(size);
        }
        
        // 如果chunk不为null(currentBuffer肯定是够了8096,才会返回不为null的chunk)
        if (chunk != null) {
            // 直接返回
            return chunk;
        }
        
        // 计算currentBuffer还差多少到 8096
        size = calculateRemainingSize();
    }
    
    // 使用 已编码的HttpData集合 的 迭代器 判断 是否已无下1个要处理的 已编码的HttpData 了
    if (!iterator.hasNext()) {
        
        // 作 最后1个分块的处理
        return lastChunk();
    }
    
    // size表示当前currentBuffer中还差多少个字节到 默认大小(8096)
    // 当还需要读取size字节数据才能将currentBuffer凑够8096,并且当前还有下一个httpData时,继续循环
    while (size > 0 && iterator.hasNext()) {
        
        // currentData 拿到 已编码的HttpData集合 中下1个 HttpData
        // (currentData记录的 就是 当前需要处理的已编码HttpData)
        currentData = iterator.next();
        
        HttpContent chunk;
        
        // 如果要作 文件上传
        if (isMultipart) {
            // 从 currentData中获取size大小的数据 添加到 currentBuffer 中
            chunk = encodeNextChunkMultipart(size);
        } else {
            // 如果不作文件上传
            chunk = encodeNextChunkUrlEncoded(size);
        }
        
        // 如果 读取的currentBuffer的可读字节数不够 默认大小(8096)
        if (chunk == null) {
            
            // 计算 默认大小(8096) - currentBuffer的可读字节数
            size = calculateRemainingSize();
            
            // 继续循环(不够8096,就继续循环)
            continue;
        }
        
        // 此时读取的 currentBuffer的可读字节数 肯定够8096(够了就直接返回)
        return chunk;
    }
    
    // 如果size够了,那么上面循环就返回了chunk;
    // 如果size不够,那么上面循环就会获取下一个已编码的httpData,继续去读取数据到currentBuffer中;
    
    // 所以走到这里一定是没有下一个已编码的httpData,所以这里就处理最后的chunk了
    return lastChunk();
}
calculateRemainingSize()
java 复制代码
private int calculateRemainingSize() {
    
    // 默认大小 是 8096
    int size = HttpPostBodyUtil.chunkSize;
    
    // 如果currentBuffer不为null,则将 (默认大小) 减去 (currentBuffer的可读字节数大小)并返回
    if (currentBuffer != null) {
        size -= currentBuffer.readableBytes();
    }
    
    return size;
}
lastChunk()
java 复制代码
private HttpContent lastChunk() {
    
    // 标记 已经没有下1个要处理的 已编码的HttpData 了
    isLastChunk = true;
    
    // 如果 当前currentBuffer 为null
    if (currentBuffer == null) {
        
        // 标记 最后的数据块已发送 为true
        isLastChunkSent = true;

        // 紧接着,就把 最后的空内容(这个就是 最后的数据块,它必定是空内容,但不为null哦)
        return LastHttpContent.EMPTY_LAST_CONTENT;
    }
    
    // 此时 当前currentBuffer 不为null
    
    // 将 currentBuffer 赋值给 buffer
    ByteBuf buffer = currentBuffer;
    
    // 将 当前currentBuffer 置为 null
    currentBuffer = null;
    
    // 将buffer封装到 HttpContent 返回
    // (这种情况,就是 还未发送最后的数据块。
    //   此时,就由nextChunk()代码中第一个if判断后,发送最后的数据块)
    return new DefaultHttpContent(buffer);
}
encodeNextChunkMultipart(int sizeleft)
java 复制代码
private HttpContent encodeNextChunkMultipart(int sizeleft) {
    
    // 如果 当前处理的HttpData(即currentData) 是null, 
    if (currentData == null) {
        // 则直接返回null
        return null;
    }
    
    // buffer引用(表示最终要封装到HttpContent中的内容)
    ByteBuf buffer;
    
    // 如果 当前处理的HttpData 是 InternalAttribute(表示处理的是编码的头部信息)
    if (currentData instanceof InternalAttribute) {
        
        // 创建1个 compositeBuffer,并添加 InternalAttribute 的 value(List<ByteBuf> )
        buffer = ((InternalAttribute) currentData).toByteBuf();
        
        // 将 当前处理的HttpData(即 currentData) 重置为 null(因为此时就相当于这个currentData已经赋值给了buffer,就不需要管currentData了)
        currentData = null;
        
    } else {
        
        // 如果 当前处理的HttpData 不是 InternalAttribute(表示处理的是实际的内容)
        
        try {
            
            // 从 currentData 中 最多 获取 sizeLeft个字节(注意,不是读取哦),
            // (如果currentData中的数据不够sizeLeft个字节,则全部获取)
            buffer = ((HttpData) currentData).getChunk(sizeleft);
            
        } catch (IOException e) {
            throw new ErrorDataEncoderException(e);
        }
        
        // 如果 从currentData中 获取的buffer的容量是0,
        // 说明这个currentData已经获取完了(处理完了)
        if (buffer.capacity() == 0) {
            
            // currentData重置为null
            currentData = null;
            
            // 返回null
            return null;
        }
        
    }
    
    // 如果 currentBuffer 是null 
    if (currentBuffer == null) {
        // 就将currentBuffer赋值为 buffer
        currentBuffer = buffer;
    } 
    else {
        // 如果 currentBuffer 不是null,则将 currentBuffer与 buffer 组合起来,
        // 然后 赋值给currentBuffer
        currentBuffer = wrappedBuffer(currentBuffer, buffer);
    }
    
    // 如果 currentBuffer 的可读字节数 小于 默认大小(8096)
    if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) {
        
        // 将currentData 置为 null(注意此时还未将currentBuffer置为null)
        currentData = null;
        
        // 返回null(会继续循环,等到大小凑够8096,就返回HttpContent了,而不是null了)
        return null;
    }
    
    // 此时 currentBuffer 的可读字节数 必定 大于等于 默认大小(8096)
    // 也就是说,只有 currentBuffer 的可读字节数 大于等于 默认大小(8096),当前方法才不会返回null
    
    // 将 currentBuffer中的数据 按默认大小(8096)处理后,返回
    buffer = fillByteBuf();
    
    // 将 currentBuffer处理后的数据 封装为 HttpContent
    return new DefaultHttpContent(buffer);
}
encodeNextChunkUrlEncoded(int sizeleft)
java 复制代码
private HttpContent encodeNextChunkUrlEncoded(int sizeleft) {
    
    // 如果currentData是null,直接返回。因为没有此时没有需要处理的处理
    if (currentData == null) {
        return null;
    }
    
    // 还需要读取的剩余字节数
    int size = sizeleft;
    
    ByteBuf buffer;

    // 果isKey为true,表示当前需要处理键(key)的部分
    if (isKey) {
        
        // 获取当前数据的名称(key),并将其转换为字节序列,存入buffer
        String key = currentData.getName();
        
        // 就是 currentData的name
        buffer = wrappedBuffer(key.getBytes(charset));
        
        // 将isKey设置为false,表示接下来处理值(value)。
        isKey = false;
        
        // 如果currentBuffer为null, 则不需要加上currentBuffer
        // 如果currentBuffer不为null,则需要最前面加上currentBuffer,
        // 后面再 添加上 =
        if (currentBuffer == null) {
            currentBuffer = wrappedBuffer(buffer, 
                                          wrappedBuffer("=".getBytes(charset)));
        } else {
            currentBuffer = wrappedBuffer(currentBuffer, 
                                          buffer, 
                                          wrappedBuffer("=".getBytes(charset)));
        }
        
        // 还需要读取的字节数 减去 currentData的name的字节数和"="的字节数
        size -= buffer.readableBytes() + 1;
        
        // 如果currentBuffer此时已经够了8096
        if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
            // 将currentBuffer的8096读取,并返回
            //(如果currentBuffer刚好满8096,则currentBuffer置为null)
            buffer = fillByteBuf();
            // 返回数据
            return new DefaultHttpContent(buffer);
        }
        
        // 走到这里表示currentBuffer此时不够8096
    }

    // 接下来,处理值(value)部分
    
    try {
        // 从httpData中 (最多)获取size大小的数据
        buffer = ((HttpData) currentData).getChunk(size);
    } catch (IOException e) {
        throw new ErrorDataEncoderException(e);
    }

    ByteBuf delimiter = null;
    
    // 如果 从httpData中获取的数据 的可读字节数 不够size(说明当前httpData已经读完)
    if (buffer.readableBytes() < size) {
        
        // 重置isKey为true
        isKey = true;
        
        // 如果还有下1个httpData,则将&作为分隔符;
        // 如果没有下1个httpData,则分隔符为null;
        delimiter = iterator.hasNext() ? wrappedBuffer("&".getBytes(charset)) : null;
    }

    // 如果buffer的容量为0,表示当前数据currenData已经结束(进入该方法后,就会结束当前方法)
    if (buffer.capacity() == 0) {
        
        // 将currentData设置为null(它已经处理完毕了)
        currentData = null;
        
        // 如果currentBuffer为null
        if (currentBuffer == null) {
            
            // 如果delimiter也为null,则返回null
            if (delimiter == null) {
                return null;
            } 
            // 如果delimiter不为null,currentBuffer设置为delimiter
            else {
                currentBuffer = delimiter;
            }
            
        } else {
            
            // 如果currentBuffer不为null
            
            // 且如果 delimiter不为null
            if (delimiter != null) {
                // 将 currentBuffer 与 delimiter 合并
                currentBuffer = wrappedBuffer(currentBuffer, delimiter);
            }
        }
        
        // 如果合并后的currentBuffer的可读字节数达到块大小,则调用fillByteBuf()并返回DefaultHttpContent。
        if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
            buffer = fillByteBuf();
            return new DefaultHttpContent(buffer);
        }
        
        // 否则返回null
        return null;
    }
    
    // 此时,buffer的容量不为0,说明还有数据

    // 如果currentBuffer为null
    if (currentBuffer == null) {
        
        // 如果 delimeter分隔符不为null
        if (delimiter != null) {
            
            // 将 buffer和delimiter合并,赋值给currentBuffer
            currentBuffer = wrappedBuffer(buffer, delimiter);
            
        } else {
            
            // delimeter分隔符为null
            
            // 将buffer赋值给currentBuffer
            currentBuffer = buffer;
        }
    } else {
        
        // currentBuffer不为null
        
        // 如果 delimeter分隔符不为null
        if (delimiter != null) {
            
            // 将 currentBuffer和buffer和delimiter合并,赋值给currentBuffer
            currentBuffer = wrappedBuffer(currentBuffer, buffer, delimiter);
            
        } else {
            
            // 将 currentBuffer和buffer合并,赋值给currentBuffer
            currentBuffer = wrappedBuffer(currentBuffer, buffer);
        }
    }

    // 如果currentBuffer的可读字节数小于块大小,那么将currentData设置为null,isKey设置为true,并返回null。
    // 这意味着当前数据已经处理完,但是当前块还没有达到块大小,所以等待下一个数据来填充。
    if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) {
        
        // currentData设置为null
        currentData = null;
        
        // isKey设置为true
        isKey = true;
        
        // 返回null
        return null;
    }
    
    // 此时说明 currentBuffer 的可读字节数 必定 大于等于 默认大小(8096)

    // 将 currentBuffer中的数据 按默认大小(8096)处理后,返回
    buffer = fillByteBuf();
    
    // 将 currentBuffer处理后的数据 封装为 HttpContent
    return new DefaultHttpContent(buffer);
}
fillByteBuf()
java 复制代码
// 这个方法就是 将 currentBuffer 按  默认大小(8096)去读取,
// 		如果不能读完,就读取8096个字节返回,
// 		如果能读完,就全部返回,并将currentBuffer置为null
private ByteBuf fillByteBuf() {
    
    // 拿到 currentBuffer 的 可读字节数
    int length = currentBuffer.readableBytes();
    
    // 如果 currentBuffer 的 可读字节数 大于 默认大小(8096)
    if (length > HttpPostBodyUtil.chunkSize) {
        
        // 从 currentBuffer 中,读取1个 大小固定为 8096 的 分片,并返回
        // (此时currentBuffer中还有数据)
        return currentBuffer.readRetainedSlice(HttpPostBodyUtil.chunkSize);
        
    } else {
        
        // 如果 currentBuffer 的 可读字节数 小于等于 默认大小(8096)
        
        // currentBuffer 赋值给slice
        ByteBuf slice = currentBuffer;
        
        // currentBuffer 置为null
        // (此时currentBuffer中所有数据已经交给了slice了,因此,将currentBuffer直接置为null)
        currentBuffer = null;
        
        // 返回slice
        return slice;
    }
}

HttpPostRequestEncoder示例

HttpServer

java 复制代码
public class HttpServer {

    public static void main(String[] args) {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {

                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HttpRequestDecoder());
                            ch.pipeline().addLast(new HttpResponseEncoder());
                            ch.pipeline().addLast(new HttpServerHandler());
                        }
                    });
            ChannelFuture bindFuture = serverBootstrap.bind(8080);
            Channel channel = bindFuture.sync().channel();
            System.out.println("---启动http服务成功---");
            channel.closeFuture().sync();
        } catch (Exception e) {
            System.out.println("---启动http服务失败---");
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
        System.out.println("---http服务已关闭---");

    }
}

HttpServerHandler

java 复制代码
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    HttpPostRequestDecoder decoder;
    HttpDataFactory factory;
    HttpRequest request;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject httpObject) throws Exception {
        System.out.println("[============> channelRead0]: " + httpObject);
        if (httpObject instanceof HttpRequest) {
            if (decoder != null) {
                decoder.destroy();
            }
            request = (HttpRequest) httpObject;
            factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
            decoder = new HttpPostRequestDecoder(factory, request);
        } else if (httpObject instanceof HttpContent) {
            decoder.offer(((HttpContent) httpObject));
            if (httpObject instanceof LastHttpContent) {
                for (InterfaceHttpData bodyHttpData : decoder.getBodyHttpDatas()) {
                    if (bodyHttpData.refCnt() > 0) {
                        handleHttpData(((HttpData) bodyHttpData));
                    }
                }
                decoder.destroy();
                decoder = null;
                sendResponse(ctx, request);
            } else {
                InterfaceHttpData next = null;
                while ((next = decoder.next()) != null) {
                    try {
                        handleHttpData(((HttpData) next));
                    } finally {
                        next.release();
                        decoder.removeHttpDataFromClean(next);
                    }
                }
            }
        } else {
            System.out.println("never reach here");
            ctx.fireChannelRead(httpObject);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常了...");
        cause.printStackTrace();
        ctx.close();
    }

    private void sendResponse(ChannelHandlerContext ctx, HttpRequest request) {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                Unpooled.wrappedBuffer("请求处理完成".getBytes(CharsetUtil.UTF_8))
        );
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        ChannelFuture channelFuture = ctx.writeAndFlush(response);
        // 客户端让我关, 我才关
        if (request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)) {
            System.out.println("添加关闭");
            channelFuture.addListener(future -> {
                ctx.channel().close();
            });
        }
    }

    private void handleHttpData(HttpData httpData) {
        if (httpData instanceof Attribute) {
            Attribute attribute = (Attribute) httpData;
            System.out.println("received attribute: " + attribute);
        } else if (httpData instanceof FileUpload) {
            FileUpload fileUpload = (FileUpload) httpData;
            System.out.println("received fileUpload: " + fileUpload);
            String filename = fileUpload.getFilename();
            String userDir = System.getProperty("user.dir");
            File file = new File(userDir + File.separator + "file" + File.separator + filename);
            if (file.exists() && file.isFile()) {
                file.delete();
            }
            try {
                fileUpload.renameTo(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

HttpClient

java 复制代码
public class HttpClient {

    public static void main(String[] args) {

        EventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HttpResponseDecoder());
                            ch.pipeline().addLast(new HttpRequestEncoder());
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new HttpClientHandler());
                        }
                    });
            ChannelFuture connectFuture = bootstrap.connect("127.0.0.1", 8080);
            Channel channel = connectFuture.sync().channel();
            System.out.println("---客户端连接http服务成功---");
            // 发送单个请求
            // sendSingleRequest(channel);
            // 发送多次请求
            // sendManyRequests(channel);
            // 发送文件上传请求
            sendPostData(channel);
            channel.closeFuture().sync();
        } catch (Exception e) {
            System.out.println("---客户端发生错误---");
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }

    }

    private static void sendPostData(Channel channel) throws HttpPostRequestEncoder.ErrorDataEncoderException {
        DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/testEncoder");
        HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(request, true);
        encoder.addBodyAttribute("name", "zzhua");
        encoder.addBodyAttribute("age", "18");
        encoder.addBodyFileUpload("mfile", new File("D:\\Projects\\practice\\demo-netty\\logs\\app.log"), null, false);
        HttpRequest httpRequest = encoder.finalizeRequest();
        channel.writeAndFlush(httpRequest);
        if (encoder.isChunked()) {
            channel.writeAndFlush(encoder);
        }
    }

    private static void sendManyRequests(Channel channel) {
        System.out.println("发第1个请求");
        sendSimplePost(channel);
        // try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
        System.out.println("发第2个请求");
        sendSimplePost(channel, true);
    }

    private static void sendSingleRequest(Channel channel, boolean... needClose) {
        System.out.println("发1个请求");
        sendSimplePost(channel, needClose);
    }

    private static ChannelFuture sendSimplePost(Channel channel, boolean... needClose) {
        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/test");
        if (needClose != null && needClose.length > 0 && needClose[0]) {
            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        }
        return channel.writeAndFlush(request);
    }
}

HttpClientHandler

java 复制代码
public class HttpClientHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        System.out.println("[客户端received]: " + msg);
        if (msg instanceof HttpResponse) {
            HttpResponse response = (HttpResponse) msg;
            System.out.println("响应状态码: " + response.status());
            System.out.println("响应头: " + response.headers());
        } else if (msg instanceof HttpContent) {
            HttpContent httpContent = (HttpContent) msg;
            System.out.println("响应内容: " + httpContent.content().toString(CharsetUtil.UTF_8));
            if (httpContent instanceof LastHttpContent) {
                System.out.println("响应结束");
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("客户端异常");
        cause.printStackTrace();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端inactive");
    }
}

SimpleHttpClient(值得看)

java 复制代码
public class SimpleHttpClient {
    private final EventLoopGroup workerGroup;
    private final Bootstrap bootstrap;

    public SimpleHttpClient() {
        workerGroup = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new HttpClientCodec());
                        ch.pipeline().addLast(new HttpObjectAggregator(65536));
                    }
                });
    }

    /**
     * 发送HTTP请求
     * @param url 请求URL
     * @param method 请求方法
     * @param body 请求体内容
     * @param timeout 超时时间(秒)
     * @return 响应内容
     */
    public CompletableFuture<String> sendRequest(String url, HttpMethod method, 
                                                String body, int timeout) {
        CompletableFuture<String> future = new CompletableFuture<>();
        
        try {
            URI uri = new URI(url);
            String host = uri.getHost();
            int port = uri.getPort() != -1 ? uri.getPort() : 
                      (uri.getScheme().equals("https") ? 443 : 80);
            String path = uri.getRawPath() + (uri.getRawQuery() != null ? "?" + uri.getRawQuery() : "");

            // 创建HTTP请求
            FullHttpRequest request;
            if (body != null && !body.isEmpty()) {
                request = new DefaultFullHttpRequest(
                        HttpVersion.HTTP_1_1, 
                        method, 
                        path,
                        Unpooled.copiedBuffer(body, CharsetUtil.UTF_8)
                );
                request.headers().set(HttpHeaderNames.CONTENT_LENGTH, body.length());
            } else {
                request = new DefaultFullHttpRequest(
                        HttpVersion.HTTP_1_1, 
                        method, 
                        path
                );
            }

            // 设置请求头
            request.headers()
                    .set(HttpHeaderNames.HOST, host)
                    .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
                    .set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP)
                    .set(HttpHeaderNames.USER_AGENT, "SimpleNettyClient/1.0");

            if (body != null && !body.isEmpty()) {
                request.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
            }

            // 连接服务器
            ChannelFuture channelFuture = bootstrap.connect(host, port);
            channelFuture.addListener((ChannelFutureListener) f -> {
                if (f.isSuccess()) {
                    Channel channel = f.channel();
                    
                    // 添加响应处理器
                    channel.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpResponse>() {
                        @Override
                        protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) {
                            try {
                                String content = response.content().toString(CharsetUtil.UTF_8);
                                future.complete("Status: " + response.status() + 
                                               "\nHeaders: " + response.headers() + 
                                               "\nBody: " + content);
                            } finally {
                                ctx.close();
                            }
                        }

                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                            future.completeExceptionally(cause);
                            ctx.close();
                        }
                    });

                    // 发送请求
                    channel.writeAndFlush(request);
                } else {
                    future.completeExceptionally(f.cause());
                }
            });

        } catch (URISyntaxException e) {
            future.completeExceptionally(e);
        }

        // 设置超时
        workerGroup.schedule(() -> {
            if (!future.isDone()) {
                future.completeExceptionally(new RuntimeException("Request timeout"));
            }
        }, timeout, TimeUnit.SECONDS);

        return future;
    }

    /**
     * 发送GET请求(简化方法)
     */
    public CompletableFuture<String> get(String url) {
        return sendRequest(url, HttpMethod.GET, null, 10);
    }

    /**
     * 发送POST请求(简化方法)
     */
    public CompletableFuture<String> post(String url, String body) {
        return sendRequest(url, HttpMethod.POST, body, 10);
    }

    /**
     * 关闭客户端
     */
    public void shutdown() {
        workerGroup.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {
        SimpleHttpClient client = new SimpleHttpClient();

        try {
            // 示例1:发送GET请求
            System.out.println("=== Sending GET request ===");
            String getResponse = client.get("http://127.0.0.1:8080/abc").get(15, TimeUnit.SECONDS);
            System.out.println(getResponse);

            Thread.sleep(1000); // 等待一下

            // 示例2:发送POST请求
            System.out.println("\n=== Sending POST request ===");
            String postResponse = client.post("http://127.0.0.1:8080/abc",
                    "Hello from Netty!").get(15, TimeUnit.SECONDS);
            System.out.println(postResponse);

        } finally {
            client.shutdown();
        }
    }
}
相关推荐
Marshmallowc4 小时前
强缓存失效了怎么办?深度解析浏览器内存缓存与硬盘缓存的存储逻辑
http·缓存·浏览器原理
Marshmallowc4 小时前
为什么 Webpack 要打包?从 HTTP/1.1 限制到 HTTP/2 多路复用原理详解
前端·http·webpack
星辰徐哥5 小时前
易语言网络通信编程基础:HTTP/HTTPS/TCP/UDP实战开发
开发语言·http·https·udp·tcp·易语言
DevilSeagull5 小时前
HTTP/HTTPS数据包拓展
网络·网络协议·http·https·web渗透·we
码农水水5 小时前
从 OpenFeign 到 RestClient:Spring Cloud 新时代的轻量化 HTTP 调用方案
java·运维·后端·spring·http·spring cloud·面试
REDcker5 小时前
HTTP 状态码清单大全
网络·网络协议·http
yanlou2331 天前
[C++/Linux HTTP项目] HTTP服务器基于muduo高性能服务器搭载【深入详解】
运维·服务器·http·muduo库·http高性能服务器
不许哈哈哈1 天前
HTTP协议基础(运维开发面试版)
http·面试·运维开发
王锋(oxwangfeng)1 天前
Nginx 四层 TCP 与七层 HTTP 转发实战指南
tcp/ip·nginx·http