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

com.hibegin.http.server.impl.HttpRequestDecoderImpl Maven / Gradle / Ivy

Go to download

Simple, flexible, less dependent, more extended. Less memory footprint, can quickly build Web project. Can quickly run embedded, Android devices

There is a newer version: 0.3.162
Show newest version
package com.hibegin.http.server.impl;

import com.hibegin.common.util.BytesUtil;
import com.hibegin.common.util.IOUtil;
import com.hibegin.common.util.LoggerUtil;
import com.hibegin.common.util.ObjectUtil;
import com.hibegin.http.HttpMethod;
import com.hibegin.http.io.ChunkedStreamUtils;
import com.hibegin.http.server.ApplicationContext;
import com.hibegin.http.server.api.HttpRequest;
import com.hibegin.http.server.api.HttpRequestDeCoder;
import com.hibegin.http.server.config.ConfigKit;
import com.hibegin.http.server.config.RequestConfig;
import com.hibegin.http.server.execption.RequestBodyTooLargeException;
import com.hibegin.http.server.handler.ReadWriteSelectorHandler;
import com.hibegin.http.server.util.FileCacheKit;
import com.hibegin.http.server.util.HttpQueryStringUtils;

import java.io.*;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.file.NoSuchFileException;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HttpRequestDecoderImpl implements HttpRequestDeCoder {

    private static final String CRLF = "\r\n";
    static final String SPLIT = CRLF + CRLF;
    private static final Logger LOGGER = LoggerUtil.getLogger(HttpRequestDecoderImpl.class);
    private SimpleHttpRequest request;
    private byte[] inputBytes = new byte[]{};
    private final RequestConfig requestConfig;
    private final ApplicationContext applicationContext;
    private final ReadWriteSelectorHandler handler;

    public HttpRequestDecoderImpl(RequestConfig requestConfig, ApplicationContext applicationContext, ReadWriteSelectorHandler handler) {
        this.requestConfig = requestConfig;
        this.applicationContext = applicationContext;
        this.handler = handler;
    }

    @Override
    public ReadWriteSelectorHandler getHandler() {
        return handler;
    }

    private static int findSequence(byte[] data, byte[] sequence) {
        if (data == null || sequence == null || data.length < sequence.length) {
            return -1; // 数据为空或序列长度不足
        }

        for (int i = 0; i < data.length - sequence.length + 1; i++) {
            boolean match = true;
            for (int j = 0; j < sequence.length; j++) {
                if (data[i + j] != sequence[j]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                return i; // 找到序列的起始索引
            }
        }

        return -1; // 未找到序列
    }

    @Override
    public Map.Entry doDecode(ByteBuffer byteBuffer) throws Exception {
        if (Objects.isNull(request)) {
            this.request = new SimpleHttpRequest(handler, applicationContext, requestConfig);
        }
        Map.Entry result;
        //代理头,直接保存到body
        if (request.method == HttpMethod.CONNECT) {
            result = saveRequestBodyBytes(byteBuffer.array());
        }
        //存在body需要处理
        else if (inputBytes.length > 0 && getContentLength() > 0) {
            result = saveRequestBodyBytes(byteBuffer.array());
        } else {
            // 存在2种情况,提交的数据一次性读取完成,提交的数据一次性读取不完
            inputBytes = BytesUtil.mergeBytes(inputBytes, byteBuffer.array());
            int idx = findSequence(inputBytes, SPLIT.getBytes());
            if (idx < 0) {
                int maxHeaderSize = request.getRequestConfig().getMaxRequestHeaderSize();
                //没有读取到 SPLIT 时,检查 header 的最大长度
                if (inputBytes.length > maxHeaderSize) {
                    throw new RequestBodyTooLargeException("The http header to large " + inputBytes.length + " more than " + maxHeaderSize);
                }
                //没有 SPLIT,请求头部分不完整,需要继续等待,且已处理 byteBuffer,返回0
                return new AbstractMap.SimpleEntry<>(false, ByteBuffer.allocate(0));
            }
            String httpHeader = new String(BytesUtil.subBytes(inputBytes, 0, idx));
            String[] headerArr = httpHeader.split(CRLF);
            //parse http method
            request.method = HttpMethod.parseHttpMethodByRequestLine(headerArr[0]);
            // parse HttpHeader
            parseProtocolHeader(headerArr);
            request.requestHeaderStr = httpHeader;
            // parse body
            int headerByteLength = SPLIT.getBytes().length + idx;
            byte[] requestBody = BytesUtil.subBytes(inputBytes, headerByteLength, inputBytes.length - headerByteLength);
            result = saveRequestBodyBytes(requestBody);
        }
        if (Objects.equals(result.getKey(), true)) {
            dealRequestBodyData();
            //处理完成,清空byte[]
            inputBytes = new byte[]{};
            if (Objects.nonNull(request.getHandler())) {
                request.getHandler().flushRequestBB();
            }
        }
        return result;
    }

    /**
     * 使用磁盘文件替代内存 ByteBuffered,避免OOM
     *
     * @param bytes
     * @return
     * @throws IOException
     */
    private Map.Entry saveRequestBodyBytes(byte[] bytes) throws IOException {
        long dataLength = getContentLength();
        if (Objects.isNull(bytes) || bytes.length == 0) {
            if (dataLength == 0) {
                return new AbstractMap.SimpleEntry<>(true, ByteBuffer.allocate(0));
            }
            return new AbstractMap.SimpleEntry<>(dataLength == getRequestBodyLength(), ByteBuffer.allocate(0));
        }
        byte[] handleBytes = bytes;
        try {
            if (dataLength > 0) {
                handleBytes = BytesUtil.subBytes(bytes, 0, (int) dataLength);
            }
            File tempFile = saveRequestBodyToTempFile(handleBytes);
            //requestBody full
            if (Objects.nonNull(tempFile) && tempFile.exists() && tempFile.length() == dataLength) {
                int hasNextData = bytes.length - handleBytes.length;
                if (hasNextData > 0) {
                    byte[] nextData = BytesUtil.subBytes(bytes, handleBytes.length, hasNextData);
                    return new AbstractMap.SimpleEntry<>(true, ByteBuffer.wrap(nextData));
                } else {
                    return new AbstractMap.SimpleEntry<>(true, ByteBuffer.allocate(0));
                }
            } else {
                return new AbstractMap.SimpleEntry<>(dataLength == 0, ByteBuffer.allocate(0));
            }
        } finally {
            if (request.getApplicationContext().getServerConfig().getHttpRequestDecodeListener() != null) {
                request.getApplicationContext().getServerConfig().getHttpRequestDecodeListener().decodeRequestBodyBytesAfter(request, handleBytes);
            }
        }
    }

    private File saveRequestBodyToTempFile(byte[] handleBytes) throws IOException {
        if (Objects.isNull(request.tmpRequestBodyFile)) {
            request.tmpRequestBodyFile = FileCacheKit.generatorRequestTempFile(request.getServerConfig().getPort() + "", handleBytes);
            return request.tmpRequestBodyFile;
        }
        try (FileOutputStream fileOutputStream = new FileOutputStream(request.tmpRequestBodyFile, true)) {
            fileOutputStream.write(handleBytes);
        }
        return request.tmpRequestBodyFile;
    }

    private byte[] getRequestBodyBytes() {
        File tempFile = request.tmpRequestBodyFile;
        if (Objects.isNull(tempFile) || !tempFile.exists()) {
            return null;
        }
        try {
            if (Objects.equals(request.getHeader("Transfer-encoding"), "chunked") && requestConfig.isEnableRequestChunkedStream()) {
                try (FileInputStream fileInputStream = new FileInputStream(tempFile)) {
                    try {
                        return ChunkedStreamUtils.convertChunkedStream(fileInputStream);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return IOUtil.getByteByFile(tempFile);
        } catch (RuntimeException e) {
            //read file lost, ignore exception
            if (Objects.nonNull(e.getCause()) && e.getCause() instanceof NoSuchFileException) {
                return null;
            }
            throw e;
        }
    }

    private long getRequestBodyLength() {
        if (request.tmpRequestBodyFile != null && request.tmpRequestBodyFile.exists()) {
            return request.tmpRequestBodyFile.length();
        }
        return 0;
    }

    private void parseProtocolHeader(String[] headerArr) throws Exception {
        //
        request.header.clear();
        String pHeader = headerArr[0];
        String[] protocolHeaderArr = pHeader.split(" ");
        String tUrl = request.uri = protocolHeaderArr[1];
        // just for some proxy-client
        if (tUrl.startsWith(request.getScheme() + "://")) {
            tUrl = tUrl.substring((request.getScheme() + "://").length());
            if (tUrl.contains("/")) {
                request.header.put("Host", tUrl.substring(0, tUrl.indexOf("/")));
                tUrl = tUrl.substring(tUrl.indexOf("/"));
            } else {
                request.header.put("Host", tUrl);
                tUrl = "/";
            }
        }
        if (tUrl.contains("?")) {
            request.uri = tUrl.substring(0, tUrl.indexOf("?"));
            request.queryStr = tUrl.substring(tUrl.indexOf("?") + 1);
        } else {
            request.uri = tUrl;
            request.queryStr = "";
        }
        if (request.uri.contains("/")) {
            request.uri = URLDecoder.decode(request.uri.substring(request.uri.indexOf("/")), request.getRequestConfig().getCharSet());
        } else {
            request.getHeaderMap().put("Host", request.uri);
            request.uri = "/";
        }
        // 先得到请求头信息
        for (int i = 1; i < headerArr.length; i++) {
            dealRequestHeaderString(headerArr[i]);
        }
        request.paramMap = HttpQueryStringUtils.parseUrlEncodedStrToMap(request.queryStr);
        if (getContentLength() > getRequest().getRequestConfig().getMaxRequestBodySize()) {
            throw new RequestBodyTooLargeException("The Content-Length outside the max upload size " + ConfigKit.getMaxRequestBodySize());
        }
    }

    private long getContentLength() {
        return Long.parseLong(ObjectUtil.requireNonNullElse(request.getHeader("Content-Length"), "0"));
    }

    private void dealRequestHeaderString(String str) {
        int delimiterIndex = str.indexOf(": ");
        if (delimiterIndex != -1) {
            String key = str.substring(0, delimiterIndex);
            String value = str.substring(delimiterIndex + 2).trim();
            request.header.put(key, value);
        }
    }

    public static String getFileExtension(String filename) {
        int lastDotIndex = filename.lastIndexOf(".");
        if (lastDotIndex != -1 && lastDotIndex < filename.length() - 1) {
            return filename.substring(lastDotIndex + 1);
        }
        return null;
    }

    private void dealRequestBodyData() throws IOException {
        byte[] requestBody = getRequestBodyBytes();
        if (Objects.isNull(requestBody)) {
            return;
        }
        String contentTypeHeader = request.getHeader("Content-Type");
        if (Objects.isNull(contentTypeHeader) || contentTypeHeader.trim().isEmpty()) {
            return;
        }
        String contentType = contentTypeHeader.split(";")[0];
        //FIXME 不支持多文件上传,不支持这里有其他属性字段
        if ("multipart/form-data".equals(contentType)) {
            StringBuilder sb = new StringBuilder();
            try (BufferedReader bin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(requestBody)))) {
                String headerStr;
                while ((headerStr = bin.readLine()) != null && !headerStr.isEmpty()) {
                    sb.append(headerStr).append(CRLF);
                    dealRequestHeaderString(headerStr);
                }
            } catch (IOException e) {
                LOGGER.log(Level.SEVERE, "", e);
            }
            String contentDisposition = request.getHeader("Content-Disposition");
            if (contentDisposition != null) {
                String[] kvs = contentDisposition.split(";");
                String inputKeyName = kvs[1].split("=")[1].replace("\"", "");
                String ext = null;
                if (kvs.length == 3) {
                    String inputFileName = kvs[2].split("=")[1].replace("\"", "");
                    ext = getFileExtension(inputFileName);
                }
                int length1 = sb.toString().split(CRLF)[0].getBytes().length + CRLF.getBytes().length;
                int length2 = sb.toString().getBytes().length + 2;
                int dataLength = requestBody.length - length1 - length2 - SPLIT.getBytes().length;
                File file = FileCacheKit.generatorRequestTempFile(request.getServerConfig().getPort() + (Objects.isNull(ext) ? "" : "." + ext), BytesUtil.subBytes(requestBody, length2, dataLength));
                if (request.files == null) {
                    request.files = new HashMap<>();
                }
                request.files.put(inputKeyName, file);
            }
        } else if ("application/x-www-form-urlencoded".equals(contentType)) {
            request.paramMap.putAll(HttpQueryStringUtils.parseUrlEncodedStrToMap(new String(requestBody)));
        }
    }

    @Override
    public HttpRequest getRequest() {
        return request;
    }

    @Override
    public void doNext() {
        //处理完成,清空byte[]
        this.inputBytes = new byte[]{};
        this.request = null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy