001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.filter;
017
018import org.opengion.fukurou.util.StringUtil;
019
020import java.io.IOException;
021import jakarta.servlet.ServletRequest;
022import jakarta.servlet.ServletResponse;
023import jakarta.servlet.Filter;
024import jakarta.servlet.FilterChain;
025import jakarta.servlet.FilterConfig;
026import jakarta.servlet.ServletException;
027import jakarta.servlet.http.HttpServletRequest;
028import jakarta.servlet.http.HttpServletResponse;
029
030/**
031 * GZIPFilter は、Filter インターフェースを継承した ZIP圧縮クラスです。
032 * web.xml で filter 設定することにより、Webアプリケーションへのアクセスを制御できます。
033 * フィルタへのパラメータは、ipAddress と debug を指定できます。
034 * ipAddress は、リモートユーザーのIPアドレスをCSV形式で複数指定できます。
035 * これは、CSV形式で分解した後、アクセス元のアドレスの先頭文字列の一致を
036 * 判定しています。
037 *
038 * フィルターに対してweb.xml でパラメータを設定します。
039 * <ul>
040 *   <li>ipAddress :フィルタするリモートIPアドレス。無指定時は、ZIP圧縮しません。</li>
041 *   <li>debug     :フィルタ処理の詳細情報を出力します。(デバッグモード)</li>
042 * </ul>
043 *
044 *<pre>
045 * 【WEB-INF/web.xml】
046 *     &lt;filter&gt;
047 *         &lt;filter-name&gt;GZIPFilter&lt;/filter-name&gt;
048 *         &lt;filter-class&gt;org.opengion.hayabusa.filter.GZIPFilter&lt;/filter-class&gt;
049 *         &lt;init-param&gt;
050 *             &lt;param-name&gt;ipAddress&lt;/param-name&gt;
051 *             &lt;param-value&gt;192.168.&lt;/param-value&gt;
052 *         &lt;/init-param&gt;
053 *         &lt;init-param&gt;
054 *             &lt;param-name&gt;debug&lt;/param-name&gt;
055 *             &lt;param-value&gt;true&lt;/param-value&gt;
056 *         &lt;/init-param&gt;
057 *     &lt;/filter&gt;
058 *
059 *     &lt;filter-mapping&gt;
060 *         &lt;filter-name&gt;GZIPFilter&lt;/filter-name&gt;
061 *         &lt;url-pattern&gt;/jsp/*&lt;/url-pattern&gt;
062 *     &lt;/filter-mapping&gt;
063 *</pre>
064 *
065 * @version  4.0
066 * @author   Kazuhiko Hasegawa
067 * @since    JDK5.0,
068 */
069public class GZIPFilter implements Filter {
070        private String[] ipaddrArray    ;
071        private boolean  isDebug                ;
072
073        /**
074         * デフォルトコンストラクター
075         *
076         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
077         */
078        public GZIPFilter() { super(); }                // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
079
080        /**
081         * Filter インターフェースの doFilter メソッド
082         *
083         * Filter クラスの doFilter メソッドはコンテナにより呼び出され、 最後のチェーンにおける
084         * リソースへのクライアントリクエストのために、 毎回リクエスト・レスポンスのペアが、
085         * チェーンを通して渡されます。 このメソッドに渡される FilterChain を利用して、Filter が
086         * リクエストやレスポンスをチェーン内の次のエンティティ(Filter)にリクエストとレスポンスを
087         * 渡す事ができます。
088         * このメソッドの典型的な実装は以下のようなパターンとなるでしょう。
089         * 1. リクエストの検査
090         * 2. オプションとして、入力フィルタリング用にコンテンツもしくはヘッダをフィルタリング
091         *    するためにカスタム実装によるリクエストオブジェクトのラップ
092         * 3. オプションとして、出力フィルタリング用にコンテンツもしくはヘッダをフィルタリング
093         *    するためにカスタム実装によるレスポンスオブジェクトラップ
094         * 4. 以下の a)、b) のどちらか
095         *    a) FileterChain オブジェクト(chain.doFilter()) を利用してチェーンの次のエンティティを呼び出す
096         *    b) リクエスト処理を止めるために、リクエスト・レスポンスのペアをフィルタチェーンの次の
097         *       エンティティに渡さない
098         * 5. フィルタチェーンの次のエンティティの呼び出した後、直接レスポンスのヘッダをセット
099         *
100         * @param       req             ServletRequestオブジェクト
101         * @param       res             ServletResponseオブジェクト
102         * @param       chain   FilterChainオブジェクト
103         * @throws IOException 入出力エラーが発生したとき
104         * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
105         */
106        @Override       // Filter
107        public void doFilter( final ServletRequest req,
108                                                        final ServletResponse res,
109                                                        final FilterChain chain )
110                                                                throws IOException, ServletException {
111                if( req instanceof HttpServletRequest && res instanceof HttpServletResponse ) {
112                        final HttpServletRequest request = (HttpServletRequest) req;
113                        final HttpServletResponse response = (HttpServletResponse) res;
114                        if( isFiltering( request ) ) {
115                                final GZIPResponseWrapper wrappedResponse = new GZIPResponseWrapper(response);
116                                chain.doFilter(req, wrappedResponse);
117                                wrappedResponse.finishResponse();
118                                return;
119                        }
120                }
121                chain.doFilter(req, res);
122        }
123
124        /**
125         * フィルター処理の判定
126         *
127         * フィルター処理を行うかどうかを判定します。
128         * ipAddress と前方一致するリモートクライアントからのアクセス時に、
129         * GZIPフィルタリング処理を行います。
130         *
131         * @param       request HttpServletRequestオブジェクト
132         *
133         * @return      フィルター処理を行うかどうか[true:フィルタ対象/false:非対象]
134         */
135        private boolean isFiltering( final HttpServletRequest request ) {
136
137                boolean isFilter = false;
138
139                final String ae   = request.getHeader("accept-encoding");
140                final String uri  = request.getRequestURI();
141                final String adrs = request.getRemoteAddr();
142
143                // ブラウザが GZIP 対応かどうか
144                if( ae != null && ae.indexOf("gzip") >= 0 ) {
145                        // リクエストが、jsp,js,css かどうか
146                        if( uri.endsWith(".jsp") || uri.endsWith(".js") || uri.endsWith(".css") ) {
147                                // アドレスが、指定のアドレス配列で先頭一致しているかどうか
148                                for( int i=0; i<ipaddrArray.length; i++ ) {
149                                        if( adrs.startsWith( ipaddrArray[i] ) ) {
150                                                isFilter = true;        // 一致
151                                                break;
152                                        }
153                                }
154                        }
155                }
156
157                if( isDebug ) {
158                        System.out.println("[Filtering " + isFilter + "]");
159                        System.out.println("  IP Address :"     + adrs );
160                        System.out.println("  Request URI:"     + uri );
161                }
162
163                return isFilter;
164        }
165
166        /**
167         * Filter インターフェースの init メソッド (何もしません)。
168         *
169         * Web コンテナは、Filter をサービス状態にするために init メソッドを呼び出します。
170         * Servlet コンテナは、Filter をインスタンス化したあと、 一度だけ init メソッドを呼び出します。
171         * Filter がフィルタリングの仕事を依頼される前に、init メソッドは正常に完了してなければいけません。
172         *
173         * init メソッドが以下のような状況になると、Web コンテナは Filter をサービス状態にできません。
174         * 1. ServletException をスローした
175         * 2. Web コネクタで定義した時間内に戻らない
176         *
177         * @param       filterConfig    FilterConfigオブジェクト
178         */
179        @Override       // Filter
180        public void init(final FilterConfig filterConfig) {
181                ipaddrArray = StringUtil.csv2Array( filterConfig.getInitParameter("ipAddress") );
182                isDebug = Boolean.parseBoolean( filterConfig.getInitParameter("debug") );       // 6.1.0.0 (2014/12/26) refactoring
183        }
184
185        /**
186         * Filter インターフェースの destroy メソッド (何もしません)。
187         *
188         * サービス状態を終えた事を Filter に伝えるために Web コンテナが呼び出します。
189         * Filter の doFilter メソッドが終了したか、タイムアウトに達した全てのスレッドにおいて、
190         * このメソッドを一度だけ呼び出されます。 Web コンテナがこのメソッドを呼び出した後は、
191         * Filter のこのインスタンスにおいて二度と doFilter メソッドを呼び出す事はありません。
192         *
193         * このメソッドは、フィルタに保持されている(例えば、メモリ、ファイルハンドル、スレッド)
194         * 様々なリソースを開放する機会を与え、 あらゆる永続性の状態が、メモリ上における Filter
195         * の現在の状態と同期しているように注意してください。
196         */
197        @Override       // Filter
198        public void destroy() {
199                // noop
200        }
201}