Linuxでサーバ容量・フォルダサイズ・ファイルサイズを調べるコマンド

Linux

Linuxサーバ上で、サーバ全体のディスク使用率や、フォルダサイズ・ファイルサイズを調査する時に便利なコマンドについて紹介していきます。


サーバ容量を確認する(dfコマンド)


サーバ全体の容量について確認する際に使用するのはdfコマンドになります。


[@users]$ df
ファイルシス   1K-ブロック     使用    使用可 使用% マウント位置
devtmpfs           3981472        0   3981472    0% /dev
tmpfs              3999296      260   3999036    1% /dev/shm
/dev/sda1           613184     6908    606276    2% /boot/efi

オプションには、以下があります。


-a

すべてのディスクを表示。

隠しファイルシステムなど、全てのディスクを表示します。

-H

単位を付けて結果を表示。

容量の表示は数字が大きくなってしまうので、k(キロ)、M(メガ)、G(ギガ)の単位を付けて表示するのが一般的です。


フォルダサイズを確認する(duコマンド)


フォルダのサイズを確認する時はduコマンドを使用します。


[@users]$ du
28      ./numberAnalyze/module
1088    ./numberAnalyze/logs
1124    ./numberAnalyze
16      ./winningAnalyze/module
916     ./winningAnalyze/logs
940     ./winningAnalyze
16      ./winningExpected/module
26044   ./winningExpected/logs
26068   ./winningExpected
12      ./winningCollection/module/__pycache__
24      ./winningCollection/module
12      ./winningCollection/logs
44      ./winningCollection
28184   .

duコマンドではフォルダサイズだけではなく、ファイルサイズも確認することができます。
以下のコマンドでは、コマンドを発行しているフォルダ直下ファイルのファイルサイズも表示します。


[users]$ du ./*
4       ./loto.sh                       ★
28      ./numberAnalyze/module
1088    ./numberAnalyze/logs
1124    ./numberAnalyze
16      ./winningAnalyze/module
916     ./winningAnalyze/logs
940     ./winningAnalyze
12      ./winningCollection/module/__pycache__
24      ./winningCollection/module
12      ./winningCollection/logs
44      ./winningCollection
16      ./winningExpected/module
26044   ./winningExpected/logs
26068   ./winningExpected

★=直下のファイル


以下のコマンドでは、もう1階層下フォルダのファイルサイズを表示します。


[users]$ du ./*/*
1088    ./numberAnalyze/logs
28      ./numberAnalyze/module
4       ./numberAnalyze/numberAnalyze.sh         ★
916     ./winningAnalyze/logs
16      ./winningAnalyze/module
4       ./winningAnalyze/winningAnalyze.sh       ★
12      ./winningCollection/logs
12      ./winningCollection/module/__pycache__
24      ./winningCollection/module
4       ./winningCollection/winningCollection.sh ★
26044   ./winningExpected/logs
16      ./winningExpected/module
4       ./winningExpected/winningExpected.sh     ★

★=1階層下のファイル


指定した階層のフォルダサイズを確認したい場合は「–max-depth」オプションを使用します。
以下のコマンドでは、コマンドを実行したフォルダ直下について、フォルダ毎のサイズを表示しています。


[users]$ du --max-depth=1
1124    ./numberAnalyze
940     ./winningAnalyze
26068   ./winningExpected
44      ./winningCollection
28184   .

サイズを調べるフォルダを深くしたいきたい場合は、「–max-depth」オプションの数字を大きくしていけばよいです。

数字が大きいほど、下の階層まで検索対象となります。

[users]$ du --max-depth=2
28      ./numberAnalyze/module
1088    ./numberAnalyze/logs
1124    ./numberAnalyze
16      ./winningAnalyze/module
916     ./winningAnalyze/logs
940     ./winningAnalyze
16      ./winningExpected/module
26044   ./winningExpected/logs
26068   ./winningExpected
24      ./winningCollection/module
12      ./winningCollection/logs
44      ./winningCollection
28184   .

上記以外のよく使うオプションも紹介しておきます。


-h

読みやすい単位で表示する。

-H

読みやすい単位で表示する。

ただし、1024単位ではなく1000単位の値を使用。

-c

合計のサイズも表示

-k

キロ単位で表示

-m

メガ単位で表示



Pythonのloggingを使って、用途別に複数のログファイルにログ出力する

phtyonロゴ

Pythonでは、ログ出力については標準機能として「logging」が準備されています。
実際のシステムでログ出力をおこなう場合、ログファイルが一種類であることは少なく、用途によってログファイルを分ける事が通常であると思います。


今回は、loggingを使って用途別に複数のログファイルにログ出力する方法を紹介します。


環境情報


  • Python 3.4.1

ログ管理クラス


ログファイルは以下の3種類を出力することを想定して、ログ出力クラスを作成していきます。

  • アプリケーションログ
  • SQLログ
  • エラーログ

この3つのログファイルを出力するログ管理クラスを作成します。


#!/usr/local/bin/python3.4
# coding: utf-8
import logging
from logging import getLogger, FileHandler

#
# ログクラス
#
class Log:

    # -------------------------------------
    # コンストラクタ
    # -------------------------------------
    def __init__(self):

        # 1.アプリケーションログ
        l = logging.getLogger('APL')
        formatter = logging.Formatter(
            '%(asctime)s <%(levelname)s> : %(message)s')
        fileHandler = 
            logging.FileHandler('./logs/APL.log', mode='a')
        fileHandler.setFormatter(formatter)
        l.setLevel(logging.INFO)
        l.addHandler(fileHandler)

        # 2.SQLログ
        l = logging.getLogger('SQL')
        formatter = logging.Formatter(
            '%(asctime)s <%(levelname)s> : %(message)s')
        fileHandler = 
            logging.FileHandler('./logs/SQL.log', mode='a')
        fileHandler.setFormatter(formatter)
        l.setLevel(logging.INFO)
        l.addHandler(fileHandler)

        # 3.エラーログ
        l = logging.getLogger('ERROR')
        formatter = logging.Formatter(
            '%(asctime)s <%(levelname)s> : %(message)s')
        fileHandler = 
            logging.FileHandler('./logs/ERROR.log', mode='a')
        fileHandler.setFormatter(formatter)
        l.setLevel(logging.INFO)
        l.addHandler(fileHandler)

        Log.__instance = self

    # -------------------------------------
    # 4.アプリケーションログにINFOでログ出力
    # -------------------------------------
    def aplInfo(self, msg):
        log = logging.getLogger('APL')
        log.info(msg)

    # -------------------------------------
    # 5.SQLログにINFOでログ出力
    # -------------------------------------
    def sqlInfo(self, msg):
        log = logging.getLogger('SQL')
        log.info(msg)

    # -------------------------------------
    # 6.エラーログにERRORでログ出力
    # -------------------------------------
    def errorError(self, msg):
        log = logging.getLogger('ERROR')
        log.error(msg)

順番に説明していきます。
まずは「1.アプリケーションログ」です。


‘APL’という名前のロガーを作成し、ログファイルの出力先パスや、ログファイルの出力フォーマット、および、ログ出力のモードを指定して、ログファイル出力のためのファイルハンドラを作成します。


l = logging.getLogger('APL')
formatter = logging.Formatter('%(asctime)s <%(levelname)s> : %(message)s')
fileHandler = logging.FileHandler('./logs/APL.log', mode='a')
fileHandler.setFormatter(formatter)

次に、ロガーのログレベルを設定し作成したファイルハンドラもロガーに設定すれば完了です。


l.setLevel(logging.INFO)
l.addHandler(fileHandler)

「2.SQLログ」「3.エラーログ」も、設定するロガーが「SQL」「ERROR」といったように異なるだけで、「1.アプリケーションログ」と同様の作りです


4~6はログ出力のメソッドになります。


「4.アプリケーションログにINFOでログ出力」のメソッドである「aplInfo」はAPLロガーを使っています。
つまり、APL.logにログレベルINFOでログ出力をおこないます。


「5.SQLログにINFOでログ出力」のメソッドである「sqlInfo」も「aplInfo」と同様です。
異なる部分はSQLロガーを使っているということのみであり、SQL.logにログレベルINFOでログ出力をおこないます。


「6.エラーログにERRORでログ出力」は、ERRORロガーを使い、ERROR.logにログレベルERRORでログ出力をおこないます。


今回のサンプルでは3つのログ出力メソッドしか用意していませんが、必要であれば”アプリケーションログにERRORでログ出力””エラーログにINFOでログ出力”といったログ出力も可能な事がわかると思います。


ログを出力する


ログクラスを呼び出せば、ログクラスのコンストラクタでログ出力に必要なインスタンスを使ってくれるので、ログ出力をおこなう方の実装は極めてシンプルです。
以下のログ出力処理を、ログ出力したい箇所に実装すればログ出力がおこなわれます。


# インスタンス作成
log = Log()

# アプリケーションログをINFOで出力
log.aplInfo("アプリケーションログ")

# SQLログをINFOで出力
log.aplInfo(SQLログ")

# エラーログをERRORで出力
log.aplInfo(エラーログ")


ロリポップでPythonを使ってスクレイピング。モジュールを指定フォルダにインストールして環境構築。

phtyonロゴ

筆者は、レンタルサーバはロリポップを使っているのですが、Pythonでスクレイピングをするための環境構築で、ちょっと手こずりました。
原因がわかってしまえば単純なことだったので、記事として残しておきます。


環境情報


  • ロリポップレンタルサーバ
  • Python 3.4.1

モジュールのインストールコマンド


ロリポップでPythonのモジュールをインストールする方法は以下になります。
TeraTermにSSHで接続し、以下のコマンドを実行します。

※SSHはデフォルトで使用できないので、ロリポップの管理画面で使用できるように設定変更が必要です。


/usr/local/bin/python -m pip install ※モジュール名

Pythonでスクレイピングをおこなうためには「beautifulsoup3」というモジュールが必要です。
しかし、上記のコマンドで「beautifulsoup3」をインストールしようとするとエラーが発生してしまい、インストールすることができません。


$ /usr/local/bin/python -m pip install beautifulsoup3
Downloading/unpacking beautifulsoup3
  Real name of requirement beautifulsoup3 is beautifulsoup4
  Could not find any downloads that satisfy the requirement beautifulsoup3
Cleaning up...
No distributions at all found for beautifulsoup3
Storing debug log for failure in /home/users/0/oops.jp-sakusaku/.pip/pip.log
[oops.jp-sakusaku@users443 winningCollection]$ /usr/local/bin/python -m pip install beautifulsoup
Downloading/unpacking beautifulsoup
  Downloading BeautifulSoup-3.2.2.tar.gz
  Running setup.py (path:/tmp/pip_build_oops.jp-sakusaku/beautifulsoup/setup.py) egg_info for package beautifulsoup
    Traceback (most recent call last):
      File "<string>", line 17, in <module>
      File "/tmp/pip_build_oops.jp-sakusaku/beautifulsoup/setup.py", line 3
        "You're trying to run a very old release of Beautiful Soup under Python 3. This will not work."<>"Please use Beautiful Soup 4, available through the pip package 'beautifulsoup4'."
                                                                                                        ^
    SyntaxError: invalid syntax
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 17, in <module>

  File "/tmp/pip_build_oops.jp-sakusaku/beautifulsoup/setup.py", line 3

    "You're trying to run a very old release of Beautiful Soup under Python 3. This will not work."<>"Please use Beautiful Soup 4, available through the pip package 'beautifulsoup4'."

                                                                                                    ^

SyntaxError: invalid syntax

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in /tmp/pip_build_oops.jp-sakusaku/beautifulsoup
Storing debug log for failure in /home/users/0/oops.jp-sakusaku/.pip/pip.log

インストールとパス設定


インストール、および、Python実行時のパス設定にコツがあります。
前提として、以下のフォルダ構成を前提とします。


------
    |
    |---python
    |    |
    |    |---vendor         ・・・  Pythonの個別モジュールインストール先
    |
    |---test
      |
      |---scriping
           |
           |---scriping.sh  ・・・  作成したプログラムを実行するシェル
           |
           |---module       ・・・  作成プログラムの格納フォルダ

まず、インストールしたいモジュールである「beautifulsoup3」は個別モジュールインストール先である「python/vendor」にインストールする必要があります。
詳しい理由はわからないですが、「mysql-connector-python-rf」はインストール先を選択しなくても正常にインストールできたのですが、どうやら「beautifulsoup3」は自身で作成するフォルダにインストールする必要があるようです。


コマンドは以下になります。
「python」フォルダで以下のコマンドを実行します。


/usr/local/bin/python -m pip install BeautifulSoup --target ./vendor

このコマンドを実行することで、vendorフォルダ配下に「beautifulsoup3」のモジュールがインストールされます。


次にパス設定です。
実行するPythonのプログラムに「python/vendor」へのパスを設定する必要があります。
実行するPythonのプログラムは、「test/scriping/module」に格納されています。


#!/usr/local/bin/python3.4
# coding: utf-8
import os
import sys
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '../../../python/vendor'))
from bs4 import BeautifulSoup 

これで、Pythonでスクレイピングのプログラムを実行する環境構築は完了です。



PythonでMySQLに接続するクラスを共通クラスとして部品化

phtyonロゴ

何かしらのシステムを構築しようとした場合、DBにデータを登録する、更新する、取得する、といった機能は必須になります。

Pythonも例外ではないのですが、いろいろなサイトでDBアクセスの一般的な方法は紹介しているのですが、実システムに沿ったような部品化についてはきちんとまとまっているサイトをみつけることができませんでした。


なければ作ってしまえということで、今回は、Pythonを使ってDBアクセス、および、DBアクセスクラスの部品化について紹介します。


環境情報


  • Python 3.4.1
  • MySQL 5.6.23

MySQLのデータベースには、以下のテーブルが存在することを前提とします。


CREATE TABLE test (
 test_id INT NOT NULL AUTO_INCREMENT,
 value INT NOT NULL,
 register_name VARCHAR(40) NOT NULL,
 register_date DATETIME NOT NULL,
 del_flg CHAR(1) NOT NULL,
 PRIMARY KEY (test_id)
) ENGINE = InnoDB DEFAULT CHARACTER SET utf8;

メインクラスとDBアクセスクラス


今回のクラス構成としては、メインクラスとDBアクセスクラスの2つとなります。
DBアクセスクラスは、DBアクセスに必要な最低限の機能を保持する事します。
メインクラスは、DBアクセスが必要な際にDBアクセスクラスを使います。


メインクラス

#!/usr/local/bin/python3.4
# coding: utf-8
from dbAccessor import dbAccessor

#
# メインクラス
#
class Main:

    # -----------------------------------
    # メインクラス
    #
    # DBアクセスクラスを呼び出すメイン処理
    # -----------------------------------
    def excecuteMain():
        obj = dbAccessor(
            '※DB名', '※ホスト名', '※ユーザ名', '※パスワード');

        # SELECT実行
        rows = obj.excecuteQuery('select test_id, value from test;')
        for row in rows:
            print("%d %s" % (row[0], row[1]))

        # INSERT実行
        num = obj.excecuteInsert("INSERT INTO test (
            value, register_name, register_date, del_flg) 
            VALUES (100,'管理者', '2020-12-20 00:00:00','管理者', 
            '0')")

        # UPDATE実行
        num = obj.excecuteUpdate(
            "UPDATE test SET value = 100 where test_id = 10")

        # DELETE実行
        num = obj.excecuteDelete(
            "DELETE FROM test WHERE test_id = 10")

# メイン処理を実行
Main.excecuteMain()

DBアクセスクラス

#!/usr/local/bin/python3.4
# coding: utf-8
from urllib.parse import urlparse
import mysql.connector

#
# DBアクセス管理クラス
#
class dbAccessor:

    # -----------------------------------
    # コンストラクタ
    #
    # コネクションを取得し、クラス変数にカーソルを保持する。
    # -----------------------------------
    def __init__(self, dbName, hostName, id, password):
        print("start:__init__")

        # DB接続URLを作成してパース
        url = 'mysql://' + id + ':' + password + '@' + 
            hostName + '/' + dbName
        parse = urlparse(url)

        try:
            # DBに接続する
            self.conn = mysql.connector.connect(
                host = parse.hostname,
                port = parse.port,
                user = parse.username,
                password = parse.password,
                database = parse.path[1:],
            )

            # コネクションの設定
            self.conn.autocommit = False

            # カーソル情報をクラス変数に格納
            self.conn.is_connected()
            self.cur = self.conn.cursor()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:__init__")

    # -----------------------------------
    # クエリの実行
    #
    # クエリを実行し、取得結果を呼び出し元に通知する。
    # -----------------------------------
    def excecuteQuery(self, sql):
        print("start:excecuteQuery")

        try:
            self.cur.execute(sql)
            rows = self.cur.fetchall()
            return rows
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:excecuteQuery")

    # -----------------------------------
    # インサートの実行
    #
    # インサートを実行する。
    # -----------------------------------
    def excecuteInsert(self, sql):
        print("start:excecuteInsert")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteInsert")

    # -----------------------------------
    # アップデートの実行
    #
    # アップデートを実行する。
    # -----------------------------------
    def excecuteUpdate(self, sql):
        print("start:excecuteUpdate")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteUpdate")

    # -----------------------------------
    # デリートの実行
    #
    # デリートを実行する。
    # -----------------------------------
    def excecuteDelete(self, sql):
        print("start:excecuteDelete")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteDelete")

    # -----------------------------------
    # デストラクタ
    #
    # コネクションを解放する。
    # -----------------------------------
    def __del__(self):
        print("start:__del__")
        try:
            self.conn.close()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)
        print("end:__del__")

DBアクセスクラスの各メソッド


上記に紹介したサンプルプログラムについて説明していきます。


DBコネクションの取得と解放


DBコネクションの取得はコンストラクタで、解放はデストラクタで実施しています。


    def __init__(self, dbName, hostName, id, password):
        print("start:__init__")

        # DB接続URLを作成してパース
        url = 'mysql://' + id + ':' + password + '@' + 
            hostName + '/' + dbName
        parse = urlparse(url)

        try:
            # DBに接続する
            self.conn = mysql.connector.connect(
                host = parse.hostname,
                port = parse.port,
                user = parse.username,
                password = parse.password,
                database = parse.path[1:],
            )

            # コネクションの設定
            self.conn.autocommit = False

            # カーソル情報をクラス変数に格納
            self.conn.is_connected()
            self.cur = self.conn.cursor()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:__init__")

    def __del__(self):
        print("start:__del__")
        try:
            self.conn.close()
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)
        print("end:__del__")

コンストラクタで通知されたパラメータを使って、MySQLに接続するURLを作成しています。
実際に作成されるURLは以下になります。


url = urlparse('mysql://※ユーザ名:※パスワード@※ホスト名/※DB名')

また、オートコミットをオフ(False)に設定しています。
オートコミットをオフにすることにより、トランザクション管理が容易になります。
これも、RDB(リレーションナルデータベース)を使ったシステムでは必須の機能となります。


こういって取得したDBコネクションとカーソル定義を、DBアクセスクラスのクラス変数として保持しています。
クラス変数として保持することにより、オブジェクト内でコネクションの使いまわしが可能となっています。


最後にデストラクタです。
デストラクタでコネクションを解放することにより、オブジェクト解放時に同時にコネクションも解放されます。


SELECT


SELECTを実行するメソッドは、実行するSQLを呼び出し側(メインクラス)から文字列として通知される形としています。
SQL文を通知して、SELECTした結果をそのまま返却しています。
非常にシンプルです。


    def excecuteQuery(self, sql):
        print("start:excecuteQuery")

        try:
            self.cur.execute(sql)
            rows = self.cur.fetchall()
            return rows
        except (mysql.connector.errors.ProgrammingError) as e:
            print(e)

        print("end:excecuteQuery")

INSERT


INSERT文もSELECTと同様で、呼び出し側から通知されたSQLを発行しているのみです。


SELECTと違うのは返り値となり、「rowcount」を返却しているのがポイントです。
「rowcount」は、INSERTした数になります。


    def excecuteInsert(self, sql):
        print("start:excecuteInsert")

        try:
            self.cur.execute(sql)
            self.conn.commit()
            return self.cur.rowcount
        except (mysql.connector.errors.ProgrammingError) as e:
            self.conn.rollback()
            print(e)

        print("end:excecuteInsert")

UPDATEとDELETEのメソッドも、UPDATEと同じメソッドになります。
UPDATEのメソッドは更新した数、DELETEのメソッドは削除した数、を返却します。




Apache2.4で、ポート毎にドキュメントルールをわける方法

apache

自分のパソコンを開発環境として開発をおこなう際、実態としては複数のPJの開発環境を共存させる必要があります。
その際、ポート毎にドキュメントルートをわけて、ドキュメントルートそれぞれに対して開発環境を準備する方法がやり易いと思います。


今回は、Apache2.4を使って、ポート毎にドキュメントルールをわけるためのApacheの設定方法について紹介します。


環境情報


Windowsマシンに、Apacheを既にインストールしてる状態を想定して説明していきます。


  • OS:Windows10
  • HTTPサーバ:Apache2.4

Apacheのインストールと環境構築方法は過去に記事を使っているので、そちらを参照してください。



筆者のパソコンには既にApache2.4がインストールされていて稼働しているのですが、ちょっと訳あって「80、81」ポートを使用することができないので、今回は「82、83」ポートそれぞれにドキュメントルールを設定して動作させるようにしてみます。


VirtualHostの設定


まずはVirtualHostの設定をおこないます。
82ポートと83ポートを使用できるようにし、それぞれのポート対してドキュメントルートを設定します。



# 1.Listenポートを設定
Listen 82
Listen 83

# 2.82ポートに対するドキュメントルートを設定
<VirtualHost *:82>
    DocumentRoot c:/PORT82/htdocs
    ServerName sakusaku-techs.com:82
    ErrorLog logs/PORT82/error_log
    CustomLog logs/PORT82/access_log common
</VirtualHost>

# 3.83ポートに対するドキュメントルートを設定
<VirtualHost *:83>
    DocumentRoot c:/PORT83/htdocs
    ServerName sakusaku-techs.com:83
    ErrorLog logs/PORT83/error_log
    CustomLog logs/PORT83/access_log common
</VirtualHost>

「1.Listenポートを設定」で、解放するポートを設定します。
今回は、82と83の2ポートを解放します。


” #2.82ポートに対するドキュメントルートを設定 “で、各ポートでアクセスするドキュメントルートを設定します。
ドキュメントルートの設定もおこなっていますが、ログの設定(アクセスログとエラーログ)もおこなっています。
これだけで、ポートとドキュメントの紐付けは完了です


” #3.83ポートに対するドキュメントルートを設定 “は、82ポートと同様の設定です。


ここまでの設定をおこなえばドキュメントルートの設定は完了ですが、このままではアクセスはできません。
次に説明する、アクセス制限の設定をおこなう必要があります。


アクセス制限の設定


VirtualHostの設定をおこなっただけではアクセス権限が設定されておらず、対象URLにブラウザでアクセスしても権限エラーで画面は表示することはできません。


VirtualHostの設定に、以下のようにアクセス制御の設定をおこないます。


<VirtualHost *:82>
    DocumentRoot c:/PORT82/htdocs
    ServerName sakusaku-techs.com:82
    ErrorLog logs/PORT82/error_log
    CustomLog logs/PORT82/access_log common

    <Directory c:/PORT82/htdocs>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost *:83>
    DocumentRoot c:/PORT83/htdocs
    ServerName sakusaku-techs.com:83
    ErrorLog logs/PORT83/error_log
    CustomLog logs/PORT83/access_log common

    <Directory c:/PORT83/htdocs>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

各バーチャルホストの設定に、以下の権限設定を入れています。


<Directory xxx>
    AllowOverride All
    Require all granted
</Directory>

Apache2.4では上記の設定が正解となります。
「Require all granted」がポイントです。
この設定を入れなかった場合、Apacheはデフォルトで「Require all denied」の設定となります。


そのため、「Require all granted」の権限設定をおこない、アクセスを有効にする必要があります。


ここまでの設定をおこなえば、82ポートと83ポートそれぞれでドキュメントルートをわけることができています。

確認として、82ポートに設定したドキュメントルートである「c:/PORT82/htdocs」と、83ポートに設定したドキュメントルートである「c:/PORT83/htdocs」下にindex.htmlを格納してみます。

その状態で、ブラウザを使って「http://localhost:82/index.html」「http://localhost:83/index.html」にアクセスすると、それぞれのドキュメントルートに配置したhtmlが表示されます。



MySQLのDBコネクション関連でトラブルが発生した際にやること

Linux

稼働中のシステムにおいて発生するトラブルのひとつに、MySQLのコネクションに関するトラブルがあります。
“アプリケーションからDBコネクションを取得できない!””想定以上にアプリケーションがコネクションを取得している”といったとトラブルになります。


今回は、MySQLのコネクションに関するトラブルが発生した場合に調査するポイントと、調査のために実行するコマンドについて紹介していきます。


現在のコネクション数を確認


MySQLに、現在接続しているコネクション数を確認するには、show status コマンドを実行します。
MySQLにログインしてコマンドを実行します。


mysql> show status like 'Threads_connected';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 29    |
+-------------------+-------+
1 row in set (0.00 sec)

上記コマンドで現在接続されているコネクション数を確認して、MySQLで許可している最大コネクション数を超えていないかの確認が可能です。


最大コネクション数を確認


MySQLが許可している最大コネクション数を確認します。
このコネクション数が接続される可能性がある数を超えてしまうので、MySQLに接続できない状態になってしまいます。


mysql> SHOW GLOBAL VARIABLES like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 512   |
+-----------------+-------+
1 row in set (0.01 sec)

接続元のIPを確認


現在のプロセスを一覧で表示して、発生しているコネクションが、どのサーバから接続されているかの確認も可能です。


mysql> SHOW processlist;
+------------+------------+------------------------------+
| Id | User | Host | db | Command | Time | State | Info  |
+------------+------------+---------------------+--------+
  ・
  ・
  ・

Hostカラムが接続元サーバのIPとPORTです。


MySQLを起動してからの最大接続数を確認


MySQLを起動してから接続された最大コネクション数も確認することができます。

トラブルが発生しても、調査を開始したタイミングではコネクション数は問題ない場合があります。

しかし、トラブル発生直後はコネクション数が規定値をオーバーしている可能性があるので、このコマンドを実行して過去の最大コネクション数を確認するとよいです。


mysql> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 513   |
+----------------------+-------+
1 row in set (0.00 sec)

DBサーバのKeepAliveを確認


TomcatなどのWEBアプリケーションがコネクションプールを取得している状態で、Tomcatが起動しているサーバが落ちてしまうと、MySQLのコネクションは残ってしまいます。
このコネクションは不要なコネクションとなるのですが、一定時間が過ぎると自動的にコネクションは解放されます。


この「一定時間」は、MySQLがインストールされているサーバのKeepAliveパケット送信の設定に依存します。

システム設定値となり、以下のコマンドで格納可能です。


以下の設定例では、60秒で不要なコネクションは解放されます。


$>sysctl -a
 ・
 ・
 ・
net.ipv4.tcp_keepalive_intvl=5
net.ipv4.tcp_keepalive_probes=6
net.ipv4.tcp_keepalive_time=30
 ・
 ・
 ・

30秒間隔でKeepAliveパケットを6回送信する。
その6回は5秒間隔、という設定になります。


各項目の説明を一応記載しておきます。


net.ipv4.tcp_keepalive_intvl

KeepAliveパケットを送信する間隔

net.ipv4.tcp_keepalive_probes

KeepAliveパケットを送信する回数

net.ipv4.tcp_keepalive_time

KeepAliveパケットを送信するまでの時間


JavaのSupplierを使って汎用的なリトライ処理を実装する

Java

アプリケーションの機能を実装していく中で、様々なリトライ処理が必要になってきます。
DBアクセスに失敗した場合のリトライ、HTTP通信(APIなど)に失敗した場合のリトライ、など様々かと思います。


Javaでこういったリトライ処理を実装しようとすると、普通にやるとfor文やwhile文を使ってしまいがちです。
それでもいいですが、Javaのユーティリティクラス群であるSupplierを使えば、もうちょっと汎用的に実装することができます。


今回は、Javaのユーティリティクラスである「java.util.function.Supplier」を使って、汎用的なリトライ処理を実装する方法を紹介していきます。


環境情報


  • OS:Windows10
  • Java:Java1.8

ユーティリティクラス群で使用されている関数型インターフェースは、Java8から導入された機能となります。
Java1.7以前は使用できないので注意してください。


java.util.functionは関数型インターフェース


java.util.function配下の機能は全て、関数型インターフェースで実装されています。
イメージとしては、実行するメソッドをパラメータとして渡す形です。


java.util.functionのAPI仕様をみると複雑なインターフェースが沢山あるようにみえるのですが、大きくわけて4つに分類されます。
今回のリトライ処理では「Supplier」を使うのですが、他3つのインターフェースは以下になります。


種類

実装するメソッド

概要

Function<T,R>

R apply(T t)

実装するメソッドは、引数としてTを受け取り、結果としてRを返すものになる

Consumer<T>

void accept(T t)

実装するメソッドは、引数としてTを受け取り、結果を返さず終了するものになる

Predicate<T>

boolean test(T t)

実装するメソッドは、引数としてTを受け取り、boolean値を結果として返すものになる

Supplier<T>

T get()

実装するメソッドは、何も引数として受け取らず、結果としてTを返すものになる


関数型インターフェースの簡単なサンプルを、以下に紹介します。
先ほど説明した通り、関数をパラメータとして渡して渡された側で関数を実行しています。


return new RetryUtil()
    .retryFunc(() -> {
        ※実行する機能
    );
public class RetryUtil {
    private <T> T retryFunc(Supplier<T> func) {
        return func.get();
    }
}

リトライ処理


リトライ処理は、java.util.function配下の「Supplier」を使用します。
「Supplier」を使用し、実施する処理自体は呼び出し元で定義し、リトライの仕組み自体は共通クラス(ユーティリティクラス)に実装しています。


まずはリトライ処理を実施するメイン処理です。

import java.util.function.Supplier;

/**
    リトライ処理サンプル
*/
public class RetrySample {

    /**
        リトライ処理メイン
    */
    public static void main(String[] args) 
        throws InterruptedException {
        System.out.print("start: RetrySample\r\n");

        // 処理結果を失敗で初期化
        boolean isDone = false;

        // リトライ処理
        try {
            isDone = retryExec();
        } catch (Exception e) {
            System.out.print("RetrySample: main is failer\r\n");
        } finally {
            System.out.print(
                "RetrySample: result is " + isDone + "\r\n");
        }

        System.out.print("end: RetrySample\r\n");
    }

    /**
        リトライ処理の実施
    */
    private static boolean retryExec () 
        throws InterruptedException {

        // 1.リトライ回数とスリープ時間を設定
        int retryNum = 3;
        long sleepMilSec = 1000;

        // 2.ユーティリティを使って〇〇〇処理を実施する
        return new RetryUtil(retryNum, sleepMilSec)
            .retryFunc(() -> {
                try {
                    System.out.print("retryExec\r\n");
                    boolean isSucess = 〇〇〇処理を実施
                    if (isSucess) {
                        return true;
                    } else {
                        throw new RetryException();
                    }
                } catch (Exception e) {
                    throw new RetryException();
                }
            }, "RetrySample");
    }
}

「1.リトライ回数とスリープ時間を設定」で、リトライユーティリティのオブジェクトに設定する初期値を定義しています。
今回はリトライ処理ということで、ありがちなリトライ回数とスリープ時間はユーティリティクラスのコンストラクタで設定できようにしています。
その他、初期値として設定したい値があるのであれば、同様にコンストラクタで定義するのがよいかと思います。


「2.ユーティリティを使って〇〇〇処理を実施する」の機能が、実際に関数型インフェースで定義しているリトライ処理する中身です。
サンプルなので「〇〇〇処理を実施」としていますが、実際にはここにリトライ制御したい機能を実装していくのがよいでしょう。
リトライする契機としては、独自例外クラスである「RetryException」が発生した場合になります。
失敗したら(isDoneがfalseだったら)「RetryException」をスローすることで、リトライ処理を実行するようにユーティリティクラスを準備しています。


次にリトライユーティリティクラスです。

import java.text.MessageFormat;
import java.util.function.Function;
import java.util.function.Supplier;

/**
    リトライユーティリティ
*/
public class RetryUtil {

    // リトライ回数
    private final int retryNum;

    // リトライ時のスリープ時間
    private final long retryWait;

    /**
     * コンストラクタ
     */
    public RetryUtil(int retryNum, long retryWait) {

        // 1.リトライ回数とスリープ時間を設定
        this.retryNum = retryNum;
        this.retryWait = retryWait;
    }

    /**
     * リトライ処理
     */
    public <T> T retryFunc(Supplier<T> func, String funcName)
            throws RetryException, InterruptedException {
        return retryFunc(func, funcName, 1, null);
    }

    /**
     * リトライユーティリティ
     */
    private <T> T retryFunc(Supplier<T> func, 
        String funcName, int count, Exception befEx)
        throws RetryException, InterruptedException {

        // 2.Supplierで提供されたfuncを実行
        try {
            return func.get();

        // 3.例外が発生した場合はリトライ回数に
        //     達するまで自身を再度呼び出す
        } catch (RetryException e) {
            System.out.print("RetryException\r\n");
            if (count > this.retryNum) {
                System.out.print("retry end\r\n");
                throw new RetryException(e);
            }
            count++;
            Thread.sleep(this.retryWait);
            System.out.print("next retry " + 
                Integer.toString(count) + "回目の処理開始\r\n");
            return retryFunc(func, funcName, count, null);
        }
    }
}

「1.リトライ回数とスリープ時間を設定」で、リトライ回数とスリープ時間をクラス変数として格納しています。
実際のリトライ処理時に、この値を参照しています。


「2.Supplierで提供されたfuncを実行」で、実際に関数型インターフェースで渡されたメソッドを実行しています。
実行方法は、パラメータ名.get() で実行されます。


「3.例外が発生した場合はリトライ回数に達するまで自身を再度呼び出す」がリトライ処理のメインとなる部分ですね。
個別例外である「RetryException」が発生するとリトライ処理を実施します。
リトライ回数が上限に達すると上位に例外をスローしてリトライ処理を中断します。
上限に達していない場合は再度自身を呼び出して同じ処理を実行しています。


最後に個別の例外クラスです。
リトライ処理を制御しやすくするために、個別例外を準備しています。


/**
    リトライユーティリティ固有例外
*/
public class RetryException extends RuntimeException {

    /**
     * コンストラクタ
     */
    public RetryException() {
        super();
    }

    /**
     * コンストラクタ(例外指定)
     * @param cause 例外
     */
    public RetryException(Throwable cause) {
        super(cause);
    }
}


Windows OSをリモートから再起動するバッチのコマンド

JavaScript

特定のWindows OSのパソコンから、他のWindowsOSを搭載しているパソコンに再起動したい場合があります。
その場合、リモートデスクトップで接続して手動で再起動をおこなう方法をとる場合が多いかと思いますが、バッチファイルひとつで実行可能にすると便利です。


今回は、リモートでWindows OSを再起動するバッチについて紹介します。


環境情報


  • クライアント(バッチを実行する方):Windows10、WindowsServer2012
  • サーバ(再起動する方):Windows10、WindowsServer2012

クライアントとサーバの両方がWindows10、クライアントとサーバの両方がWindowsServer2012、の組み合わせでは確認できています。

Windows10とWindowsServer2012や、その他WindowsOSの組み合わせは試していませんが、たぶん以下に説明するバッチで正常に動作すると思います。


shutdown.exe と WMIC


リモートからバッチファイルを使って再起動することができるコマンドは2つあります。
shutdown.exe と WMIC です。


  • shutdown.exe

Windowsに標準格納されている再起動用の実行ファイル。
リモートで再起動する場合は、再起動するOSの資格情報(ID・パスワード)は指定できない。


  • WMIC

WMI(Windows Management Instrumentation)は、Windows OSにおけるシステム管理のための共通アーキテクチャ。
再起動だけではなく、Windows OSを操作する様々なコマンドを使用することができる。
リモートで再起動する場合は、再起動するOSの資格情報(ID・パスワード)が指定できる。


リモートで再起動する場合の2つの大きな違いは、資格情報を指定できるか?できないか?です。

「shutdown.exe」の場合は資格情報を指定することができないので、バッチファイルを実行するユーザと再起動するWindows OSのユーザは一致している必要があります。
しかし、WMICをつかった方法は、バッチファイル内で資格情報を指定できるので、同じユーザでのログインは不要です。


上記の理由から、WMICを使って再起動をおこなう方が使い勝手よいかと思います。


WMICでの再起動コマンド


リモートのWindows OSを再起動するための、WMICを使ったバッチファイルは以下になります。


set SERVER_REBOOT=192.168.1.100
set USER=administrator
set PASS=password01

WMIC /NODE:%SERVER_REBOOT% /USER:%USER% /PASSWORD:%PASS% OS WHERE Primary=1 CALL Win32Shutdown 6

上記のバッチファイルを実行することで、「SERVER_REBOOT」に定義したIPのWindows OSを再起動することができます。
「USER」「PASS」には、再起動するWindows OSのIDとパスワードを設定します。


WMICのコマンドは多機能なので、詳細はMicrosoftのマニュアルを参照して頂きたいのですが、ポイントとしては「Win32Shutdown 6」の部分です。
「6」は強制再起動なのですが、このパラメータを変更することで、強制再起動以外の操作も可能です。


以下に各パラメータの値と概要、および、説明を記述します。

説明分はMicroSoftのサイト(英文のみ)の文章をGoogle翻訳で訳して記載しているだけですので、日本語が少しおかしい部分があります。


0(0x0)

ログオフ

ユーザーをコンピューターからログオフします。ログオフする

exit関数を呼び出したプロセスのセキュリティコンテキストに関連付けられているすべてのプロセスが停止し、現在のユーザーがシステムからログオフされ、ログオンダイアログボックスが表示されます。

4(0x4)

強制ログオフ

ユーザーをコンピューターからすぐにログオフし、ログオンセッションが終了することをアプリケーションに通知しません。

これにより、データが失われる可能性があります。

1(0x1)

シャッタダウン

安全に電源を切れるところまでコンピュータをシャットダウンします。 (すべてのファイルバッファーがディスクにフラッシュされ、実行中のすべてのプロセスが停止します。)ユーザーにメッセージが表示されます。コンピューターの電源をオフにしても安全です。

シャットダウン中、システムは実行中の各アプリケーションにメッセージを送信します。アプリケーションは、メッセージの処理中にクリーンアップを実行し、Trueを返して、終了できることを示します。

5(0x5)

強制シャッタダウン

安全に電源を切れるところまでコンピュータをシャットダウンします。 (すべてのファイルバッファーがディスクにフラッシュされ、実行中のすべてのプロセスが停止します。)ユーザーにメッセージが表示されます。コンピューターの電源をオフにしても安全です。

強制シャットダウンアプローチを使用すると、WMIを含むすべてのサービスがすぐにシャットダウンされます。このため、リモートコンピューターに対してスクリプトを実行している場合、戻り値を受け取ることはできません。

2(0x2)

再起動

コンピュータをシャットダウンしてから再起動します。

6(0x6)

強制再起動

コンピュータをシャットダウンしてから再起動します。

強制再起動アプローチを使用すると、WMIを含むすべてのサービスがすぐにシャットダウンされます。このため、リモートコンピューターに対してスクリプトを実行している場合、戻り値を受け取ることはできません。

8(0x8)

パワーオフ

コンピュータをシャットダウンし、電源をオフにします(問題のコンピュータでサポートされている場合)。

12(0xC)

強制パワーオフ

コンピュータをシャットダウンし、電源をオフにします(問題のコンピュータでサポートされている場合)。

強制電源オフのアプローチを使用すると、WMIを含むすべてのサービスがただちにシャットダウンされます。このため、リモートコンピューターに対してスクリプトを実行している場合、戻り値を受け取ることはできません。




WordPressプラグインのページングデザインを変更する

WordPress

WordPressプラグインを開発する際、記事の一覧を表示するようなプラグインを開発する場合は、一覧を切り替えるためのページングがどうしても必須になってきます。
ページング(ページナビゲーション)とはページ送りのことで、一覧画面の次ページ・前ページ、といった機能の事です。


WordPressプラグインの標準機能で、「paginate_links」という機能を使えばページング自体は出力されるのですが、画面デザインについては自身でカスタマイズする必要があります。


今回は、WordPressプラグインのページングデザインを修正する方法を紹介します。


環境情報


  • WordPress 5.1.6

paginate_linksでのページング出力


WordPressプラグインでは、「paginate_links」を使用すればページングが出力されます。
記事一覧とページングを表示するサンプルプログラムは以下になります。


// 検索条件を設定
$paged = get_query_var('paged') ? get_query_var('paged') : 1 ;
$args = array(
    'post_type' => 'post',
    'posts_per_page' => 20,
    'paged' => $paged
);
// クエリを実行
$query = new WP_Query($args);

// クエリ結果を表示
if ( $query->have_posts() ) {
    echo '<table>';
    $lc = 1;
    while ( $query->have_posts() ) {
        $query->the_post();
        echo '<tr>';
        echo '<td>';
        echo $lc;
        echo '</td>';
        echo '<td>';
        echo the_title();
        echo '</td>';
        echo '</tr>';
        $lc++;
    }
    echo '</table>';

    echo '
';
    echo paginate_links(array(
        'base' => str_replace(
            $big, '%#%', esc_url(get_pagenum_link($big))),
        'show_all' => true,
        'type' => 'list',
        'format' => '?paged=%#%',
        'current' => max(2, get_query_var('paged')),
        'total' => $query->max_num_pages,
        'prev_text' => '前へ',
        'next_text' => '次へ',
    ));
    echo '</table>';
    echo '
';
} else {
    echo "no result.
";
}

上記のプログラムでページング自体は表示されますが、デザインは何も設定されていない状態になります。



このページング部分について、CSSでデザインを設定していきます。


CSSを作成する


ページング部分のHTMLは以下になります。
このHTMLを「paginate_links」が出力しています。


<ul class="page-numbers">
    <li><a class="prev page-numbers" href="URL">前へ</a></li>
    <li><a class="page-numbers" href="URL">1</a></li>
    <li><span aria-current="page" class="page-numbers current">2</span></li>
    <li><a class="page-numbers" href="URL">3</a></li>
    <li><a class="page-numbers" href="URL">4</a></li>
    <li><a class="page-numbers" href="URL">5</a></li>
    <li><a class="next page-numbers" href="URL">次へ</a></li>
</ul>

<ul><li>のリスト形式でページングが構成されていることがわかります。
リスト形式を縦並びではなく横並びにする方法で何通りかあるのですが、inline-block形式で横並びにする方法でデザインを変更していきます。


以下のCSSを適用します。


ul {
  list-style: none;
}

ul.page-numbers {
  width: 1000px;
  padding:0;
  font-size:0px;
}

ul.page-numbers li {
  padding-top:10px;
  margin-left:10px;
  width:50px;
  border:1px solid black;
  display: inline-block;
  text-align: center;
  font-size:15px;
  height:30px;
}

ul.page-numbers li .current {
  font-size:15px;
}

ul.page-numbers li a {
  display: block;
  font-weight:bold;
}

ul.page-numbers li .current {
  padding-top:50px;
}

このCSSを適用することで、ページングのデザインが整います。



よくあるページングデザインにしてみたつもりですが、デザインを変更したい場合は、上記のサンプルCSSを調整すればデザインは調整できます。


CSSをプラグインに適用


CSSを自作したWordPressプラグインに適用する場合、プラグインのプログラムにCSSの読み込み処理を実装する必要があります。
まず、自作したCSSをプラグインフォルダに格納します。
以下の例では、プラグインフォルダの下にCSSという名前のフォルダを作成し、その中に「style.css」というファイルを格納しておきます。



WordPressプラグイン側の実装は、以下になります。


/**
コンストラクタ
*/
function __construct() {
    if (is_admin() && is_user_logged_in()) {
        wp_register_style(
            'myplugin_css', plugins_url('css/style.css', __FILE__));
        wp_enqueue_style('myplugin_css');
    }
}

使用する機能は以下の2つになります。


  • wp_register_style

CSSスタイルファイルの登録。


  • wp_enqueue_style

画面ページに CSSファイルを追加。


上記の実装をWordPressプラグインに実装することで、WordPressページにデザインが適用されます。



ApacheAntを使ってWEBアプリケーションをビルドする環境を作成

apache

実業務でWEBアプリケーションを開発する際、製造と単体試験は各メンバのパソコンで実施するかと思います。
しかし、本番リリースするアプリケーションをビルドする際は、それなりのビルドマシンを準備してビルドする方がよいです。
正式なアプリケーションを個人のパソコンでビルドするのはリスクが高いですからね。


ビルド用のマシンを準備してビルド環境を構築する際、ApacheAnt(アパッチアント)を使ってビルド環境を構築するのがシンプルで使い勝手もよいとおもいます。


ApacheAntとはいわゆるビルドツールでして、ビルド内容を定義するxmlファイルにAntタスクと呼ばれる定義を記載していくことで、ビルド環境を構築することができます。


今回はApacheAntを使ってWEBアプリケーションのビルド環境を構築する方法を紹介していきます。


環境情報


  • OS:Windows10
  • ビルドツール:ApacheAnt 1.8.2
  • Java:Java1.8.60

ApacheAntの公式サイトは以下になります。



ビルド環境の構成


Windows環境であることを前提に説明していきますが、Linux系の環境であっても同様かと思います。

Linux環境の人は、パス等を読み替えて試してみてください。


ビルド環境としては以下になります。


C:---apache-ant-1.8.2       // ApacheAntはCドライブ直下に格納。
  |                         //
  |                         //
  |---buildEnv              // ビルドするプロジェクトが全て
    |                       // 格納されているフォルダ。
    |---buildPJ.bat         // ビルドを実行するBATファイル
    |                       //
    |---targetPJ            // ビルド対象プロジェクト。
        |                   //
        |                   //
        |---build           // ビルド対象プロジェクトのフォルダ。
            |               //
            |               //
            |---build.xml   // ビルド対象プロジェクトの定義ファイル。

buildEnvの下に、ビルド対象プロジェクトを格納しています。
今回は、ビルド対象プロジェクトが一つ(targetPJのみ)の例なのですが、ビルド対象プロジェクトが複数存在する場合は、この「buildEnv」下に格納する方法がよいです。


BATファイル・XMLファイルについて、以降に説明していきます。


BATファイル(バッチファイル)


ビルドする際、格納したバッチファイルである「buildPj.bat」を実行したビルドするようにします。
「buildPj.bat」にビルドを開始する定義をおこなっておきます。


@echo off

echo ビルド環境の準備
set ANT_HOME=C:\apache-ant-1.8.2
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_60
set CLASSPATH=.
set PATH=%ANT_HOME%\bin;%PATH%
set STDWEBAP_SETENV="OK"

echo targetPJのビルドをおこないます。
cd C:\buildEnv\targetPJ\build >> C:\buildEnv\buildTargetPJ.txt
call ant clean >> C:\buildEnvbuildTargetPJ.txt
call ant war >> C:\buildEnv\buildTargetPJ.txt
cd C:\buildEnv >> C:\buildEnv\buildTargetPJ.txt

ビルド結果をログで確認できるように、「C:\buildEnv\buildTargetPJ.txt」に実行結果を出力しています。


XMLファイル


XMLファイルの基本構成を説明する前に、ビルド対象プロジェクトを説明していきます。


今回のビルド対象プロジェクトはWEBアプリケーションを想定します。
WEBアプリケーションの場合、Javaのようなソースコード以外に、JavaScriptファイル、CSSファイル、画像ファイル、といったメタファイル(静的ファイル)も扱わなければいけません。
かつ、今回はWAR形式に圧縮する形でビルドをおこないます。
そのため、XMLファイルは複雑になりがちなので、きちんとAntタスクを使いこなす形で定義する必要があります。

ビルド対象プロジェクトのフォルダ構成は以下になります。


targetPJ--build--build.xml           // ビルド定義。
         |                           //
         |                           //
         |--src                      //
             |                       //
             |--main                 //
                 |                   //
                 |--java             // ソースコード
                 |                   //
                 |--webapp           //
                     |               //
                     |--css          // CSS
                     |               // JavaScript
                     |--js           //
                     |               //
                     |--WEB-INF      //
                        |            //
                        |--properties// プロパティ
                        |            //
                        |--lib       // ライブラリ
                        |            //
                        |--view      // JSPなど

XMLの基本構成


XMLの基本構成は以下になります。


<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<project basedir="." default="war" name="targetPJ">

    <property environment="env" />

    <!-- ----------------------------------------------- --->
    <!-- 1.アプリ名、コンテンツ格納元・先、といった     -->
    <!--     プロパティを定義                             -->
    <!-- ----------------------------------------------- --->
    <property name="app.name" value="targetWeb" />
    <property name="dest.dir" value="dest" />
    <property name="src.dir" value="../src" />
    <property name="src.script.dir" 
        value="../src/main/webapp/js" />
    <property name="dest.script.dir" 
        value="${dest.dir}/release/resource/js" />
         ・
         ・
         ・

    <!-- ------------------------------------------------ -->
    <!-- 2.クラスパス定義を追加                         -->
    <!-- ----------------------------------------------- --->
    <path id="classpath">
        <fileset dir="${lib.dir}">
            <include name="*.jar" />
        </fileset>
    </path>

    <!-- ------------------------------------------------ -->
    <!-- 3.初期化とクリーン                             -->
    <!-- ------------------------------------------------ -->
    <target name="init" depends="clean">
        <mkdir dir="${dest.dir}" />
    </target>
    <target name="clean">
        <delete dir="${dest.dir}" />
    </target>

    <!-- ------------------------------------------------ -->
    <!-- 4.warファイル作成                              -->
    <!-- ------------------------------------------------ -->
    <target name="war" depends="init,compile">
        <echo message="warファイルを作成します。" />
        <jar destfile="${dest.dir}/${app.name}.war" 
           basedir="${dest.webcontents.dir}">
            <manifest>
                <attribute name="version" value="1.0.0" />
            </manifest>
        </jar>
    </target>

    <!-- ------------------------------------------------ -->
    <!-- 5.コンパイル                                   -->
    <!-- ------------------------------------------------ -->
    <target name="compile">
        <echo message="ビルドを行います。" />
        <mkdir dir="${dest.class.dir}" />
        <javac debug="true" destdir="${dest.class.dir}" 
            includeantruntime="false" source="${source}" 
            target="${target}" encoding="UTF-8">
            <src path="${src.dir}" />
            <classpath refid="classpath" />
        </javac>
    </target>

    <!-- ------------------------------------------------ -->
    <!-- 6.静的ファイルのコピー                         -->
    <!-- ------------------------------------------------ -->
    <target name="contents">

        <copy preservelastmodified="true" 
            todir="${dest.css.dir}" flatten="no">
            <fileset dir="${css.dir}" includes="**/*.css" />
            <fileset dir="${css.dir}" includes="**/*.htc" />
            <fileset dir="${css.dir}" includes="**/*.png" />
            <fileset dir="${css.dir}" includes="**/*.gif" />
            <fileset dir="${css.dir}" includes="**/*.svg" />
        </copy>
    </target>
</project>

Antタスク(アントタスク)


「XMLの基本構成」で使用しているAntタスクについて、特に重要なタスクについて説明してきます。


  • <property>タスク

主に、「1.アプリ名、コンテンツ格納元・先、といったプロパティを定義」で使用しているAntタスクです。


<property>タスクは、XMLファイル内で使用する変数に値を格納するタスク定義です。
以下の例では、「src.script.dir」という定数名に”../src/main/webapp/js”という値を格納しています。


<property name="src.script.dir" value="../src/main/webapp/js" />

大きなプロジェクトになるほど、XMLファイルは複雑になります。
<property>タスクを使って、定数定義をおこなっておいた方が見やすい定義になります。


  • <path>タスク

「2.クラスパス定義を追加」で使用しているAntタスクです。


<path>タスクは、idで指定したパスに新たなパスを追加することができるAntタスクになります。
「2.クラスパス定義を追加」では、<fileset>タスク・<include>タスクを使用して、jarファイルのパスをクラスパスに設定して、ビルドがおこなえるようにしています。


  • <target>タスク

「3.初期化とクリーン」でも使用していますが、他の箇所でも多く使用しているAntタスクです。


<target>タスクは、初期化やコンパイルといった大きなタスクを括りを定義できるタスクです。
「■BATファイル」の中で以下の2行を定義しています。


call ant clean >> C:\buildEnvbuildTargetPJ.txt
call ant war >> C:\buildEnv\buildTargetPJ.txt

「clean」「war」という2つのタスクを、<target>タスクに定義することでBATファイルからの定義を可能としています。


<target>タスクで囲ったタグの間に、定義した名称で実行するタスクを定義します。


<target name="clean">
    <delete dir="${dest.dir}" />
</target>

上記例では、「clean」という名称で実行するタスクを定義しています。


かつ、「3.初期化とクリーン」では、<mkdir>タスク・<delete>タスクを使用して、ビルド環境を初期化しています。
<mkdir>タスクはフォルダ作成のタスクになります。
<delete>タスクは削除のタスクになります。


  • <javac>タスク

「5.コンパイル」で使用しているAntタスクです。


構文は難しくないですが、ポイントとしては「includeantruntime」です。
「includeantruntime」とは、”ビルドにAntのライブラリを使用するか?”という属性になります。
この属性をfalseしておかないと、ビルドにAntのライブラリを参照してしまい、ビルドに失敗してしまう場合があります。


  • <copy>タスク

「6.静的ファイルのコピー」で使用しているAntタスクです。


JavaScriptファイル、CSSファイル、画像ファイル、といった静的ファイルのコピーをおこなっています。
「preservelastmodified」をtrueにすいることで、ファイルのタイムスタンプも保持したままコピーすることができます。


<fileset>タグを使い、コピーする対象ファイルを、拡張子指定のワイルドカードで指定をおこなっています。


まとめ


ApacheAntを使えば、ビルドルールをあらかじめ定義することができ、安定したビルド環境を構築することができます。


きちんと開発をおこなっていても、ビルドで失敗して不具合が発生したら悔しいですからね。


紹介してきれていないAntタスクを沢山ありますので、皆さんで調べていろいろ試してみてください。