vijayakumarpsg587
vijayakumarpsg587

Reputation: 1177

Cannot write new response in servlet filter

I am trying to read the current response and trying to overwrite the response with new data. But currently I am unable to do so. I always end up with the exception that the GetWriter has already been called

Here is what I am trying to do

I would like to read from the response, modify it and write a new response in the filter. I would not be able to do it normally since the process is handled by swagger-springfox. The reason I am looking for this approach is to have an xml element - which gets generated in the APIDOC to be removed. I understand that this is a one off and probably have some configuration issues but there are one too many files for scanning and identifying where the issue is. Hence I decided to go with this approach. More details are available in this link - https://github.com/springfox/springfox/issues/2821

In the string that I printing - it works perfectly fine, but the response is never getting sent to the client because it seems that the getWriter() of the response is either already been called or getting closed.

Here is a sample JSON string that you could - String s = " <Json>{ \"id\": 1, \"itemName\": \"theItem \", \"owner\": { \"id\": 2, \"name\": \"theUser\" }} </Json>";

Here is my filter

public class SwaggerFilter extends GenericFilterBean {


 @Override
  public void doFilter(ServletRequest request, ServletResponse response, 
   FilterChain chain)
          throws IOException, ServletException {
      // TODO Auto-generated method stub
      HttpServletRequest req = (HttpServletRequest) request;
      HttpServletResponse res = (HttpServletResponse) response;

    if (req.getRequestURI().toLowerCase().endsWith("someURL")) {

        try {

            ResponseWrapper wrapperResponse = new ResponseWrapper(res);
            chain.doFilter(req, wrapperResponse);

            String responseContent = new String(wrapperResponse.getDataStream());
            System.out.println("response obtained:" + responseContent);

            try {

                List<String> s13 = Stream.of(responseContent).filter((s1) -> s1.contains("<Json>"))
                        .map((sample) -> Arrays.asList(sample.split(" ")))
                        .flatMap((listString) -> {
                            StringBuffer sb = new StringBuffer();
                            listString.forEach(item -> {

                                sb.append(item);

                            });
                            return Stream.of(sb.toString().trim().replace("<Json>", "").replace("</Json>", ""));
                        }).collect(Collectors.toList());
                s13.forEach(item -> System.out.println("items :" + item));
                String s14 = String.join("", s13);
                System.out.println("tt" + s14);
                PrintWriter writer = wrapperResponse.getWriter();
                writer.write(s14);
                writer.close();
            } catch (Exception e) {
                System.out.println(e.getLocalizedMessage());

            }

        } finally {
        }
    }
    chain.doFilter(req, res);
}

}

This is the servletoutputStream

public class FilterServletOutputStream extends ServletOutputStream {

ByteArrayOutputStream bos;

public FilterServletOutputStream(OutputStream output) {
    bos = (ByteArrayOutputStream) output;
}

@Override
public boolean isReady() {
    // TODO Auto-generated method stub
    return false;
}

@Override
public void setWriteListener(WriteListener listener) {
    // TODO Auto-generated method stub

}

@Override
public void write(int b) throws IOException {
    // TODO Auto-generated method stub
    bos.write(b);

}

@Override
public void write(byte[] arg0, int arg1, int arg2) throws IOException {
    bos.write(arg0, arg1, arg2);
}

@Override
public void write(byte[] arg0) throws IOException {
    bos.write(arg0);
}

}

This is the response Wrapper

public class ResponseWrapper extends HttpServletResponseWrapper {

ByteArrayOutputStream output;
FilterServletOutputStream filterOutput;

public ResponseWrapper(HttpServletResponse response) {
    super(response);
    output = new ByteArrayOutputStream();
    // TODO Auto-generated constructor stub
}

@Override
public ServletOutputStream getOutputStream() {
    if (filterOutput == null) {
        filterOutput = new FilterServletOutputStream(output);
    }
    return filterOutput;
}

public byte[] getDataStream() {
    return output.toByteArray();
}

}

Error:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'server.servlet.contextPath' in value "${server.servlet.contextPath}"
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172) ~[spring-core-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124) ~[spring-core-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:237) ~[spring-core-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:211) ~[spring-core-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175) ~[spring-context-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:834) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1086) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:91) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:373) ~[spring-beans-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    ... 22 common frames omitted

Could you please identify where the issue is and help in writing the new response data and send it over?

UPDATE: I have rewritten most of it. here is the code. Somehow the response document is coming as empty. I feel I have almost able to crack it. Could someone please help me out at the end part?

@Component
@Order(2)
public class DumpFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        // TODO Auto-generated method stub
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

        HttpServletResponse response = (HttpServletResponse) servletResponse;
        ByteArrayPrinter pw = new ByteArrayPrinter();
        HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) {
            @Override
            public PrintWriter getWriter() {
                return pw.getWriter();
            }

            @Override
            public ServletOutputStream getOutputStream() {
                return pw.getStream();
            }

        };
        System.out.println("before chaingin");
        chain.doFilter(httpRequest, wrappedResp);

        byte[] bytes = pw.toByteArray();
        String respBody = new String(bytes);
        if (respBody.startsWith("<Json>")) {
            System.out.println("in if");

            List<String> s13 = Stream.of(respBody).filter((s1) -> s1.contains("<Json>"))
                    .map((sample) -> Arrays.asList(sample.split(" ")))
                    .flatMap((listString) -> {
                        StringBuffer sb = new StringBuffer();
                        listString.forEach(item -> {

                            sb.append(item);

                        });
                        return Stream.of(sb.toString().trim().replace("<Json>", "").replace("</Json>", ""));
                    }).collect(Collectors.toList());
            s13.forEach(item -> {
                System.out.println("items in list:" + item);
                try {
                    response.getOutputStream().write(item.getBytes());
                } catch (IOException e) {
                    e.printStackTrace();
                }

            });

            // String s14 = String.join("", s13);
            // System.out.println("s14" + s14.getBytes());
            // bytes = s14.getBytes();
            // response.getOutputStream().write(s14.getBytes());
        } else {
            response.getOutputStream().write(bytes);
        }

        System.out.println("RESPONSE -> " + new String(bytes));

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }
}

here are the helper classes used:

public class ByteArrayPrinter {

    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    private PrintWriter pw = new PrintWriter(baos);

    private ServletOutputStream sos = new ByteArrayServletStream(baos);

    public PrintWriter getWriter() {
        return pw;
    }

    public ServletOutputStream getStream() {
        return sos;
    }

    byte[] toByteArray() {
        return baos.toByteArray();
    }
}




public class ByteArrayServletStream extends ServletOutputStream {

    ByteArrayOutputStream baos;

    ByteArrayServletStream(ByteArrayOutputStream baos) {
        this.baos = baos;
    }

    @Override
    public void write(int param) throws IOException {
        baos.write(param);
    }

    @Override
    public boolean isReady() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void setWriteListener(WriteListener listener) {
        // TODO Auto-generated method stub

    }

}

On calling the v2/api-docs?groupName=XXXXXX, I am ending up with the error - error on line 1 at column 1: Document is empty

Upvotes: 3

Views: 2872

Answers (2)

Assegd
Assegd

Reputation: 106

For my case as it is mentioned on the above (@Joey587), it is working from postman and getting errors from the browser side. So if you face the sam problem, try sending the request to access the page from postman side.

cheers

Upvotes: 0

vijayakumarpsg587
vijayakumarpsg587

Reputation: 1177

Atlast I have found the answer. For those who stumble upon such issue here is the answer to it

Issue#1 The problem if we make this filter execute it as the first filter by ordering it with HIGHEST PRECEDENCE will make the filter chaining and chaining and will run for perpetual time. So discard trying to order the filter.

Issue2#

If you try to execute the filter , since the response is corrupted, the content-type header will be set to application/xhtml or text/html (thanks to my colleague who pointed that out) by default and the browser tries to execute with the same content-type. But in POSTMAN and SOAPUI. It will execute it flawlessly

Issue#3 Since we are wrapping a response , we cannot set the content-type directly. So it has to set in the wrapper when we get the outputStream

Here is the implementation:

@Component
public class SwaggerFilter implements Filter {

    final String APPLICATION_XHTML = "application/xhtml";
    final String XML_ELEMENT_START = "<Json>";
    final String XML_ELEMENT_END = "</Json>";

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

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        // TODO Auto-generated method stub
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

        HttpServletResponse response = (HttpServletResponse) servletResponse;
        ByteArrayPrinter pw = new ByteArrayPrinter();
        HttpServletResponse wrappedResp = new HttpServletResponseWrapper(response) {

            @Override
            public void setContentType(final String type) {
                super.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            }

            @Override
            public PrintWriter getWriter() {
                return pw.getWriter();
            }

            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                ServletResponse response = this.getResponse();

                String ct = (response != null) ? response.getContentType() : null;
                if (ct != null && ct.contains(APPLICATION_XHTML)) {
                    response.setContentType(ct + "," + MediaType.APPLICATION_JSON_UTF8_VALUE);
                }
                return pw.getStream();
            }

        };
        chain.doFilter(httpRequest, wrappedResp);

        byte[] bytes = pw.toByteArray();
        String respBody = new String(bytes);
        if (respBody.startsWith(XML_ELEMENT_START)) {

            List<String> s13 = Stream.of(respBody).filter((s1) -> s1.contains(XML_ELEMENT_START))
                    .map((sample) -> Arrays.asList(sample.split(" ")))
                    .flatMap((listString) -> {
                        StringBuffer sb = new StringBuffer();
                        listString.forEach(item -> {

                            sb.append(item);

                        });
                        return Stream
                                .of(sb.toString().trim().replace(XML_ELEMENT_START, "").replace(XML_ELEMENT_END, ""));
                    }).collect(Collectors.toList());

            String s14 = String.join("", s13);

            response.getOutputStream().write(s14.getBytes());
        } else {
            response.getOutputStream().write(bytes);
        }

    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

Upvotes: 4

Related Questions