LoginSignup
2
0

More than 5 years have passed since last update.

[Java]jsoupでブラウザから時刻合わせ

Last updated at Posted at 2018-01-09

要件

  • とあるアプリケーションで、時刻がすぐに狂ってしまう。
  • NTPへの接続はできない。社内NTPも不可。
  • アプリケーションはブラウザからIPアドレスでアクセスが出来る。
  • ブラウザ上から手動で時刻設定は可能。
  • 該当アプリケーションが動くサーバは、全国30台近く点在している。
  • サーバは今後増減可能性あり。
  • ログインパスワードはバッチ実行時に入力する(引数もしくはプロンプト)
  • Javaは社内標準で導入されている。それ以外の言語の導入は手続きが必要。

実行環境

  • Windows8.1, 10 (メインは8だが、将来的に10になる可能性大)
  • Java1.8以上

開発環境

  • Windows7
  • Eclipse
  • Java1.8
    • jsoup 1.10.2
  • gradle
    • applicationプラグインでbatファイルからjarを実行可能に設定

ソースコード

時刻合わせ起動バッチ

  • src\dist\以下に配置
TimeAdjusterMain.bat
@echo off

rem ------------------------------------------------
rem --- 時刻合わせバッチ
rem ------------------------------------------------

rem -- カレントディレクトリを基準とする
cd /d %~dp0

echo **** 時刻合わせ開始: %date% %time%

rem -- 引数をチェックする(xxを付ける事で空にならないようにする)
if xx%1==xx goto confirmPw
goto setPw



:confirmPw

rem -- 引数が指定されていない場合、管理者パスワードの入力
set /P PASSWD=管理者パスワードを入力してください: 

goto exec



:setPw

rem -- 引数が指定されている場合、管理者パスワードとして設定
set PASSWD=%1

goto exec



rem -- 処理バッチの実行
:exec

rem -- 処理バッチの実行開始
call bin\TimeAdjuster.bat %PASSWD%

echo **** 時刻合わせ終了: %date% %time%

rem -- 完了させずとどまる
pause

処理対象IPアドレスリストtsv

  • src\dist\以下に配置
list.tsv
10.20.30.40 abc123  ABC123  A支店 Bフロア  A-B#1
10.20.50.60 def456  DEF456  C支店 Dフロア  C-D#2

Javaソースコード

Main

TimeAdjusterMain.java
package jp.co.sample.timeadjuster;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.sample.timeadjuster.bean.SettingBean;
import jp.co.sample.timeadjuster.launcher.TimeAdjusterLauncher;

public class TimeAdjusterMain {

    private static Logger logger = LoggerFactory.getLogger(TimeAdjusterMain.class);

    public static void main(String[] args) {
        logger.info("TimeAdjusterMain 開始");

        // 設定情報
        SettingBean setting;
        try {
            // args[0]: 管理者パスワード
            // args[1]: src/distフォルダ絶対パス
            setting = new SettingBean(args[0], args[1], "list.tsv");
        } catch (Exception e) {
            logger.error("設定情報エラー", e);
            return;
        }

        // 処理実行
        TimeAdjusterLauncher launcher = new TimeAdjusterLauncher(setting);
        try {
            launcher.execute();
        } catch (Exception e) {
            logger.error("時刻合わせ処理エラー", e);
            return;
        }
    }
}

設定情報

SettingBean.java
package jp.co.sample.timeadjuster.bean;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SettingBean {

    public static Logger logger = LoggerFactory.getLogger(SettingBean.class);

    /**
     * 管理者パスワード
     */
    String passwd;

    /**
     * 実行パスの基準ディレクトリ
     */
    String basePath;

    /**
     * tsvファイル名
     */
    String tsvName;

    /**
     * サーバ情報リスト
     */
    ArrayList<ServerBean> serverList = new ArrayList<>();


    public SettingBean(String passwd, String basePath, String tsvName) throws IOException {
        this.passwd = passwd;

        this.basePath = basePath;

        logger.debug("this.basePath: "+ this.basePath);

        this.tsvName = tsvName;

        logger.debug("this.tsvName: "+ this.tsvName);

        File targetFile = new File(this.basePath, this.tsvName);
        if (!targetFile.isFile()) {
            logger.error("実在するファイルパスを指定して下さい。: "+ path);
            return;
        }

        // ファイルを読み込む
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(targetFile),"UTF-8"));) {
            // TAB区切りのファイルで、ヘッダあり。空白は除外する
            Iterable<CSVRecord> records = CSVFormat.TDF.withIgnoreEmptyLines().withIgnoreSurroundingSpaces().parse(reader);
                for (CSVRecord record : records) {

                    logger.debug(record.get(0));

                    // サーバのIPアドレスはIPアドレス型のチェックを入れる
                    // メモ帳でtsvファイルを作成すると、先頭に?が入ってしまうため
                    Pattern p = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
                    Matcher m = p.matcher(record.get(0));

                    if (!m.find()) {
                        throw new RuntimeException("IPアドレスの指定方法が間違っています。 IPアドレス:"+ record.get(0));
                    }

                    // サーバ情報を取得し、リストに追加する
                    ServerBean server = new ServerBean(m.group(), record.get(1), record.get(2), record.get(3), record.get(4), record.get(5));
                    serverList.add(server);

                    logger.debug("server: "+ server.getIpaddress() + " / "+ server.getSerial_no() + " / "+ server.getDevice_name());
                }
        } catch (FileNotFoundException e) {
            logger.error("TSVファイル読み込みエラー", e);
            throw e;
        } catch (IOException e) {
            logger.error("TSVファイル読み込みエラー", e);
            throw e;
        }

    }


    public String getPasswd() {
        return passwd;
    }


    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }


    public String getBasePath() {
        return basePath;
    }


    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }


    public String getTsvName() {
        return tsvName;
    }


    public void setTsvName(String tsvName) {
        this.tsvName = tsvName;
    }


    public ArrayList<ServerBean> getServerList() {
        return serverList;
    }


    public void setServerList(ArrayList<ServerBean> serverList) {
        this.serverList = serverList;
    }

}

サーバ情報(tsvから読み込んだ情報)

ServerBean.java
package jp.co.sample.timeadjuster.bean;

public class ServerBean {
    /**
     * IPアドレス
     */
    String ipaddress;

    /**
     * ホスト名
     */
    String host_name;

    /**
     * シリアル番号
     */
    String serial_no;

    /**
     * 拠点名
     */
    String base_name;


    /**
     * 設置場所
     */
    String installation_location;

    /**
     * 機器名称
     */
    String device_name;

    public ServerBean(String ipaddress, String host_name, String serial_no, String base_name,
            String installation_location, String device_name) {
        super();
        this.ipaddress = ipaddress;
        this.host_name = host_name;
        this.serial_no = serial_no;
        this.base_name = base_name;
        this.installation_location = installation_location;
        this.device_name = device_name;
    }

    public String getIpaddress() {
        return ipaddress;
    }

    public void setIpaddress(String ipaddress) {
        this.ipaddress = ipaddress;
    }

    public String getHost_name() {
        return host_name;
    }

    public void setHost_name(String host_name) {
        this.host_name = host_name;
    }

    public String getSerial_no() {
        return serial_no;
    }

    public void setSerial_no(String serial_no) {
        this.serial_no = serial_no;
    }

    public String getBase_name() {
        return base_name;
    }

    public void setBase_name(String base_name) {
        this.base_name = base_name;
    }

    public String getInstallation_location() {
        return installation_location;
    }

    public void setInstallation_location(String installation_location) {
        this.installation_location = installation_location;
    }

    public String getDevice_name() {
        return device_name;
    }

    public void setDevice_name(String device_name) {
        this.device_name = device_name;
    }

}

時刻合わせ処理

TimeAdjusterLauncher.java
package jp.co.sample.timeadjuster.launcher;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

import org.apache.commons.lang3.StringUtils;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.sample.timeadjuster.bean.ServerBean;
import jp.co.sample.timeadjuster.bean.SettingBean;

public class TimeAdjusterLauncher {

    public static Logger logger = LoggerFactory.getLogger(TimeAdjusterLauncher.class);

    /**
     * 設定情報
     */
    SettingBean setting;

    /**
     * ログイン画面表示結果
     * Cookie等を保持
     */
    Response loginRes;

    /**
     * コンストラクタ
     *
     * @param setting 設定情報
     */
    public TimeAdjusterLauncher(SettingBean setting) {
        super();

        // 設定を保持する
        this.setting = setting;
    }

    /**
     * 時刻合わせ処理を実行します
     * @throws RuntimeException
     * @throws IOException
     */
    public void execute() throws RuntimeException, IOException {

        // サーバ一台ごとに処理をループする
        for (ServerBean server : setting.getServerList()) {
            logger.info("時刻合わせ開始 ****************** ");
            logger.info("対象サーバ: "+ server.getIpaddress() + " / "+ server.getDevice_name());

            // ログイン
            login(server);

            // 現在設定時刻確認
            String zone = getTimer(server);

            // 新規時刻設定
            adjustTimer(server, zone);
        }

    }

    /**
     * ログイン処理
     * @throws RuntimeException
     * @throws IOException
     */
    private void login(ServerBean server) throws RuntimeException, IOException {
        // ログイン処理実行
        String loginUrl = "http://"+ server.getIpaddress() + "/logon.cgi";
        try {
            loginRes = Jsoup.connect(loginUrl)
                    .data("Password", setting.getPasswd())
                    .data("Mode", "Admin")
                    .data("Lang", "Japanese")
                    .method(Method.POST)
                    .execute();
        } catch (IOException e) {
            logger.error("初期画面ログインエラー", e);
            throw e;
        }

//      logger.debug(loginRes.parse().outerHtml());

        // HTTPステータス照合
        checkStatusCode(loginRes, loginUrl);


        if (StringUtils.contains(loginRes.parse().outerHtml(), "/pages/_top2.htm") ) {
            // ログインエラー画面にリダイレクトがかかっている場合、
            // 管理者ログインに失敗したという事なので、エラー終了
            throw new RuntimeException("管理者ログインに失敗しました。処理を中断します。");
        }


        // ログイン成功後、リダイレクトを行う
        // 実際には特に必要ない処理。確認のために行っただけ。
        String redirectUrl = "http://"+ server.getIpaddress() + "/pages/_devadm.htm";
        Response redirectRes;
        try {
            redirectRes = Jsoup.connect(redirectUrl)
                // ログイン時に取得したcookieを設定する
                .cookies(loginRes.cookies())
                .method(Method.GET)
                .execute();
        } catch (IOException e) {
            logger.error("初期画面ログインリダイレクトエラー", e);
            throw e;
        }

//      logger.debug(redirectRes.parse().outerHtml());

        // HTTPステータス照合
        checkStatusCode(redirectRes, redirectUrl);

    }

    /**
     * 現在サーバに設定されている時刻を取得します
     *
     * @param server
     * @throws RuntimeException
     * @return ゾーン
     * @throws IOException
     */
    private String getTimer(ServerBean server) throws RuntimeException, IOException {

        // サーバ時刻設定画面を取得します
        String timerUrl = "http://"+ server.getIpaddress() + "/pages/ed_time.htm";
        Response timerRes;
        try {
            timerRes = Jsoup.connect(timerUrl)
                    // ログイン時に取得したcookieを設定する
                    .cookies(loginRes.cookies())
                    .method(Method.GET)
                    .execute();
        } catch (IOException e) {
            logger.error("時刻設定画面取得エラー", e);
            throw e;
        }

        // HTTPステータス照合
        checkStatusCode(timerRes, timerUrl);

//      logger.debug(timerRes.parse().outerHtml());

        Document doc = timerRes.parse();

        // 現在サーバに設定されている時刻を取得します。
        String year = doc.getElementsByAttributeValue("name", "DateYYYY").get(0).val();
        String month = doc.getElementsByAttributeValue("name", "DateMM").get(0).val();
        String day = doc.getElementsByAttributeValue("name", "DateDD").get(0).val();
        String hour = doc.getElementsByAttributeValue("name", "TimeHH").get(0).val();
        String minutes = doc.getElementsByAttributeValue("name", "TimeMM").get(0).val();
        String zone = doc.select("select option[selected]").get(0).val();

        logger.info("現在サーバ時刻: "+ year + "/"+ month + "/"+ day + " "+ hour +":"+ minutes + " ("+ zone + ")");

        // zoneだけ、既存値を使用するので、返します。
        return zone;
    }

    /**
     * 時刻合わせを行います。
     *
     * @param server
     * @param zone
     * @throws RuntimeException
     * @throws IOException
     */
    private void adjustTimer(ServerBean server, String zone) throws RuntimeException, IOException {
        // 実行環境の現在時刻を取得します。
        LocalDateTime now = LocalDateTime.now();

        logger.info("現在時刻: "+ now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));

        // 1分後の0秒に合わせるため、次の時刻合わせのタイミングを取得します。
        LocalDateTime nextLdt =
                LocalDateTime.now()
                .plusMinutes(1)
                .truncatedTo(ChronoUnit.MINUTES);

        logger.debug("合わせる時刻: "+ nextLdt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));

        // 待機ミリ秒
        long localDiffMsec = ChronoUnit.MILLIS.between(now, nextLdt);

        logger.debug("合わせる時刻まで、ミリ秒待機: "+ localDiffMsec);

        try {
            // 待機ミリ秒分、待つ
            Thread.sleep(localDiffMsec);
        } catch (InterruptedException e) {
            logger.error("サーバ時刻合わせミリ秒待機失敗");
        }

        logger.info("時刻合わせ実行");

        // サーバ時刻合わせ処理実行(POST)
        // なぜかcookieがなくても動いたので、そのままPOST投げてます
        String timerUrl = "http://"+ server.getIpaddress() + "/settime.cgi";
        Response timerRes;
        try {
            timerRes = Jsoup.connect(timerUrl)
                    .data("DateYYYY", ""+ nextLdt.getYear())
                    .data("DateMM", ""+ nextLdt.getMonthValue())
                    .data("DateDD", ""+ nextLdt.getDayOfMonth())
                    .data("TimeHH", ""+ nextLdt.getHour())
                    .data("TimeMM", ""+ nextLdt.getMinute())
                    .data("TimeZone", ""+ zone)
                    .method(Method.POST)
                    .execute();
        } catch (IOException e) {
            logger.error("サーバ時刻合わせ投稿エラー", e);
            throw e;
        }

//      logger.debug(timerRes.parse().outerHtml());

        // HTTPステータス照合
        checkStatusCode(timerRes, timerUrl);

        logger.info("時刻合わせ完了: "+ server.getIpaddress() + " / "+ server.getDevice_name() + " / " + nextLdt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));

    }

    /**
     * HTTPステータスのチェック
     *
     * @param res
     * @param url
     * @throws RuntimeException
     */
    private void checkStatusCode(Response res, String url) throws RuntimeException {

        if (res.statusCode() != 200) {
            // HTTPステータスが200(正常)ではない場合、エラーとする
            throw new RuntimeException("URLアクセス失敗: url="+ url + " status="+ res.statusCode());
        }
    }

}

所感

  • jsoupというとスクレイピングがよくに出てきますが、こういう使い方もありますよ、という事で。
  • ログイン->cookie保持->ログイン後処理 というjsoupコードが日本語であんまりなかったので、せっかくの機会ですし纏めてみました。
  • jsoupで一番苦労するのは、実際に処理が実行されるURLがどういうものなのか、を見つける事ですね。form で指定されていたり、リダイレクトされたり…
  • メモ帳でtsvを触ると、なぜか先頭に?が入ってしまう、という謎の現象が起きたので、IPアドレスの取得方法には正規表現を使用しています。エディタの指定とかできないんで…
  • つい昔の習慣で、beanを作って詰めてしまいますが、最近はこうじゃないんですかね…新しい方法も勉強しないと ^^;

今年もコツコツ頑張っていきたいと思います。どうぞよろしくお願いいたします。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0