可重复读方式解析请求中的JSON body

目录

问题症状:

不能同时使用HttpServletRequestWrapper,又在Controller类里用@RequestBody。不然应该是会报「流已关闭」错误

豆包解释:[1](#豆包解释:1)

需要在 Filter 中手动读取 Body(关键:避免流已关闭)。

由于 Request Body 的输入流(ServletInputStream)是 只能读取一次 的,直接读取后会导致后续 Controller 无法再读取,因此需要先「缓存 Body」,再通过包装类(HttpServletRequestWrapper)重写流读取逻辑。

解决:

请求包装类代码如下,其关键应是把json body缓存在byte[] cachedBody中了:

java 复制代码
import com.alibaba.fastjson.JSONArray;
import com.yrd.gpt.util.JsonUtil;
import com.yrd.gpt.util.StringUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 支持重复读取
 */
@Slf4j
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] cachedBody;

    private Map<String, String[]> parameterMap;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        // 读取原始流并缓存(Spring 的 StreamUtils 更简洁)
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());

        String jsonBody = getBody();
        HashMap<String, Object> requestMap = JsonUtil.fromJson(jsonBody, HashMap.class);
        parameterMap = new HashMap<>();
        if (null != requestMap && !requestMap.isEmpty()) {
            for (Map.Entry<String, Object> entry : requestMap.entrySet()) {
                String key = entry.getKey();
                if (StringUtil.isEmpty(key)) {
                    continue;
                }
                Object value = entry.getValue();
                if (null == value) {
                    continue;
                }
                if (value instanceof JSONArray) {
                    String[] values = Arrays.stream(((JSONArray) value).toArray())
                            .map(Object::toString)
                            .toArray(String[]::new);
                    log.warn("array: key@" + key + ";values@" + Arrays.toString(values));
                    this.parameterMap.put(entry.getKey(), values);
                } else {
                    this.parameterMap.put(entry.getKey(), new String[]{String.valueOf(value)});
                }
            }
        }
    }

    // 重写输入流,返回缓存的字节数组(支持重复读取)
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }
        };
    }

    // 重写 getReader(避免某些场景下通过 Reader 读取流失败)
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
    }

    // 获取 JSON 字符串的便捷方法
    public String getBody() {
        return new String(cachedBody, StandardCharsets.UTF_8);
    }

    @Override
    public String getParameter(String name) {
        String[] results = this.parameterMap.get(name);
        if (null == results || results.length < 1) {
            return null;
        } else {
            return results[0];
        }
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return this.parameterMap;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<>(this.parameterMap.keySet());
        return vector.elements();
    }

    @Override
    public String[] getParameterValues(String name) {
        return this.parameterMap.get(name);
    }

}

  1. 豆包:HttpParameterFilter 解析 JSON ↩︎
相关推荐
咘噜biu25 天前
Java SpringBoot后端Filter包装请求(新增/覆盖请求头)
java·spring boot·filter·requestwrapper
OceanSky61 年前
springboot请求入参重复读问题ContentCachingRequestWrapper
spring boot·contentcaching·requestwrapper·请求响应重复读取