简体   繁体   English

如何使用 java 过滤器修改响应正文?

[英]how modify the response body with java filter?

I want to perform some filter logic to HTTP responses (which are in json format).我想对 HTTP 响应(采用 json 格式)执行一些过滤逻辑。

I had successfully change the response body, but when the (string) size of the body changes: I am getting missing the last characters.我已经成功更改了响应正文,但是当正文的(字符串)大小发生变化时:我错过了最后一个字符。

To make it simpler, I had created a simple Spring Boot application, with only Web dependency for my rest controller.为了让它更简单,我创建了一个简单的 Spring Boot 应用程序,我的 rest 控制器只有 Web 依赖项。

My Rest Controller我的休息控制器

@RestController
@RequestMapping("/home/")
public class RestControllerHome {

@GetMapping (produces=MediaType.APPLICATION_JSON_VALUE)
public String home() {
        return "{ \"name\" : \"Peter\" }";
  }
}

My Filter我的过滤器

@Component
public class MyFilter implements Filter {

@Override
public void destroy() { }

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

    HtmlResponseWrapper capturingResponseWrapper = new HtmlResponseWrapper((HttpServletResponse) response);
    filterChain.doFilter(request, capturingResponseWrapper);        
    if (response.getContentType() != null && response.getContentType().contains("application/json")) {
        String content = capturingResponseWrapper.getCaptureAsString();

        // This code works fine
        //response.getWriter().write(content.toUpperCase());

        // This code doesn't works because the content size is changed
        response.getWriter().write("{ \"name\" : \"************r\" }");

    }
}

@Override
public void init(FilterConfig arg0) throws ServletException {  }    
}

HttpServletResponseWrapper // capture the response before it is written HttpServletResponseWrapper // 在写入之前捕获响应

public class HtmlResponseWrapper extends HttpServletResponseWrapper {

private final ByteArrayOutputStream capture;
private ServletOutputStream output;
private PrintWriter writer;

public HtmlResponseWrapper(HttpServletResponse response) {
    super(response);
    capture = new ByteArrayOutputStream(response.getBufferSize());
}

@Override
public ServletOutputStream getOutputStream() {
    if (writer != null) {
        throw new IllegalStateException("getWriter() has already been called on this response.");
    }

    if (output == null) {
        // inner class - lets the wrapper manipulate the response 
        output = new ServletOutputStream() {
            @Override
            public void write(int b) throws IOException {
                capture.write(b);
            }

            @Override
            public void flush() throws IOException {
                capture.flush();
            }

            @Override
            public void close() throws IOException {
                capture.close();
            }

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

            @Override
            public void setWriteListener(WriteListener arg0) {
            }
        };
    }

    return output;
}

@Override
public PrintWriter getWriter() throws IOException {
    if (output != null) {
        throw new IllegalStateException("getOutputStream() has already been called on this response.");
    }

    if (writer == null) {
        writer = new PrintWriter(new OutputStreamWriter(capture,
                getCharacterEncoding()));
    }

    return writer;
}

@Override
public void flushBuffer() throws IOException {
    super.flushBuffer();

    if (writer != null) {
        writer.flush();
    } else if (output != null) {
        output.flush();
    }
}

public byte[] getCaptureAsBytes() throws IOException {
    if (writer != null) {
        writer.close();
    } else if (output != null) {
        output.close();
    }

    return capture.toByteArray();
}

public String getCaptureAsString() throws IOException {
    return new String(getCaptureAsBytes(), getCharacterEncoding());
}

}

In my doFilter method, the following code ...在我的doFilter方法中,以下代码...

// This code works fine
response.getWriter().write(content.toUpperCase());

// This code doesn't works because the content size is changed
//response.getWriter().write("{ \"name\" : \"************r\" }");

... gives my the following output : {"NAME": "PETER"} Which tell me, that the code is working properly. ... 给我以下输出: {"NAME": "PETER"}这告诉我,代码工作正常。

But, in reality I want to change the body content ...但是,实际上我想更改正文内容...

// This code works fine
//response.getWriter().write(content.toUpperCase());

// This code doesn't works because the content size is changed
response.getWriter().write("{ \"name\" : \"************r\" }");

... and the previous code, is giving me an incomplete text body as output: **{ "name" : "********** ...和前面的代码,给了我一个不完整的文本体作为输出:**{“name”:“**********

What am I doing wrong?我究竟做错了什么? My app have a bigger json body, and a little more complex logic in the filter.我的应用程序有一个更大的 json 主体,过滤器中的逻辑更复杂一些。 But, if I dont get this working I am not being able to make the rest of my code work.但是,如果我不让这个工作,我就无法让我的其余代码工作。 Please, help.请帮忙。

I took the Filter and HttpServletResponseWrapper from https://www.leveluplunch.com/java/tutorials/034-modify-html-response-using-filter/我从https://www.leveluplunch.com/java/tutorials/034-modify-html-response-using-filter/获取了过滤器和 HttpServletResponseWrapper

Thanks to the help of JBNizet, I found that the solution was to add the Content Lenght:感谢 JBNizet 的帮助,我发现解决方案是添加 Content Lenght:

String newContent = "{ \"name\" : \"************r\" }";
response.setContentLength(newContent .length());
response.getWriter().write(newContent);

Actually, Following the answer above, you will get an error saying getWriter() has already been called for this response实际上,按照上面的答案,你会得到一个错误,说getWriter() has already been called for this response

So, you can simply just modify the last line like below:因此,您可以简单地修改最后一行,如下所示:

String newContent = "{ \"name\" : \"************r\" }";
response.setContentLength(newContent.length());
response.getOutputStream().write(newContent.getBytes());

on my side, to modify the response I used this wrapper to capture the response's content into a byte array : ContentCachingResponseWrapper在我这边,为了修改响应,我使用这个包装器将响应的内容捕获到一个字节数组中: ContentCachingResponseWrapper

( see also this useful general documentation from Oracle :https://www.oracle.com/java/technologies/filters.html][2] ) (另请参阅 Oracle 的这份有用的通用文档:https://www.oracle.com/java/technologies/filters.html][2]

here is my source code :这是我的源代码:

...
import java.util.logging.Level;
import java.util.logging.Logger;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.util.ContentCachingResponseWrapper;
...

public class MyCustomFilter implements Filter {

    private static final Logger LOGGER = Logger.getLogger( "MyCustomFilter" );

    @Override
    public void destroy() {
    }

    /**
     * MODIFY THE RESPONSE  
     * https://www.oracle.com/java/technologies/filters.html
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // Use a SPRING-4 wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.
        final ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);

        // Invoke the next entity in the filter chain
        chain.doFilter(request, responseWrapper);

        try {
            // Get the original response data
            final byte[] originalData = responseWrapper.getContentAsByteArray();
            final int originalLength = responseWrapper.getContentSize();

            // Modify the original data
            final String newData = performYourOwnBusinessLogicIntoThisMethod( originalData ) ;
            final int newLength = newData.length();

            // Write the data into the output stream
            response.setContentLength(newData.length());
            response.getWriter().write(newData);

            // Commit the written data
            response.getWriter().flush();

        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Please contact your developer about this ERROR !" + e.getClass() + " : " + e.getMessage(), e);
            response.setContentLength(responseWrapper.getContentSize());
            response.getOutputStream().write(responseWrapper.getContentAsByteArray());
            response.flushBuffer();

        } finally {
            //NOOP
        }
    }
}

In my case, originalData represents a JSON object (of which I do not have the classes unfortunately... I need to parse), the business logic consists in updating or removing some nodes :在我的例子中, originalData 代表一个 JSON 对象(不幸的是我没有类......我需要解析),业务逻辑包括更新或删除一些节点:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
...
        
public String performYourOwnBusinessLogicIntoThisMethod(byte[] jsonInput) throws IOException {

        final ObjectMapper mapper = new ObjectMapper();
        final ObjectNode root = (ObjectNode) mapper.readTree(jsonInput);

        //update the json root
        //...

        return root.toString();
    }       

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM