Tomcat 8 Disable Chunked Encoding Filter

У нас есть VPN, который мы иногда используем для удаленного подключения к сайтам только для того, чтобы обнаружить, что tomcat, использующий фрагментированную кодировку, часто использует усеченные файлы JavaScript. В попытке исправить это я хотел создать фильтр Tomcat для файлов JS, который отправляет их за один раз.

Читая статьи здесь и здесь, я попытался сделать свой собственный снимок и попытался реализовать его следующим образом.

Я подключил фильтр к своему контексту с помощью пользовательской карты фильтров

new CustomFilterMap(
    new String[]{"*.js"},
    new String[]{"REQUEST"}
)

Эта часть действительно работает хорошо, и мой фильтр, похоже, применяется к файлам JS. Теперь часть, которую я не получил так хорошо, - это настоящий фильтр.

public class NoChunkEncodedJSFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        val unversionedServletPath = request.getRequestURI().substring(request.getRequestURI().indexOf("/js"));
        val requestFilePath = getServletContext().getRealPath(unversionedServletPath);

        if (requestFilePath != null) {
            File requestedFile = new File(requestFilePath);
            if (requestedFile.exists()) {
                val fileSize = Math.toIntExact(requestedFile.length());

                val out = response.getWriter();
                val wrappedResponse = new NonEncodedResponse(response, fileSize);
                filterChain.doFilter(request, wrappedResponse);

                out.write(wrappedResponse.toString(), "UTF-8");
                out.close();
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

С моим NonEncodedResponse:

public class NonEncodedResponse extends HttpServletResponseWrapper {
    private ByteArrayOutputStream baos;

    public String toString() {
        try {
            return baos.toString("UTF-8");
        } catch (UnsupportedEncodingException unsuportedEncodingException) {
            return baos.toString();
        }
    }

    /**
     * Constructs a response adaptor wrapping the given response.
     *
     * @param response The response to be wrapped
     * @throws IllegalArgumentException if the response is null
     */
    public NonEncodedResponse(HttpServletResponse response, Integer fileSize) {
        super(response);

        this.setContentLength(fileSize);
        this.setBufferSize(fileSize);
        super.setContentLength(fileSize);
        super.setBufferSize(fileSize);

        baos = new ByteArrayOutputStream(fileSize);
    }

    @Override
    public PrintWriter getWriter(){
        return new PrintWriter(baos);
    }
}

Первоначально я пытался вообще не оборачивать ответ, а просто звонить. response.setBufferSize(fileSize); а также response.setContentLength(fileSize); но это, казалось, имело 0 фактических эффектов на моем выводе, и когда я смотрел на заголовки, я все еще использовал кодирование передачи по частям без фиксированной длины содержимого. (Я также попытался установить собственный заголовок, подобный этому, и не увидел, чтобы он добавлялся к моему ответу. Я предполагаю, что ответ, который входит в фильтр в его базовой форме, является какой-то формой только для чтения.)

Я также попытался использовать мою обертку и обойти поток вывода, читая и отправляя байты прямо из файла

val fileContents = Files.readAllBytes(Paths.get(requestFilePath));
out.write(new String(fileContents));

потому что казалось, что даже при том, что мои попытки исправить длину содержимого потерпели неудачу, я все еще видел только части файлов, отправляемых как целое на вкладке сети моих браузеров, при попытке отладки.

В общем, теперь у меня есть такие файлы (вероятно, не идеальные), и даже в идеале это все еще говорит Transfer-Encoding: chunked несмотря на все мои попытки поместить фиксированную длину содержимого в мою оболочку и оригинальный ответ. Теперь я могу добавить свой контент в заголовок, чтобы знать, что он проходит через мой фильтр. Кажется, по какой-то причине он все еще просто закодирован.

Может кто-нибудь сказать мне, что я делаю не так, или, если вообще можно использовать фильтры, чтобы отключить чанкованное кодирование (я думаю, я видел вещи, предполагающие, что они могут это сделать из поисков в Google, но на самом деле я просто не знаю). Я с радостью приветствую любые советы по этому вопросу, у меня больше нет идей, чтобы попробовать, и я, конечно, не знаю, что я делаю.

1 ответ

Так что мне действительно удалось заставить это работать, вернувшись к моему первоначальному подходу и полностью отказавшись от своей обертки, и переместив filterChain.doFilter до конца моего блока кода. Я не совсем уверен, почему это работает по сравнению с тем, что я делал, потому что честно признаться, я не знаю, что filterChain.doFilter на самом деле вообще. Это был мой конечный результат, который заставил все работать.

public class NoChunkEncodedJSFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        val requestFilePath = this.getRealFilePath(request.getRequestURI());
        File requestedFile = new File(requestFilePath);

        if (requestedFile.exists()) {
            val fileSize = Math.toIntExact(requestedFile.length());
            val fileContents = Files.readAllBytes(Paths.get(requestFilePath));

            response.reset();
            response.setHeader("Content-Length", String.valueOf(fileSize));
            response.setContentLength(fileSize);

            ServletOutputStream sos = response.getOutputStream();
            sos.write(fileContents);
            sos.close();
        }

        filterChain.doFilter(request, response);
    }

    private String getRealFilePath(String requestURI) {
        val unversionedServletPath = requestURI.substring(requestURI.indexOf("/js"));
        return getServletContext().getRealPath(unversionedServletPath);
    }
}

Теперь у меня остается только один вопрос: есть ли более разумный способ получить данные буфера для моего файла, или мне нужно каждый раз перечитывать файл с диска? Я предполагаю, что где-то там мой файл, вероятно, уже загружен в память, готовую к обработке. Здесь я загружаю его в память во второй раз, и я думаю, что это не очень эффективно.

Другие вопросы по тегам