連載
» 2003年10月28日 10時00分 公開

JavaTips 〜JSP/サーブレット編:アクセスログ記録のロジックをフィルタで一元化する

[山田祥寛,@IT]

 アクセスログをとるのは、不正アクセスを防止するという――いわゆるセキュリティ面からの意味合いからだけではありません。ユーザーからあまり利用されていない(あるいは、まったく顧みられていない)ページをログから分析することで、サイトの改善や分析にも役立てることが可能です。

 ただ、このアクセスログをとるためのしくみを、サーブレットにより個々のページに記述していくとなんとも大変な作業量になってしまいますし、ページが増える都度、いちいち同じようなロジックを記述しなければならないのは間違いのもとでもあります。もちろん、アクセスログ記録部分をクラス化してしまって、各ページから呼び出すようにしても構いませんが、それでも各ページに「呼び出し」のロジックを記述しなければならないのには変わりないのです。あなた1人がサイト作成者であるうちは、それでもなんら問題ないかもしれませんが、複数の人間がサイト作成に絡むようになった場合、こうしたルールは極力少なくしておくに越したことはありません。

 そこで登場するのが「フィルタ」のしくみです。フィルタを用いることで、このようなサイト共通(あるいはサイトの限定された一範囲)のロジックを一元化することが可能になります。

操作手順

(1)フィルタクラスを作成する

 フィルタクラスを定義するには、javax.servlet.Filterインターフェイスを実装(implements)する必要があります。フィルタの実際の動作は、doFilterメソッドをオーバーライド(上書き)します。

FilterLog.java
package filter;
import java.io.*;
import java.sql.*;
import java.util.*;
import java.text.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class FilterLog implements Filter {
  private FilterConfig objFc=null;
  public void init(FilterConfig objFc){
    this.objFc=objFc;
  }
  public void destroy(){}
  public void doFilter(ServletRequest request, 
     ServletResponse response, FilterChain chain) 
     throws ServletException, IOException {
    try{
      PreparedStatement objSql1,objSql2;
      Class.forName("org.gjt.mm.mysql.Driver");
      Connection db=DriverManager.getConnection("
          jdbc:mysql://localhost/atit?user=root&password=
          root&useUnicode=true&characterEncoding=Shift_JIS");
      objSql1=db.prepareStatement("SELECT * FROM logaccess WHERE url=?");
      objSql1.setString(1,((HttpServletRequest)request).getServletPath());
      ResultSet rs=objSql1.executeQuery();
      Calendar objCal=Calendar.getInstance();
      SimpleDateFormat objFmt=new SimpleDateFormat("yyyyMMdd");
      if(rs.next()){
        objSql2=db.prepareStatement("UPDATE logaccess SET cnt=cnt+1 WHERE url=? AND dat=?");
      } else {
        objSql2=db.prepareStatement("INSERT INTO logaccess(url,dat,cnt) VALUES(?,?,1)");
      }
      objSql2.setString(1,((HttpServletRequest)request).getServletPath());
      objSql2.setString(2,objFmt.format(objCal.getTime()));
      objSql2.executeUpdate();
      objSql1.close();
      objSql2.close();
      db.close();
    }catch(Exception e){
      e.printStackTrace();
    }
    chain.doFilter(request,response);
  }
}

 doFilterメソッドでは、atitデータベース内のlogaccessテーブルに対してアクセスログの書き込みを行っています。最初にアクセスしたURL(urlフィールド)とアクセス日付(datフィールド)とをキーにしてlogaccessテーブルを検索し、同一キーが存在した場合にはそのキーでアクセス数をインクリメントし、存在しない場合には新規キーでレコード(カウント数の初期値は1)を登録します。

(2)フィルタをアプリケーションに登録する

 作成したフィルタクラスはそのままでは動作しません。「アプリケーションルート/WEB-INF/web.xml」からフィルタクラスを有効化する必要があります。web.xml上に以下のような内容を追加してください。

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <filter>
    <filter-name>LogAccess</filter-name>
    <filter-class>filter.FilterLog</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>LogAccess</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

 すでになにかしらの記述のあるweb.xmlに追記する場合には、任意の個所に追加することはできませんので、注意してください。

 <filter>要素はフィルタクラスの完全修飾名に対してweb.xml内の論理名を定義します。ここで定義された論理名は、<filter-mapping>要素で使用されるものです。<filter-mapping>要素は、どのURLが呼び出されたときにフィルタクラスをコールするかを決定します。ここでは、サイト配下の全ページ(/*)に対してフィルタを適用することにします。

 以上の用意ができたら、アプリケーション配下の任意のページにアクセスしてみてください。データベース上のテーブルにログが記録されていれば成功です。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。