フィルタは、HttpServeretResponseWrapperパッケージHttpServeretResponseによりresponseからの戻りデータの取得、およびデータのgzip圧縮を実現する

12149 ワード

先日、プロジェクトディレクターからリクエストされたインタフェースデータを圧縮して、トラフィックを節約する目的を達成するタスクがありました.
この機能を実現するには、次のような考え方があります.
1.responseの値を取得します.2.データをgzip圧縮する(フロントエンドが変わらないことが要求されるため、このブラウザでサポートされている圧縮方式しか選択できない).responseにデータを書き込む.responseをフロントエンドに返品
しかし、私が最初のステップを実行すると、卵が痛いことに遭遇し、responseの戻りデータが取れなくなり、ここでは無言で、インタフェースメソッドごとに処理方法を加えることは許されず、最初はブロッカーのafterCompletion()メソッドでデータ処理を行うことを考えていましたが、responseではbody値を取得する方法は提供されていませんでした.自分で何とかするしかない.
インターネットで検索することで、responseのデータを取得する方法があります.HttpServeretResponseWrapperパッケージHttpServeretResponseを使用して実現します.
HttpServeretResponseWrapperでresponseのデータを取得するには、2つのバージョンがあり、1つのバージョンの数は多いですが、まったく役に立たないでしょう.次のコードです.
public class ResponseWrapper extends HttpServletResponseWrapper {
    private PrintWriter cachedWriter;
    private CharArrayWriter bufferedWriter;

    public ResponseWrapper(HttpServletResponse response) throws IOException {
        super(response);
        bufferedWriter = new CharArrayWriter();
        cachedWriter = new PrintWriter(bufferedWriter);
    }

    public PrintWriter getWriter() throws IOException {
        return cachedWriter;
    }

    public String getResult() {
        byte[] bytes = bufferedWriter.toString().getBytes();
        try {
            return new String(bytes, "UTF-8");
        } catch (Exception e) {
            LoggerUtil.logError(this.getClass().getName(), "getResult", e);
            return "";
        }
    }
}

テストgetResult()では値が得られません.具体的には、上記のコードを検討してみると、なぜか、完全に穴ですね.ここではあまり言いません.
もう一つのバージョン、つまり私が今使っている(ここではまずこの兄弟たちに感謝します.具体的な元の経路はすぐ下に貼られます)です.下には私のコードの元のコードがあります.私のところに問題があります.この問題があるのか、それとも私に問題があるのか、下にはどんな問題があるのか、どうやって解決したのか分かりません.
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;

public class ResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    private HttpServletResponse response;
    private PrintWriter pwrite;

    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new MyServletOutputStream(bytes); //       byte  
    }

    /**
     *       getWriter()   ,         PrintWriter  
     */
    @Override
    public PrintWriter getWriter() throws IOException {
        try{
            pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
        } catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return pwrite;
    }

    /**
     *       PrintWriter       
     * @return
     */
    public byte[] getBytes() {
        if(null != pwrite) {
            pwrite.close();
            return bytes.toByteArray();
        }

        if(null != bytes) {
            try {
                bytes.flush();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
        return bytes.toByteArray();
    }

    class MyServletOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream ostream ;

        public MyServletOutputStream(ByteArrayOutputStream ostream) {
            this.ostream = ostream;
        }

        @Override
        public void write(int b) throws IOException {
            ostream.write(b); //       stream  
        }

    }

}

HttpServeretResponseのパッケージクラスはフィルタでしか使用できないので、フィルタでしか実現できません.以下は私のフィルタのdoFilter()メソッドのコードです.
 @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String headEncoding = ((HttpServletRequest)servletRequest).getHeader("accept-encoding");
        if (headEncoding == null || (headEncoding.indexOf("gzip") == -1)) { //         gzip
            filterChain.doFilter(servletRequest, servletResponse);
            System.out.println("----------------       gzip    -----------------");
        } else { //    gzip   ,     gzip  

            HttpServletRequest req = (HttpServletRequest) servletRequest;
            HttpServletResponse resp = (HttpServletResponse) servletResponse;
            ResponseWrapper mResp = new ResponseWrapper(resp); //        resp        

            filterChain.doFilter(req, mResp);

            byte[] bytes = mResp.getBytes(); //          
            System.out.println("     :" + bytes.length);
            System.out.println("     :" + new String(bytes,"utf-8"));

            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            GZIPOutputStream gzipOut = new GZIPOutputStream(bout); //    GZIPOutputStream   

            gzipOut.write(bytes); //          Gzip     
            gzipOut.flush();
            gzipOut.close(); //         bout      

            byte[] bts = bout.toByteArray();
            System.out.println("     :" + bts.length);
            resp.setHeader("Content-Encoding", "gzip"); //        
            resp.getOutputStream().write(bts); //            
        }
    }

ここで上のコードを説明して、まずrequestリクエストがgzip圧縮を受け入れるかどうかを判断します.これはrequestのリクエストヘッダのaccept-encodingという属性に基づいて判断します.現在の各ブラウザはgzipをサポートしているので、gzip圧縮をしたい場合は、フロントエンドはこのリクエストヘッダを加えるだけで、バックエンドから返されるデータがgzip圧縮されたデータであれば、ブラウザは自動的に解凍します.
上のコードがgzip圧縮をサポートしていない場合は、処理しないで、通常の流れを下に進みます.gzip圧縮がサポートされている場合は、データ処理が必要です.
このコードを見てみましょう
filterChain.doFilter(req, mResp);

この方法は重要で、この方法の前の部分はすべてインタフェースを要求する前の部分で、もしあなたがインタフェースを呼び出す前に統一的に処理したいものがあれば、もちろんブロックのpreHandle()方法で処理することもできます.対応するこの方法の後の部分は,要求インタフェースに戻り値がある後の部分である.つまり今回はデータを圧縮する部分が必要です.
もちろん注意が必要なのはdoFilterの2番目のパラメータで、もともとサーブレットResponseオブジェクトだったのですが、今はデータを処理するためにResponseWrapperクラスを使ってサーブレットResponseをパッケージしているので、2番目のパラメータはResponseWrapperオブジェクトを伝えています.もちろん、servletRequestをパッケージしている場合は、では、最初のパラメータはservletRequestクラスをパッケージしたオブジェクトを伝えます.
次に、パッケージクラスのオブジェクトで返すデータを取得し、GZIPOutputStreamを使用してデータを圧縮するrespを使用する.getOutputStream().write(bts); 圧縮されたデータをresponseに書き込むには、当然、返されるリクエストヘッダにContent-Encoding(返されるコンテンツ符号化)をgzip形式とする必要があることを忘れてはいけない.
これによりresponseのデータを取り出して圧縮してフロントエンドに戻ることができます.もちろん圧縮する必要はありません.暗号化などの処理もできます.
上の流れの中で、私は1つの问题に出会って、注意しなければならなくて、あなた达が出会ったかどうか分かりませんが、上の流れはすべて正常に行って、データも取得して、圧縮も圧縮して、実行时间も印刷して、しかしフロントエンドはずっと応答の中で、つまり私达の応答の遅さ、私は见て、平均は30秒ぐらいで、これでは納得できない.
最初はフロントエンドがgzipデータを解凍する速度が遅すぎると思っていましたが、gzip関連コードを遮断して、データの戻りが同じように遅いので、gzip圧縮解凍排除しました.
そして一つだけ問題があります.それは私たちのパッケージクラスResponseWrapperに問題があります.debugを通じて、私たちがパッケージしたクラスの各方法の実行順序を発見しました.
まず、newオブジェクトの構築方法ResponseWrapper(HttpServertResponse response)メソッドを呼び出し、フィルタのdoFilterメソッドを実行するときにパッケージクラスのgetoutputStream()メソッドを呼び出して、定義したByteArrayOutputStream、すなわちbyttesにデータを書き込みます.次にgetBytes()メソッドを呼び出してbytesをbyte配列に変換して返します.これが戻りデータです.
上記の流れから,理論的には問題なく,実際には我々が望んでいるデータも得られ,これらの方法は実行速度も速く,どの部分にもカートンが住んでいないことが分かった.では、問題はどこにあるのでしょうか.私はネットで半日探しましたが、この方面の資料は少ないです.最後にブログで、このコードを書いたのは、データを書く前にResponseオブジェクトを使ってcontentLengthをチャージする必要があります.つまり次のコードです
response.setContentLength(-1);

ここで最初はどこにこのコードを入れるか考えていませんでしたが、フィルターの中で考えていましたが、入れるタイミングが間違っていたと思いました.その後、パッケージ類を見てみると、このコードを書いた兄たちがHttpServeretResponseオブジェクトを定義し、構造方法でも初期化していることがわかりました.しかし全文はこのresponseオブジェクトには用いられなかった.getoutputStream()を呼び出してbytesにデータを書き込む前にこのコードを付けたのではないかと思います.やってみましたが、本当にいいですね.これで問題が解決する.
今回の需要は、相応の緩やかな問題をどのように解決するかに1日かかりましたが、多くのものを収穫しました.だからここでコードの皆さんに感謝します.そして、短いですが、私の最終的な問題を解決したブログを書いた皆さんに感謝します.
次は2つのブログのアドレスです.http://blog.csdn.net/yy417168602/article/details/53534776 http://blog.csdn.net/qbian/article/details/53909778