春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

huangapple 未分类评论47阅读模式
英文:

Spring form input values return null after read it once in filter, despite using request wrapper

问题

在我的Spring Boot项目中,我使用FreeMarker模板来创建示例表单。我需要添加一个过滤器来读取请求的有效负载并进行一些操作。我知道如果在过滤器中读取了有效负载,就需要重置请求体,因为它只能被读取一次。由于我以前遇到过这个问题,我知道我必须使用包装器。我期望能够像以前一样解决我的问题。然而,在控制器中,输入对象中的所有字段都是空的。

在这里我漏掉了什么?

我的过滤器:

public class KfsInMsgFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       
	   HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;

        Map<String, String[]> extraParams = new TreeMap<String, String[]>();
        WrappedRequest wrappedRequest = new WrappedRequest(request, extraParams);

        String body = IOUtils.toString(new BufferedReader(new InputStreamReader(wrappedRequest.getInputStream(), Constants.UTF_8)));

        // 使用body做一些操作
		// ....

        // 重置有效负载
        wrappedRequest.resetStream(body.getBytes(Constants.UTF_8));
		
		...
          
    } 
}

WrappedRequest类:

@Slf4j
public class WrappedRequest extends HttpServletRequestWrapper {
    private final Map<String, String[]> modifiableParameters;
    private ResettableServletInputStream servletStream;
    private byte[] rawData;
    private HttpServletRequest request;
    private String payload;


    /**
     * 创建一个新的请求包装器,将附加的参数合并到请求对象中,而不会过早地从原始请求中读取参数。
     *
     * @param request
     * @param additionalParams
     */
    public WrappedRequest(final HttpServletRequest request,
                          final Map<String, String[]> additionalParams) {
        super(request);
        this.request = request;
        this.modifiableParameters = new TreeMap<String, String[]>();
        this.modifiableParameters.putAll(additionalParams);
        this.servletStream = new ResettableServletInputStream();
    }

    /**
     * @param newRawData
     */
    public void resetStream(byte[] newRawData) {
        servletStream.stream = new ByteArrayInputStream(newRawData);
    }

    /**
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader());
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return servletStream;
    }

    /**
     * @return
     * @throws IOException
     */
    @Override
    public BufferedReader getReader() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader());
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return new BufferedReader(new InputStreamReader(servletStream, Constants.UTF_8));
    }

    /**
     * @return
     */
    private String getBodyAsString() {
        StringBuffer buff = new StringBuffer();
        buff.append(" BODY_DATA START [ ");
        char[] charArr = new char[getContentLength()];
        try {
            BufferedReader reader = new BufferedReader(getReader());
            reader.read(charArr, 0, charArr.length);
            reader.close();
        } catch (IOException e) {
            log.error("", e);
        }
        buff.append(charArr);
        buff.append(" ] BODY_DATA END ");
        return buff.toString();
    }

    /**
     * @return
     */
    public String getPayload() {
        return payload;
    }

    /**
     * @param payload
     */
    public void setPayload(String payload) {
        this.payload = payload;
    }

    private static class ResettableServletInputStream extends ServletInputStream {

        private InputStream stream;

        @Override
        public int read() throws IOException {
            return stream.read();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

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

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }
}

我期望在控制器中获得的请求体内容:

春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

我实际获得的内容:

春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

@PostMapping(value = "/edit")
    public String editPlatform(EditInfo editInfo, Model model) {
        Optional<Platform> p = platformService.findById(editInfo.getId());
        List<SafeCustodyOffice> officeList = safeCustodyOfficeService.getAll();

        if (p.isPresent()) {
            model.addAttribute("platform", p.get());
            model.addAttribute("offices", officeList);
            return "platform-edit";
        } else {
            throw new KfsException(ErrorCodes.KFS19);
        }
    }

重要更改:

我发现了一些有趣的内容,这些内容为我解决问题提供了线索。这可能对其他人来说更有意义。

我注意到输入的内容类型会改变结果,就像这样:

春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

是否有任何方法可以让第5行的组合像第3行一样工作?

英文:

In my spring-boot project, I use freemarker templates for sample forms. I needed to add filter in order to read payload and do some stuff. I know if you read payload in filter, you need to reset request body. Because it can be read once. Since I encountered this problem before, I knew that I must have used wrapper. I expected solve my problem as before. However, in the controller, all fields in input objects are null.

What am I missing in here ?

My filter:

public class KfsInMsgFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       
	   HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;

        Map&lt;String, String[]&gt; extraParams = new TreeMap&lt;String, String[]&gt;();
        WrappedRequest wrappedRequest = new WrappedRequest(request, extraParams);

        String body = IOUtils.toString(new BufferedReader(new InputStreamReader(wrappedRequest.getInputStream(), Constants.UTF_8)));

        // doing some stuff using body
		// ....

        // resetting payload
        wrappedRequest.resetStream(body.getBytes(Constants.UTF_8));
		
		...
          
    } 
}

WrappedRequest class:


@Slf4j
public class WrappedRequest extends HttpServletRequestWrapper {
    private final Map&lt;String, String[]&gt; modifiableParameters;
    private ResettableServletInputStream servletStream;
    private byte[] rawData;
    private HttpServletRequest request;
    private String payload;


    /**
     * Create a new request wrapper that will merge additional parameters into
     * the request object without prematurely reading parameters from the
     * original request.
     *
     * @param request
     * @param additionalParams
     */
    public WrappedRequest(final HttpServletRequest request,
                          final Map&lt;String, String[]&gt; additionalParams) {
        super(request);
        this.request = request;
        this.modifiableParameters = new TreeMap&lt;String, String[]&gt;();
        this.modifiableParameters.putAll(additionalParams);
        this.servletStream = new ResettableServletInputStream();
    }

    /**
     * @param newRawData
     */
    public void resetStream(byte[] newRawData) {
        servletStream.stream = new ByteArrayInputStream(newRawData);
    }

    /**
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader());
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return servletStream;
    }

    /**
     * @return
     * @throws IOException
     */
    @Override
    public BufferedReader getReader() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader());
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return new BufferedReader(new InputStreamReader(servletStream, Constants.UTF_8));
    }

    /**
     * @return
     */
    private String getBodyAsString() {
        StringBuffer buff = new StringBuffer();
        buff.append(&quot; BODY_DATA START [ &quot;);
        char[] charArr = new char[getContentLength()];
        try {
            BufferedReader reader = new BufferedReader(getReader());
            reader.read(charArr, 0, charArr.length);
            reader.close();
        } catch (IOException e) {
            log.error(&quot;&quot;, e);
        }
        buff.append(charArr);
        buff.append(&quot; ] BODY_DATA END &quot;);
        return buff.toString();
    }

    /**
     * @return
     */
    public String getPayload() {
        return payload;
    }

    /**
     * @param payload
     */
    public void setPayload(String payload) {
        this.payload = payload;
    }

    private static class ResettableServletInputStream extends ServletInputStream {

        private InputStream stream;

        @Override
        public int read() throws IOException {
            return stream.read();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

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

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }
}

Body I expected to get in controller:
春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

What I get:
春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

@PostMapping(value = &quot;/edit&quot;)
    public String editPlatform(EditInfo editInfo, Model model) {
        Optional&lt;Platform&gt; p = platformService.findById(editInfo.getId());
        List&lt;SafeCustodyOffice&gt; officeList = safeCustodyOfficeService.getAll();

        if (p.isPresent()) {
            model.addAttribute(&quot;platform&quot;, p.get());
            model.addAttribute(&quot;offices&quot;, officeList);
            return &quot;platform-edit&quot;;
        } else {
            throw new KfsException(ErrorCodes.KFS19);
        }
    }

Important Edit:

I discovered someting I found interesting and gives me clues about the problem. This may be makes more sense for anybody but me.

I see that the content type of input changes the result like this:

春季表单输入值在过滤器中读取一次后返回null,尽管使用了请求包装器。

Is there any workaround to make row 5 combination work like row 3?

huangapple
  • 本文由 发表于 2020年4月9日 21:35:04
  • 转载请务必保留本文链接:https://java.coder-hub.com/61122412.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定