36

サーブレットフィルターを使用して応答にコンテンツを挿入する人の例をネットとスタックオーバーフローで検索してきましたが、出力をキャプチャ/圧縮したり、ヘッダーを変更したりする人の例しか見つかりません。私の目標は、すべてのHTML応答の終了</body>の直前にHTMLのチャンクを追加することです。

私は、HttpServletResponseWrapperを拡張して独自のPrintWriterを使用し、その上で書き込みメソッドをオーバーライドするソリューションに取り組んでいます。writeメソッド内に、最後の7文字を格納して、本文の終了タグと等しいかどうかを確認します。次に、残りのドキュメントに対して通常の書き込み操作を続行する前に、HTMLチャンクと本文の終了タグを書き込みます。

誰かがすでにこの問題を解決したに違いないと思います。おそらく私よりもエレガントに。サーブレットフィルタを使用してコンテンツを応答に挿入する方法の例をいただければ幸いです。

更新しました

コメントに応じて、 http: //www.oracle.com/technetwork/java/filters-137243.htmlからCharResponseWrapperを実装しようとしています。これが私のコードです:

PrintWriter out = response.getWriter();
CharResponseWrapper wrappedResponse = new CharResponseWrapper(
        (HttpServletResponse)response);

chain.doFilter(wrappedRequest, wrappedResponse);
String s = wrappedResponse.toString();

if (wrappedResponse.getContentType().equals("text/html") &&
        StringUtils.isNotBlank(s)) {
    CharArrayWriter caw = new CharArrayWriter();
    caw.write(s.substring(0, s.indexOf("</body>") - 1));
    caw.write("WTF</body></html>");
    response.setContentLength(caw.toString().length());
    out.write(caw.toString());
}
else {
    out.write(wrappedResponse.toString());
}

out.close();

リクエストもラップしていますが、そのコードは機能し、レスポンスに影響を与えることはありません。

4

4 に答える 4

45

私が使用しているコードベースは、応答を処理するときにgetWriterではなくgetOutputStreamメソッドを呼び出すため、他の回答に含まれている例は役に立ちません。これは、出力ストリームとPrintWriterの両方で機能する、より完全な回答です。ライターに2回アクセスすると、正しくエラーが発生します。これは、JAVAX.SERVLET.FILTERを使用したDUMP REQUESTANDRESPONSEという優れた例から派生しています。

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class MyFilter implements Filter
{
    private FilterConfig filterConfig = null;

    private static class ByteArrayServletStream extends ServletOutputStream
    {
        ByteArrayOutputStream baos;

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

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

    private static class ByteArrayPrintWriter
    {

        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 CharResponseWrapper extends HttpServletResponseWrapper
    {
        private ByteArrayPrintWriter output;
        private boolean usingWriter;

        public CharResponseWrapper(HttpServletResponse response)
        {
            super(response);
            usingWriter = false;
            output = new ByteArrayPrintWriter();
        }

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

        @Override
        public ServletOutputStream getOutputStream() throws IOException
        {
            // will error out, if in use
            if (usingWriter) {
                super.getOutputStream();
            }
            usingWriter = true;
            return output.getStream();
        }

        @Override
        public PrintWriter getWriter() throws IOException
        {
            // will error out, if in use
            if (usingWriter) {
                super.getWriter();
            }
            usingWriter = true;
            return output.getWriter();
        }

        public String toString()
        {
            return output.toString();
        }
    }

    public void init(FilterConfig filterConfig) throws ServletException
    {
        this.filterConfig = filterConfig;
    }

    public void destroy()
    {
        filterConfig = null;
    }

    public void doFilter(
            ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        CharResponseWrapper wrappedResponse = new CharResponseWrapper(
                (HttpServletResponse)response);

        chain.doFilter(request, wrappedResponse);
        byte[] bytes = wrappedResponse.getByteArray();

        if (wrappedResponse.getContentType().contains("text/html")) {
            String out = new String(bytes);
            // DO YOUR REPLACEMENTS HERE
            out = out.replace("</head>", "WTF</head>");
            response.getOutputStream().write(out.getBytes());
        }
        else {
            response.getOutputStream().write(bytes);
        }
    }
}
于 2013-02-06T23:57:45.060 に答える
18

応答を変更するには、HttpServletResponseWrapperを実装する必要があります。このドキュメント「TheEssentialsofFilters 」を参照してください。これには、応答を変換する例があります。これは、必要以上のものです。

編集

応答フィルターを使用した単純なサーブレットを試しましたが、完全に機能しました。サーブレットは文字列Testを出力し、応答フィルターはそれに文字列filteredを追加し、最後にブラウザーから実行するTest filteredと、達成しようとしている応答を取得します。

以下のコードをApacheTomcat7で実行しましたが、例外なく動作しています。

サーブレット:

protected void doGet(HttpServletRequest request,
        HttpServletResponse response) throws ServletException, IOException {

   response.getWriter().println("Test");

}

フィルター:

public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

    System.out.println("BEFORE filter");
    PrintWriter out = response.getWriter();
    CharResponseWrapper responseWrapper = new CharResponseWrapper(
            (HttpServletResponse) response);

    chain.doFilter(request, responseWrapper);

    String servletResponse = new String(responseWrapper.toString());

    out.write(servletResponse + " filtered"); // Here you can change the response


    System.out.println("AFTER filter, original response: "
            + servletResponse);

}

CharResponseWrapper(記事とまったく同じ)

public class CharResponseWrapper extends HttpServletResponseWrapper {
    private CharArrayWriter output;

    public String toString() {
        return output.toString();
    }

    public CharResponseWrapper(HttpServletResponse response) {
        super(response);
        output = new CharArrayWriter();
    }

    public PrintWriter getWriter() {
        return new PrintWriter(output);
    }
}

web.xml

<servlet>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>TestServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/TestServlet</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>TestFilter</filter-name>
    <filter-class>MyFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <url-pattern>/TestServlet/*</url-pattern>
</filter-mapping>
于 2013-02-06T19:08:27.733 に答える
4

iTechの回答は部分的に機能しましたが、これはその回答に基づいています。

ただし、一部のWebサーバー(およびAppEngine Standard)は、Filter内でchain.doFilterを最初に呼び出した後、outputStreamを閉じているように見えることに注意する必要があります。

したがって、事前に保存されたPrintWritterに書き込む必要がある場合、ストリームは閉じられ、空白の画面が表示されます。(私は何が起こっているのかを理解するためのエラーさえも受け取りませんでした)。

したがって、私にとっての解決策は、「ダミー」のServletOutputStreamを作成し、ResponseWrapperのgetOutputStreamメソッドに戻ることでした。

これらの変更とiTechのソリューションにより、完全にレンダリングされたjsp応答をhtmlのjson応答内に挿入することができました(引用符などの競合する文字を適切にエスケープします)。

これは私のコードです:

Myfilter

@WebFilter({"/json/*"})    
public class Myfilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //Save original writer
        PrintWriter out = response.getWriter(); 
        //Generate a response wrapper with a different output stream
        ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
        //Process all in the chain (=get the jsp response..)
        chain.doFilter(request, responseWrapper);
        //Parse the response
        out.write("BEFORE"+responseWrapper.toString()+"AFTER"); //Just + for clear display, better use a StringUtils.concat
    }
    @Override
    public void destroy() {}
}

私のResponseWrapper

public class ResponseWrapper extends HttpServletResponseWrapper {
    private StringWriter output;
    public String toString() {
        return output.toString();
    }
    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        //This creates a new writer to prevent the old one to be closed
        output = new StringWriter();
    }
    public PrintWriter getWriter() {
        return new PrintWriter(output,false);
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        //This is the magic to prevent closing stream, create a "virtual" stream that does nothing..
        return new ServletOutputStream() {
            @Override
            public void write(int b) throws IOException {}
            @Override
            public void setWriteListener(WriteListener writeListener) {}
            @Override
            public boolean isReady() {
                return true;
            }
        };
    }
}
于 2018-03-13T15:37:08.340 に答える
1

素晴らしい!ただし、コンテンツの長さを更新してください。

        String out = new String(bytes);
        // DO YOUR REPLACEMENTS HERE
        out = out.replace("</head>", "WTF</head>");
        response.setContentLength(out.length());
        response.getOutputStream().write(out.getBytes());
于 2019-07-04T17:14:22.750 に答える