All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.litongjava.tio.http.common.HttpMultiBodyDecoder Maven / Gradle / Ivy

There is a newer version: 3.7.3.v20241201-RELEASE
Show newest version
package com.litongjava.tio.http.common;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.exception.LengthOverflowException;
import com.litongjava.tio.core.exception.TioDecodeException;
import com.litongjava.tio.core.utils.ByteBufferUtils;
import com.litongjava.tio.http.common.utils.HttpParseUtils;
import com.litongjava.tio.utils.SystemTimer;
import com.litongjava.tio.utils.hutool.StrUtil;

/**
 * @author tanyaowu
 * 2017年7月26日 下午2:20:43
 */
public class HttpMultiBodyDecoder {
  public static class Header {
    private String contentDisposition = "form-data";
    private String name = null;
    private String filename = null;
    private String contentType = null;

    private Map map = new HashMap<>();

    public String getContentDisposition() {
      return contentDisposition;
    }

    public String getContentType() {
      return contentType;
    }

    public String getFilename() {
      return filename;
    }

    public Map getMap() {
      return map;
    }

    public String getName() {
      return name;
    }

    public void setContentDisposition(String contentDisposition) {
      this.contentDisposition = contentDisposition;
    }

    public void setContentType(String contentType) {
      this.contentType = contentType;
    }

    public void setFilename(String filename) {
      this.filename = filename;
    }

    public void setMap(Map map) {
      this.map = map;
    }

    public void setName(String name) {
      this.name = name;
    }
  }

  /**
   * 【
   * Content-Disposition: form-data; name="uploadFile"; filename=""
   * Content-Type: application/octet-stream
   * 】
   *
   * 【
   * Content-Disposition: form-data; name="end"
   * 】
   * @author tanyaowu
   * 2017年7月27日 上午10:18:01
   */
  public static interface MultiBodyHeaderKey {
    String Content_Disposition = "Content-Disposition".toLowerCase();
    String Content_Type = "Content-Type".toLowerCase();
  }

  public static enum Step {
    BOUNDARY, HEADER, BODY, END
  }

  private static Logger log = LoggerFactory.getLogger(HttpMultiBodyDecoder.class);

  /**
   * 
   * @param request
   * @param firstLine
   * @param bodyBytes
   * @param initboundary
   * @param channelContext
   * @param httpConfig
   * @throws TioDecodeException
   * @author tanyaowu
   */
  public static void decode(HttpRequest request, RequestLine firstLine, byte[] bodyBytes, String initboundary, ChannelContext channelContext, HttpConfig httpConfig) throws TioDecodeException {
    if (StrUtil.isBlank(initboundary)) {
      throw new TioDecodeException("boundary is null");
    }

    long start = SystemTimer.currTime;

    ByteBuffer buffer = ByteBuffer.wrap(bodyBytes);
    buffer.position(0);

    String boundary = "--" + initboundary;
    String endBoundary = boundary + "--";

    Step step = Step.BOUNDARY;
    try {
      label1: while (true) {
        if (step == Step.BOUNDARY) {
          String line = ByteBufferUtils.readLine(buffer, request.getCharset(), HttpConfig.MAX_LENGTH_OF_BOUNDARY);
          if (boundary.equals(line)) {
            step = Step.HEADER;
          } else if (endBoundary.equals(line)) // 结束了
          {
            break;
          } else {
            throw new TioDecodeException("line need:" + boundary + ", but is: " + line + "");
          }
        }

        Header multiBodyHeader = new Header();
        if (step == Step.HEADER) {
          List lines = new ArrayList<>(2);
          label2: while (true) {
            String line = ByteBufferUtils.readLine(buffer, request.getCharset(), HttpConfig.MAX_LENGTH_OF_MULTI_HEADER);
            if ("".equals(line)) {
              break label2;
            } else {
              lines.add(line);
            }
          }

          parseHeader(lines, multiBodyHeader, channelContext);
          step = Step.BODY;
        }

        if (step == Step.BODY) {
          Step newParseStep = parseBody(multiBodyHeader, request, buffer, boundary, endBoundary, channelContext, httpConfig);
          step = newParseStep;

          if (step == Step.END) {
            break label1;
          }
        }

      }
    } catch (LengthOverflowException loe) {
      throw new TioDecodeException(loe);
    } catch (UnsupportedEncodingException e) {
      log.error(channelContext.toString(), e);
    } finally {
      log.info("Parsing time:{}ms", SystemTimer.currTime - start);
    }

  }

  /**
   * 返回值不包括最后的\r\n
   * @param buffer
   * @param charset
   * @return
   * @throws UnsupportedEncodingException
   */
  // public static String getLine(ByteBuffer buffer, String charset) throws UnsupportedEncodingException {
  // char lastByte = 0; // 上一个字节
  // int initPosition = buffer.position();
  //
  // while (buffer.hasRemaining()) {
  // char b = (char) buffer.get();
  //
  // if (b == '\n') {
  // if (lastByte == '\r') {
  // int startIndex = initPosition;
  // int endIndex = buffer.position() - 2;
  // int length = endIndex - startIndex;
  // byte[] dst = new byte[length];
  //
  // System.arraycopy(buffer.array(), startIndex, dst, 0, length);
  // String line = new String(dst, charset);
  // return line;
  // }
  // }
  // lastByte = b;
  // }
  // return null;
  // }

  /**
   * 
   * @param header
   * @param request
   * @param buffer
   * @param boundary
   * @param endBoundary
   * @param channelContext
   * @return
   * @throws UnsupportedEncodingException
   * @throws LengthOverflowException
   * @author tanyaowu
   * @param httpConfig 
   */
  public static Step parseBody(Header header, HttpRequest request, ByteBuffer buffer, String boundary, String endBoundary, ChannelContext channelContext, HttpConfig httpConfig)
      throws UnsupportedEncodingException, LengthOverflowException, TioDecodeException {
    int initPosition = buffer.position();

    while (buffer.hasRemaining()) {
      String line = ByteBufferUtils.readLine(buffer, request.getCharset(), httpConfig.getMaxLengthOfMultiBody());
      boolean isEndBoundary = endBoundary.equals(line);
      boolean isBoundary = boundary.equals(line);
      if (isBoundary || isEndBoundary) {
        int startIndex = initPosition;
        int endIndex = buffer.position() - line.getBytes().length - 2 - 2;
        int length = endIndex - startIndex;
        byte[] dst = new byte[length];

        System.arraycopy(buffer.array(), startIndex, dst, 0, length);
        String filename = header.getFilename();
        if (filename != null)// 该字段类型是file
        {
          if (StrUtil.isNotBlank(filename)) { //
            UploadFile uploadFile = new UploadFile();
            uploadFile.setName(filename.replaceAll("%", ""));
            uploadFile.setData(dst);
            uploadFile.setSize(dst.length);
            request.addParam(header.getName(), uploadFile);
          }
        } else { // 该字段是普通的key-value
          request.addParam(header.getName(), new String(dst, request.getCharset()));
        }
        if (isEndBoundary) {
          return Step.END;
        } else {
          return Step.HEADER;
        }
      }
    }
    log.error("文件上传,协议不对,step is null");
    throw new TioDecodeException("step is null");
  }

  /**
   * 【
   * Content-Disposition: form-data; name="uploadFile"; filename=""
   * Content-Type: application/octet-stream
   * 】
   *
   * 【
   * Content-Disposition: form-data; name="end"
   * 】
   * @param lines
   * @param header
   * @author tanyaowu
   */
  public static void parseHeader(List lines, Header header, ChannelContext channelContext) throws TioDecodeException {
    if (lines == null || lines.size() == 0) {
      throw new TioDecodeException("multipart_form_data 格式不对,没有头部信息");
    }

    try {
      for (String line : lines) {
        String[] keyvalue = line.split(":");
        String key = StrUtil.trim(keyvalue[0]).toLowerCase();//
        String value = StrUtil.trim(keyvalue[1]);
        header.map.put(key, value);
      }

      String contentDisposition = header.map.get(MultiBodyHeaderKey.Content_Disposition);
      String name = HttpParseUtils.getSubAttribute(contentDisposition, "name");// .getPerprotyEqualValue(header.map, MultiBodyHeaderKey.Content_Disposition, "value");
      String filename = HttpParseUtils.getSubAttribute(contentDisposition, "filename");// HttpParseUtils.getPerprotyEqualValue(header.map, MultiBodyHeaderKey.Content_Disposition, "filename");
      String contentType = header.map.get(MultiBodyHeaderKey.Content_Type);// .HttpParseUtils.getPerprotyEqualValue(header.map, MultiBodyHeaderKey.Content_Type, "filename");

      header.setContentDisposition(contentDisposition);
      header.setName(name);
      header.setFilename(filename);
      header.setContentType(contentType);

    } catch (Throwable e) {
      log.error(channelContext.toString(), e);
      throw new TioDecodeException(e.toString());
    }

    // for (int i = 0; i < lines.size(); i++) {
    // String line = lines.get(i);
    // if (i == 0) {
    // String[] mapStrings = StrUtil.split(line, ";");
    // String s = mapStrings[0];//
    //
    // String[] namekeyvalue = StrUtil.split(mapStrings[1], "=");
    // header.setName(namekeyvalue[1].substring(1, namekeyvalue[1].length() - 1));
    //
    // if (mapStrings.length == 3) {
    // String[] finenamekeyvalue = StrUtil.split(mapStrings[2], "=");
    // String filename = finenamekeyvalue[1].substring(1, finenamekeyvalue[1].length() - 1);
    // header.setFilename(FilenameUtils.getName(filename));
    // }
    // } else if (i == 1) {
    // String[] map = StrUtil.split(line, ":");
    // String contentType = map[1].trim();//
    // header.setContentType(contentType);
    // }
    // }
  }

  /**
   *
   */
  public HttpMultiBodyDecoder() {

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy