Проблема с RequestDispatcher, включая JSP программно в Weblogic 12c

Я борюсь со следующей ситуацией:

в нашем текущем веб-приложении, работающем на Tomcat 7.0.64, нам удается включить страницу JSP через Java с помощью собственного класса CharArrayWriterResponse implementing HttpServletResponseWrapper.

причина этого заключается в том, что мы обертываем полученный HTML в JSON, необходимый для ответа AJAX.

зависимости:

<dependency>
     <groupId>javax</groupId>
     <artifactId>javaee-web-api</artifactId>
     <version>7.0</version>
     <scope>provided</scope>
</dependency>
<dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>jstl</artifactId>
     <version>1.2</version>
</dependency>

пример кода:

// somewhere in servlet doPost()/doGet()
try (PrintWriter out = response.getWriter()) {
     out.println(getJspAsJson(request, response));
}

private static String getJspAsJson(HttpServletRequest request, HttpServletResponse response) {
    String html = getHtmlByJSP(request, response, "WEB-INF/path/to/existing.jsp");
    Gson gson = new GsonBuilder().disableHtmlEscaping().create();
    return "{"results":" + gson.toJson(html) + "}";
}

public static String getHtmlByJSP(HttpServletRequest request, HttpServletResponse response, String jsp) {
     CharArrayWriterResponse customResponse = new CharArrayWriterResponse(response);
     request.getRequestDispatcher(jsp).include(request, customResponse);
     return customResponse.getOutput();
}

public class CharArrayWriterResponse extends HttpServletResponseWrapper {
    private final CharArrayWriter charArray = new CharArrayWriter();

    public CharArrayWriterResponse(HttpServletResponse response) {
        super(response);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        // this is called ONLY in tomcat
        return new PrintWriter(charArray);
    }

    public String getOutput() {
        return charArray.toString();
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        // this is called ONLY in WebLogic
        return null; // don't know how to handle it
    }
}

подсказка: я не рассматривал обработку исключений в приведенном выше коде образцы.

мне нужно перенести это приложение на WebLogic (12.2.1), но это решение больше не работает.

то, что я узнал до сих пор:

в Tomcat после вызова request.getRequestDispatcher(jsp).include() примера выше getWriter() моего CharArrayWriterResponse класс называется.

В WebLogic getWriter() больше не вызывается, и именно поэтому он больше не работает.

после некоторой отладки я узнал, что в WebLogic вместо getWriter() только getOutputStream() вызывается, если я переопределяю его. getWriter() не вызывается один раз на Weblogic, поэтому должны быть различия в базовой реализации Tomcat и WebLogic.

проблема в том, что с getOutputStream() Я не вижу возможности получить ответ include() вызовите отдельный поток или что-то еще и преобразуйте его в строку для использования для создания окончательного JSON, содержащего HTML.

кто-то уже решил эту проблему и может предоставить рабочее решение для включая JSP программным способом в сочетании с WebLogic?

кто-нибудь знает другое решение для достижения моей цели?

Спасибо за предложения.


решение

посмотреть рабочий пример здесь

подсказка

разницу я между Tomcat и WebLogic и новые решения: С последним невозможно включить jspf непосредственно больше wheras с Tomcat getWriter() это есть.

решение обертывает JSPF внутри файла JSP.

3 ответов


Я сделал так:

@Override
public ServletOutputStream getOutputStream() throws IOException {
    // this is called ONLY in WebLogic
    // created a custom outputstream that wraps your charArray
    return new CustomOutputStream(this.charArray);
}

// custom outputstream to wrap charArray writer
class CustomOutputStream extends ServletOutputStream {

    private WriterOutputStream out;

    public CustomOutputStream(CharArrayWriter writer) {
        // WriterOutputStream has a constructor without charset but it's deprecated, so change the UTF-8 charset to the one you use, if needed
        this.out = new WriterOutputStream(writer, "UTF-8");
    }

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

    @Override
    public void setWriteListener(WriteListener writeListener) {
    }

    @Override
    public void write(int b) throws IOException {
        this.out.write(b);
        this.out.flush(); // it doesn't work without flushing
    }
}

Я WriterOutputStream из Apache commons-io, поэтому мне пришлось включить в свой pom.XML-код:

<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.5</version>
</dependency>

Я не знаю, что в вашем файле jsp, но я тестировал с простым, и я считаю, что это сработало. Мой файл jsp:

<b>Hello world</b>

<p>testing</p>

<ul>test
<li>item</li>
<li>item2</li>
</ul>

вывод (при доступе к сервлету в браузере):

{"results":"<b>Hello world</b>\n\n<p>testing</p>\n\n<ul>test\n<li>item</li>\n<li>item2</li>\n</ul>"}

поскольку я не могу понять, как поместить многострочный код в комментарии, я просто помещаю его здесь. Я мог бы исправить вопрос переопределив flush() метод MyServletOutputStream класс:

// inside MyServletOutputStream class
@Override
public void flush() throws IOException {
    if (this.bufferedOut != null) {
        this.bufferedOut.flush();
    }
    super.flush();
}

вот мой обновленный и рабочий пример, как включить файл JSP программно, если getOutputStream() вместо getWriter() вызывается при реализации HttpServletResponseWrapper:

public class MyServletOutputStream extends ServletOutputStream {

    private final BufferedOutputStream bufferedOut;

    public MyServletOutputStream(CharArrayWriter charArray) {
        this.bufferedOut = new BufferedOutputStream(new WriterOutputStream(charArray, "UTF-8"), 16384);
    }

    @Override
    public void write(int b) throws IOException {
        this.bufferedOut.write(b);
    }

    /**
     * This is needed to get correct full content without anything missing
     */
    @Override
    public void flush() throws IOException {
        if (this.bufferedOut != null) {
            this.bufferedOut.flush();
        }
        super.flush();
    }

    @Override
    public void close() throws IOException {
        this.bufferedOut.close();
        super.close();
    }

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

    @Override
    public void setWriteListener(WriteListener writeListener) {
    }
}

public class CharArrayWriterResponse extends HttpServletResponseWrapper {

    private final CharArrayWriter charArray = new CharArrayWriter();
    private ServletOutputStream servletOutputStream;

    public CharArrayWriterResponse(HttpServletResponse response) {
        super(response);
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (servletOutputStream == null) {
            servletOutputStream = new MyServletOutputStream(this.charArray);
        }
        return servletOutputStream;
    }

    public String getOutputAndClose() {
        if (this.servletOutputStream != null) {
            try {
                // flush() is important to get complete content and not last "buffered" part missing
                this.servletOutputStream.flush()
                return this.charArray.toString();
            } finally {
                this.servletOutputStream.close()
            }
        }
        throw new IllegalStateException("Empty (null) servletOutputStream not allowed");
    }

    // not necessary to override getWriter() if getOutputStream() is used by the "application server".
}

// ...somewhere in servlet process chain e.g. doGet()/doPost()
// request/response The original servlet request/response object e.g. from doGet/doPost(HttpServletRequest request, HttpServletResponse response)
CharArrayWriterResponse customResponse = new CharArrayWriterResponse(response);
request.getRequestDispatcher("/WEB-INF/path/to/existing.jsp").include(request, customResponse);
String jspOutput = customResponse.getOutputAndClose();
// do some processing with jspOut e.g. wrap inside JSON

// customResponse.getOutputStream() is already closed by calling getOutputAndClose()