Как избежать отправки HTTP-запроса с веб-сервера Java самому себе?

Реальная ситуация выглядит так: веб-сервер Java (Weblogic) получает запрос от пользователя, для которого он должен отправить ZIP-архив в ответ. Архив должен быть динамически генерирован из некоторых запрошенных пользователем файлов и одного отчета HTML, созданного самим сервером. Я хотел бы повторно использовать сервлеты JSF, которые сервер уже использует в других случаях для создания этого отчета. Итак, в основном, я использую:

HttpURLConnection  self = new URL ("http://me.myself.com/report.jsf?...").openConnection ();
String  report_html = fetchHtmlFromConnection (self);

а затем создайте запрошенный ZIP, включая сгенерированный HTML.

Вопрос в том, можно ли каким-то образом избежать внутреннего HTTP-запроса (до report.jsf) в этом сценарии? Это в основном бессмысленно (поскольку приложение просто "разговаривает" с самим собой) совершает обратные вызовы через операционную систему, HTTPD (которая может быть на другой машине) и т.д.

Ответ 1

Я не очень хорошо знаком с JSF, но из того, что я понимаю, вы можете использовать технику, которая также применима к страницам JSP:

  • Создайте собственный HttpServletResponseWrapper (класс, используемый контейнером, который позволяет изменять ответ)
  • Используйте его, чтобы переопределить значение по умолчанию Writer (которое записывает отображаемую страницу на выход) и предоставить файл, который записывает вывод в String или временный файл, который будет передавать сжатый код.

Есть хороший и простой учебник, который показывает вам, как это сделать: http://blog.valotas.com/2011/09/get-output-of-jsp-or-servlet-response.html

Тогда

  • Как намекнул gyan, получите ServletRequestDispatcher из вашего сервлета, который позволит вам вызывать рендеринг JSF
  • Переслать вызов сервлета, чтобы предоставить свой HttpServletResponseWrapper
  • Используйте HttpServletResponseWrapper, чтобы получить обработанный HTML-код и передать его в zip-код.

Таким образом, серпантин будет выглядеть так:

TempFileRespWrapper respWrapper = new TempFileRespWrapper();
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher( "/report.jsf");
dispatcher.forward(request, respWrapper);
File f = respWrapper.getOutputPath();
addFileToZip(f);

Ответ 2

Подумайте о стратегии диспетчера запросов, где объект запроса/ответа будет отправлен в сервлет отчета из сервлета ввода. В свою очередь, сервлет отчета будет генерировать отчет, а управление может быть отправлено на следующий сервлет, что завершает остаток процесса zip и send.

Для построения объекта RequestDispatcher вы можете использовать либо метод ServletRequest.getRequestDispatcher(), либо метод ServletContext.getRequestDispatcher().

RequestDispatcher dispatcher=getServletContext().getRequestDispatcher( "/report.jsf" );
dispatcher.forward( request, response );

Следующий набор сервлета устанавливает тип mime как "application/zip" и записывает zip файл в браузер. Пользовательский браузер будет обрабатывать содержимое в виде загрузки в зависимости от настроек браузера.

Ответ 3

У вас должен быть уровень бизнес-сервиса, так что услуга "generateReport" может использоваться внутри нескольких представлений представления (даже для "zip файла" ).

Вы можете сделать это стандартным способом J2EE через EJB или через любую настраиваемую среду, которая позволяет вам указывать бизнес-услуги для инъекций (например, spring).

Я думаю, что основная проблема здесь заключается в том, что вы можете генерировать отчет только через клиентский интерфейс (http). Это делает его изолированной службой, недоступной для других частей приложения. В основном вам нужен рефакторинг.

Не кодируйте бизнес внутри JSF или аналогичных. (кстати, попробуйте не использовать jsf вообще: D)


  BUSINESS LAYER              PRESENTATION
generateReportService---|---jsf-HtmlReport
                     \__|___
                        |   \ 
someOtherContentService-|----jsf-Zip

Ответ 4

Убедитесь, что ваш веб-сервер настроен на кеширование (generated) html, и вам не придется беспокоиться об этом. Первый запрос на полный или частичный URL-адрес приведет к вызову генератора jsf, а затем он будет извлечен из кеша веб-сервера.

Да, будет немного накладных расходов

(ваш источник → мы сервер → страница-генератор или кеш)

Но в конце концов это будет проще.

Ответ 5

Протестированное решение!

Это решение действительно получает идеи, уже размещенные здесь (особенно из @gyan).

  • Написать сервлет для zip

(вы также можете использовать фильтр для этого. Например, предположим, что у вас есть ZipFilter. Вы можете сопоставить фильтр для всех *.zip и цепочек, которые фильтруют с помощью фильтра URLRewrite для соответствующего URL-адреса .jsf).

public class ZipServlet
    extends HttpServlet {

    @Override
    public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException,
            IOException {

        ZipEntry zipEntry = new ZipEntry("helloWorld.html");
        ZipHttpServletResponseWrapper respWrapper = new ZipHttpServletResponseWrapper(response, zipEntry);
        RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/helloWorld.jsf");
        dispatcher.forward(request, respWrapper);
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "inline; filename=output.zip;");
        response.flushBuffer();
        respWrapper.getOutputStream().close();

    }

}
  • ПРИМЕЧАНИЕ. Да, вы должны использовать RequestDispatcher

    • ZipHttpServletResponseWrapper

Нечего об этом говорить. Из Java 7 вы можете использовать собственный класс для правильного создания zip файлов. Использование шаблона Decorator с помощью ZipOutputStream поверх response.getOutputStream() следует рекомендовать.

Помните, что HttpServletResponseWrapper является декоратором. Вы не должны использовать это, если вы не хотите повторно использовать прямой вывод "сервлета" (вы можете использовать трюк HttpServletResponse, а не использовать HttpServletResponseWrapper).

public class ZipHttpServletResponseWrapper
    extends HttpServletResponseWrapper {

    private ZipEntry entry;
    private ZipServletOutputStreamWrapper streamWrapper;
    private ZipOutputStream outputStream;
    private PrintWriter printWriter;

    public ZipHttpServletResponseWrapper(HttpServletResponse response, ZipEntry entry) {
        super(response);
        this.entry = entry;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (streamWrapper == null) {
            outputStream = new ZipOutputStream(this.getResponse().getOutputStream());
            outputStream.putNextEntry(entry);
            streamWrapper = new ZipServletOutputStreamWrapper(outputStream);
        }
        return streamWrapper;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (printWriter == null) {
            printWriter = new PrintWriter(getOutputStream());
        }
        return printWriter;
    }

    private class ZipServletOutputStreamWrapper
        extends ServletOutputStream {

        private ZipOutputStream outputStream;

        public ZipServletOutputStreamWrapper(ZipOutputStream outputStream) {
            this.outputStream = outputStream;
        }

        @Override
        public void close() throws IOException {
            outputStream.closeEntry();
            outputStream.finish();
        }

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

}
  • Теперь секрет: отображение мудро!

В некоторых важных частях JSF можно использовать цепочку фильтров (например, myfaces из Apache используют некоторые расширения для предоставления некоторых компонентов JSF). В этом случае вы должны указать в этих фильтрах дайджест FORWARD и REQUEST

    <filter-mapping>
        <filter-name>extensionsFilter</filter-name>
        <url-pattern>*.jsf</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>    
    </filter-mapping>