FortniteAPIを使って対戦履歴を取得する

fortnite-Victory Royale

Fortniteおもしろいですよね。
筆者もハマっています。
下手ですが。


自分の対戦履歴(プレイ時間やキル数、など)などを知りたいなと思い、調べてみると「Fortnite Tracker」というサイトで調べることができます。


こういうサイト作ってみたいなと思い、もうちょっと調べてみるとAPIが準備されているみたいですね。
でも、このAPIのいい感じの日本語ドキュメントが見つからず。。。


という訳で、自分なりにAPIの処理結果を解析してみたので、その結果を紹介していきます


環境情報


使用するAPIは、Rakuten Rapid APIで公開されているAPIを使用します。
Epic(Fortniteの運営会社)が公開しているAPIをラップしている形ですかね。
使いやすいです。


今回は、上記のAPIをPHPから呼び出して、処理結果をWEBページに表示する形にします。


  • 言語:PHP5.5.35
  • API:Rakuten Rapid API/Fortnite-API

Rakuten Rapid API は従量課金制ですが、お試しで使う分には課金されるまではトラフィックは使わないと思います。


ステータス取得のAPI実行


APIの実行サンプルを紹介します。


今回はcurlを使ってAPIを実行しています。
キー情報(x-rapidapi-key)は、Rakuten Rapid APIで取得したキー情報を使用します。


Fortnite APIは、「Status」「Others」「PVE」の3つがありますが、戦歴を取得できるのは「Status」のようです。
「Status」APIをcurlで実行して、取得した結果をJSONデコードします。


$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_URL => "https://fortnite-api.p.rapidapi.com/stats/" . $_POST['__nickname'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => array(
        "x-rapidapi-host: fortnite-api.p.rapidapi.com",
        "x-rapidapi-key: '※APIのキー"
    ),
));
$response = curl_exec($curl);

// 実行失敗時はエラー表示
$err = curl_error($curl);
curl_close($curl);
if ($err) {
    echo "cURL Error #:" . $err;
} 

// JSONの処理結果をデコード
$resDecode = json_decode($response, true);

ソロ結果


ソロ結果を表示するWEB画面を作りました。

ニックネームを入力して「表示する」ボタンを押下すると、ニックネームユーザの戦歴が画面上に表示されます。


以下に、API実行結果の解析方法を紹介していきます。


ソロ結果の画面表示


「Fortnite Tracker」のソロ結果表示画面は以下になります。
吹き出しでコメントを入れている数値が、APIで取得できているはず、という事です。


FortniteAPIの実行結果

ソロ結果のAPI取得


FortniteAPIの実行結果に対する日本語マニュアルがみつからず、筆者が独自で解析した結果をまとめます。


まず、ソロ結果は”defaultsolo”キーに格納されているようです。

ソロ結果の格納場所
ソロ結果の各値

  • マッチ数

‘matchNum’キーの値。


  • 勝利数(ビクロイ)

‘winrate’キーの値。


  • キル数

‘kills’キーの値。


  • 勝率(ビクロイ率)

‘winrate’キーの値。


  • 1日あたりのキル数

‘kills’キーの値 / ’matchNum’キーの値。


  • TOP10の入賞数

‘placetop10’キーの値。


  • TOP25の入賞数

‘placetop25’キーの値。


  • 総プレイ時間

‘minutesplayed’キーの値。
値は分なので、日と時間にするには計算が必要。


  • 1マッチの平均時間

‘minutesplayed’キーの値 / ’matchNum’キーの値


  • 1マッチの平均キル数

‘kills’キーの値 /’matchNum’キーの値


  • 1分あたりのの平均キル数

‘kills’キーの値 /’mminutesplayed’キーの値


  • 1マッチの平均スコア数

‘score’キーの値 /’matchNum’キーの値


  • 1分あたりのの平均スコア数

‘score’キーの値 /’minutesplayed’キーの値


  • 総スコア数

‘score’キーの値。



MicrosoftProjectでスケジュールを作成する。タスクの作成や休日の設定方法。

スケジュール

システム開発において、まずPM(プロジェクトマネージャ)が作成するのがスケジュールかと思います。
「誰が」「いつまでに」「何を」を明確にしてスケジューリングします。


この際、スケジュールを作成するツールが必要になるのですが、筆者は「MicrosoftProject」がおすすめです。
筆者自身がずっと使っていて慣れているという理由もあるのですが、とても高機能なので、スケジュール管理でやりたい事はほぼ全てできます。


今回は、「MicrosoftProject」でのスケジュールの作成方法について最低限覚えておくべきことを紹介します。
とても高機能なので、全ての機能を使うのは大変です。
というよりも、全ての機能を使わなくても実際のプロジェクトで耐えられるレベルのスケジュールは作成できます。
最低限、“これさえ覚えていれば大丈夫”という使い方を説明していきます。


環境


  • MicrosoftProject2007

プロジェクト全般の設定


タスクを作成していく前に、まずはプロジェクト全般の設定をおこないます。
プロジェクト全般の設定をきちんとおこなっていないと、以降のタスク作成に影響が出てしまいますので、まずはこの設定をきちんとおこなっていきましょう。


開始日の設定


プロジェクトの開始日を設定することができます。
ここの日付は様々な箇所で参照されますが、ここで設定する日付がタスクのデフォルト日付になります。


「プロジェクト」→「プロジェクト情報」から、『開始日』をプロジェクトの開始日とします。

MicrosoftProjectで開始日の設定

休日の設定


休日の設定も可能です。
デフォルトの休日設定では、通常の土日は休日に設定されていますが、祝祭日は休日に設定されていないので、自分で設定する必要があります。
また、人によっては個別の休日(有給を使って夏休み、とか)がありますが、祝祭日のようなPJ全体ではない、人毎の休日設定も可能です。


「ツール」→「稼働時間の変更」から、休日の設定をおこなうことができます。


全体の休日を設定する場合は、カレンダーの部分で「標準(プロジェクト カレンダー)」を設定して休日の設定をおこないます。

MicrosoftProjectで休日の設定

人によって異なるの休日の設定をおこなう場合は、「カレンダー」で休日を設定したい要員を選択して休日の設定をおこないます。
こうすることで、選択した要員(リソース)に対してだけ適用される休日を設定することができます。

MicrosoftProjectで休日の設定

タスクの作成


タスクを作成して、実際に各メンバに作業の割り振りをおこないます。


タスクを新規登録


タスク名の部分をダブルクリックすると、タスクの新規登録ウィンドウがひらくので、タスク名を入力してタスクを新規登録します。
タスクの新規登録ウィンドウで、期間・担当者の入力も可能です。

MicrosoftProjectでタスクの作成

子タスクを作成(レベル上げ・レベル下げ)


実際のプロジェクトでは、タスクは親子関係であることがほとんどです。
例えば、各画面のコーディングは『製造』というタスクの子タスクですし、各画面の試験は『試験』というタスクの子タスクです。
こういった、タスクの親子関係は、タスクの「レベル上げ・レベル下げ」で設定することができます。


以下例では、画面A・B・Cのコーディングと試験スケジュールを、親子関係を表現する形でスケジューリングしています。

MicrosoftProjectでタスクのレベル上げ・下げ

↓↓↓

MicrosoftProjectでタスクのレベル上げ・下げ

先行タスクを設定


「先行タスク」という形で、タスクの前後関係を設定することができます。
“このタスクが終わったらこのタスク”っていう設定が可能です。
「先行タスク」という名の通り、タスクの前のタスクを設定していきます。

MicrosoftProjectで先行タスクの設定

進捗の入力


スケジュールを作成した後に実際の作業を進めていくことになるのですが、各タスクの進捗を入力する必要があります。
この進捗管理もMicrosoftProjectで可能です。


この進捗の入力は「達成率」というフィールドで管理可能ですが、デフォルトではこの列は非表示となっています。
まずはこの列を表示する必要があります。


任意の列を選択して左クリックし、「列の挿入」を選択します。
そこで「達成率」を選択すれば、達成率フィールドが一覧に表示されるようになります。

MicrosoftProjectで進捗率の表示
MicrosoftProjectで進捗率の設定

この達成率フィールドにパーセンテージを入力するこで、各タスクの進捗を管理することができます。


印刷する


「MicrosoftProject」でスケジュールを作成しても、作成したスケジュールは「MicrosoftProject」がインストールされていないと参照できません。
Viewerで参照もできますが、一番手っ取り早い方法はPDFにして印刷する方法です。
そうすれば、皆が参照できます。


しかしそうした場合、デフォルトの設定だと1P目にしかタスク名が表示されず、とても見辛いです。


1P目

MicrosoftProjectで印刷

2P目

MicrosoftProjectで印刷

上記を解消するために、全てのページに左のタスク名等を表示することができます。
「ファイル」→「ページ設定」から、左からN列目までをすべてのページに印刷する、についてチェックをおこない、全ての印刷ページに印刷する列数を定義することができます。


MicrosoftProjectで印刷ページの設定

2P目

MicrosoftProjectで印刷

まとめ


MicrosoftProjectは非常に強力なスケジュール作成ツールで、とても高機能です。
しかし、今回紹介した機能さえ覚えていれば、プロジェクトを進めていく中で最低限必要な情報は定義・管理していくことができます。


上手にツールを使い、円滑なプロジェクト運営をしていきましょう!



サジェストAPIを使って、各サービスのサジェストワードを取得する

JavaScript

Googleを始めとした検索システムにおいて、検索ワードのテキストボックスにキーワードを入力すると候補文字列が表示されます。
これを「サジェスト」と呼び、日本語訳では「予測変換」になります。


このサジェスト文字列は、各サービスからAPIで提供されており、個別で実行可能になります。


今回は、各サービス毎のサジェストAPIの実行方法と、取得結果について紹介していきます。


環境情報


PHPでAPIを実行して、取得結果の表示をおこないます。


  • PHP 5.5.35

APIのURL


各APIのURLは以下になります。
取得形式はXMLだったらJSONだったりするのですが、できるだけ様々なパターンを試してみたいので、両方のパターンを実施してみます。


サービス 取得形式 URL
Google XML http://www.google.com/complete/search?hl=en&output=toolbar&q=keyword
Yahoo XML http://ff.search.yahoo.com/gossip?output=xml&command=keyword
Bing JSON https://api.bing.com/osjson.aspx?market=ja-JP&query=keyword
Youtube JSON http://clients1.google.com/complete/search?hl=en&ds=yt&client=firefox&q=keyword
Amazon JSON http://completion.amazon.co.jp/search/complete?search-alias=aps&mkt=6&q=keyword
wikipedia JSON https://ja.wikipedia.org/w/api.php?action=opensearch&format=json&search=keyword

※「keyword」が、予測変換したい文字列になります。


各APIのパラメータ詳細については、各サービスの正式ドキュメントを参照ください。


実行プログラムと実行結果


実行プログラムと実行結果を紹介していきます。
サービスによっては、APIの返り値には予測変換以外の属性情報が格納されている様なのですが、今回はあくまで予測変換文字列のみを抽出します。


なので、各プログラムで予測変換文字列以外は除外しています。


Google


一番メジャーな(気がする)Googleのサンプルコードです。
XMLで結果を取得して表示するプログラムになります。

try {
    $searchWord = "wordPress";
    $url = "http://www.google.com/complete/search?hl=en&output=toolbar&q=" . $searchWord;
    $result = simplexml_load_file($url);
    if (!isset($result)) {
        echo "not result.";
        exit;
    }
    foreach ($result->CompleteSuggestion as $item) {
        echo "suggestWord=" . 
            $item->suggestion->attributes()->data . "
"; } } catch (Exception $e) { echo "message=" . $e->getMessage() . "
"; exit; }

実行結果

suggestWord=wordpress
suggestWord=wordpress login
suggestWord=wordpress download
suggestWord=wordpress themes
suggestWord=wordpress tutorial
suggestWord=wordpress plugin
suggestWord=wordpress hosting
suggestWord=wordpress php
suggestWord=wordpress api
suggestWord=wordpress free

Yahoo


YahooもGoogleと同じようにXMLでの結果取得が可能です。
XMLの構造がYahooとは異なるので、微妙にコーディングが異なります。

try {
    $searchWord = "wordPress";
    $url = "http://ff.search.yahoo.com/gossip?output=xml&command=" . $searchWord;
    $result = simplexml_load_file($url);
    if (!isset($result)) {
        echo "not result.";
        exit;
    }
    foreach ($result as $item) {
        echo "suggestWord=" . 
            $item->attributes()->k . "
"; } } catch (Exception $e) { echo "message=" . $e->getMessage() . "
"; exit; }

実行結果

suggestWord=wordpress.com
suggestWord=wordpress login
suggestWord=wordpress.org
suggestWord=wordpress.com login
suggestWord=wordpress download
suggestWord=wordpress themes
suggestWord=www.wordpress.com login
suggestWord=wordpress login wp-admin
suggestWord=wordpress.org download
suggestWord=wordpress templates

Bing


BingからJSON形式で取得します。
XMLとはプログラムは異なりますが、基本的な構造は変わりません。


また、取得結果の配列は2段階になっているので、結果の配列構造も意識したプログラムとなります。

try {
    $searchWord = "wordPress";
    $url = "https://api.bing.com/osjson.aspx?market=ja-JP&query=" . $searchWord;
    $result = file_get_contents($url);
    if (!isset($result)) {
        echo "not result.";
        exit;
    }
    $result = json_decode($result, true);
    for ($lc = 0; $lc < count($result); $lc++) {
        if (is_array($result[$lc])) {
            $searchWord = $result[$lc];
            for ($slc = 0; 
                $slc < count($searchWord); $slc++) {
                echo "suggestWord=" . 
                    $searchWord[$slc] . "
"; } } } } catch (Exception $e) { echo "message=" . $e->getMessage() . "
"; exit; }

実行結果

suggestWord=wordpress
suggestWord=wordpress ログイン
suggestWord=wordpress 使い方
suggestWord=wordpressとは
suggestWord=wordpress インストール
suggestWord=wordpress テーマ
suggestWord=wordpress ログインできない
suggestWord=wordpress テンプレート
suggestWord=wordpress log in

Youtube


YoutubeはBingと同じプログラムで結果を取得できます。
APIのURLが違うだけです。


結果は以下になります。


実行結果

suggestWord=wordpress
suggestWord=wordpress tutorial
suggestWord=wordpress 使い方
suggestWord=wordpress website
suggestWord=wordpress blog
suggestWord=wordpress theme
suggestWord=wordpress speed optimization
suggestWord=wordpress plugins
suggestWord=wordpress マナブ
suggestWord=wordpress php

Amazon


Amazonは、BingとYoutubeと基本的に同様なのですが、結果配列の3階層目にも情報が格納されているようです。
サジェストワードは2階層目なので、3階層目を除外する制御を入れています。

try {
    $searchWord = "wordPress";
    $url = "http://completion.amazon.co.jp/search/complete?search-alias=aps&mkt=6&q=" . $searchWord;
    $result = file_get_contents($url);
    if (!isset($result)) {
        echo "not result.";
        exit;
    }
    $result = json_decode($result, true);
    for ($lc = 0; $lc < count($result); $lc++) {
        if (is_array($result[$lc])) {
            $searchWord = $result[$lc];
            for ($slc = 0; 
                $slc < count($searchWord); $slc++) {
                if (is_array($searchWord[$slc])) {
                    continue;
                }
                echo "suggestWord=" . 
                    $searchWord[$slc] . "
"; } } } } catch (Exception $e) { echo "message=" . $e->getMessage() . "
"; exit; }

実行結果

suggestWord=wordpressの教科書
suggestWord=wordpress
suggestWord=wordpress 本
suggestWord=wordpress php
suggestWord=wordpress ブログ
suggestWord=wordpress カスタマイズ
suggestWord=wordpressデザインレシピ集
suggestWord=wordpress 仕事の現場でサッと使える デザイン教科書
suggestWord=wordpress デザイン
suggestWord=wordpress レッスンブック

wikipedia


wikipediaは、他サービスとちょっと異なります。
実行プログラムは他のJSON形式と同一なのですが、結果にURLが格納されています。

※親切ですね


以下のサンプルプログラムでは、他結果との違いが分かりやすいように、キーワードを「wordPress」ではなく「japan」(結果がたくさん返ってくる)にしています。
また、空欄の結果文字列は除外しています。

try {
    $searchWord = "japan";
    $url = "https://ja.wikipedia.org/w/api.php?action=opensearch&format=json&search=" . $searchWord;
    $result = file_get_contents($url);
    if (!isset($result)) {
        echo "not result.";
        exit;
    }
    $result = json_decode($result, true);
    for ($lc = 0; $lc < count($result); $lc++) {
        if (is_array($result[$lc])) {
            $searchWord = $result[$lc];
            for ($slc = 0; 
                $slc < count($searchWord); $slc++) {
                if (is_array($searchWord[$slc])) {
                    continue;
                }
                if (strlen($searchWord[$slc]) == 0) {
                    continue;
                }
                echo "suggestWord=" . 
                    $searchWord[$slc] . "
"; } } } } catch (Exception $e) { echo "message=" . $e->getMessage() . "
"; exit; }

実行結果

suggestWord=Japan
suggestWord=JAPAN COUNTDOWN
suggestWord=JAPAN FM LEAGUE
suggestWord=Japan Expo
suggestWord=JAPAN (アルバム)
suggestWord=JAPANサッカーカレッジ
suggestWord=JAPAN JAM
suggestWord=JAPANNEXT
suggestWord=JAPANARIZM
suggestWord=Japan Skeptics
suggestWord=https://ja.wikipedia.org/wiki/Japan
suggestWord=https://ja.wikipedia.org/wiki/JAPAN_COUNTDOWN
suggestWord=https://ja.wikipedia.org/wiki/JAPAN_FM_LEAGUE
suggestWord=https://ja.wikipedia.org/wiki/Japan_Expo
suggestWord=https://ja.wikipedia.org/wiki/JAPAN_(%E3%82%A2%E3%83%AB%E3%83%90%E3%83%A0)
suggestWord=https://ja.wikipedia.org/wiki/JAPAN%E3%82%B5%E3%83%83%E3%82%AB%E3%83%BC%E3%82%AB%E3%83%AC%E3%83%83%E3%82%B8
suggestWord=https://ja.wikipedia.org/wiki/JAPAN_JAM
suggestWord=https://ja.wikipedia.org/wiki/JAPANNEXT
suggestWord=https://ja.wikipedia.org/wiki/JAPANARIZM
suggestWord=https://ja.wikipedia.org/wiki/Japan_Skeptics


WordPressプラグインの開発で発生し易いエラーと対処方法

WordPress

WordPressプラグインを開発していく中で、何かしらエラーが発生して悩んでしまう場合があるかと思います。
特にWordPressプラグインの開発を始めたばかりの人の場合、ハマって場合もあります。


そんな人むけに、WordPressプラグインの開発を始めたばかりの人がハマりやすいエラーについて、原因と対処方法を説明します。


環境情報


  • WordPress 5.4.1

文字化け


一番最初に陥り易いエラーです。


WordPressのダッシュボードで、開発しているプラグインの説明分が文字化けしてしまう場合があります。


WordPressプラグインの有効化の時に文字化け

文字化けしてしまう原因は文字コードです。
WordPressの文字コードは「UTF-8」なのですが、開発しているプラグインのPHPファイルが他の文字コードになっている事が原因です。


PHPファイルの文字コードを「UTF-8」に変更して保存すれば、文字化けは解消されます。


WordPressプラグインの有効化の時に文字化けの解消

プラグインの有効化中に”N”文字の予期しない出力が生成されました


プラグインの有効化をおこなうタイミングで発生するエラーになります。
これは、PHPプログラムの先頭、もしくは、末尾に、余計な改行や空白が存在する場合に発生します。


WordPressプラグインの余分な改行

PHPプログラムの先頭や末尾には、余分な改行や空白は不要になります。
以下のように、末尾位置と改行位置は一致する必要があります。


WordPressプラグインの先頭
WordPressプラグインの末尾

重大なエラーを引き起こしたため、プラグインを有効化できませんでした


プラグインの有効化をおこなったタイミングでPHPファイルに文法エラーが存在すると、有効化に失敗します。


WordPressプラグインの有効化時syntaxエラー

通常のPHPエラーがダッシュボードに表示されているだけなので、エラー内容を確認した上で対処すればよいです。


上記サンプルのエラーでは、「;」(セミコロン)の記載をおこなっていないので、PHPのパースに失敗しています。
通常のPHPと同様に、エラー発生箇所に対して「;」を記載して、再度プラグイン有効化すれば正常に有効化がおこなわれます。


Apacheでクライアント認証をおこない、Javaプログラムで証明書情報を取得して解析

apache

Apacheを使ったWEBアプリケーションでSSL通信をおこなうためには、Apacheが認識するサーバのフォルダ上に証明書を格納し、Apacheの設定ファイルに証明書の情報を定義する必要があります。

かつ、より安全なSSL通信をおこなう方法は、クライアント認証をおこなう方法になります。

許可した端末のブラウザにクライアント証明書をインストールし、そのクライアント証明書がインストールされている端末についてのみSSL通信を許可します。


今回は、クライアント証明書を使ってSSL通信をおこなうための設定方法と、クライアント証明書から情報を抜き出すJavaプログラムについて説明します。


環境情報


  • HTTPサーバ:Apahce2.2.15
  • 言語:Java1.7.79

SSL認証の概要


クライアント証明書を用いたSSL通信は、通常のSSL通信と少し異なります。

SSL通信をおこなう端末のブラウザにクライアント証明書をインストールしておき、通信においてブラウザのクライアント証明書と、サーバ側のルート証明書とで証明書のマッチング認証をおこない、同証明書であればSSL通信を許可します。



上記のクライアント証明書を用いたSSL通信をおこなうためには、ブラウザへのクライアント証明書のインストールと、Apacheへの証明書設定の2つが必要になります。


Apacheへの証明書設定(サーバ側)


サーバ側の証明書設定は、通常、証明書発行局が作成した証明書をサーバ上に指定フォルダに格納し、Apacheの設定ファイルに証明書のパスを設定します。
OpenSSLを使って独自で証明書を作成した場合でも設定方法は同様です。


設定するファイルは「ssl.conf」になります。
設定箇所は以下の4つです。


SSLCertificateFile

サーバ証明書の格納パス

SSLCertificateKeyFile

秘密鍵の格納パス

SSLCertificateChainFile

サーバー証明書の中間証明書格納パス

SSLCACertificateFile

クライアント証明書のルート証明書格納パス


# サーバー証明書
SSLCertificateFile /etc/httpd/cert/server.crt

# 秘密鍵
SSLCertificateKeyFile /etc/httpd/cert/secret.key

# サーバー証明書における中間証明書
SSLCertificateChainFile /etc/httpd/cert/publicKey.cer

# クライアント証明書におけるルート証明書
SSLCACertificateFile /etc/httpd/cert/root.pem

設定する証明書によっては、証明書の形式も意識する必要があります。
例えば、クライアント証明書におけるルート証明書(SSLCACertificateFile)は、テキスト形式でなくてはなりません。

Apacheのバージョンによるかもしれないのですが、少なくとも「Apahce2.2.15」ではバイナリ形式のルート証明書は認識できません。


Apacheのエラーログに、以下のログが出力されてApacheの起動に失敗します。


[Thu Mon DD HH:MI:SS YYYY] [info] SSL Library Error: 336105671error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not  return a certificate No CAs known to server for verification? 

証明書発行局からテキスト形式の証明書を発行してもらうことが一番よいのですが、OpenSSLコマンドを用いてバイナリからテキスト形式への変更も可能です。


openssl x509 -inform der -in root.cer -outform pem -out root.pem 

ブラウザへの証明書インストール(クライアント側)


ブラウザにインストールするクライアント証明書も、サーバ証明書と同様に証明書発行局が作成した証明書をインストールします。

こちらも基本的には、ウィザードに沿ってブラウザに証明書をインストールしていきます。


証明書ファイル(.der、など)をクリックすると、以下のようなインストールウィザードがひらきます。

表示内容を確認し、OKボタンを押下します。



保管場所を選択します。

通常であれば「現在のユーザ」でよいです。

選択したら次へボタンを押下します。



証明書ストアを設定します。

何かしらの理由がなければ、自動ストアを選択して、次へボタンを押下します。


以下のウィンドウが表示されれば、無事にクライアント証明書のインストール完了です。



インストールが完了したら、ブラウザの設定ウィンドウをひらいてインストール結果を確認します。

Edge(Chromium版)での確認方法は、設定ウィンドウをひらき、「プライバシーとサービス」→「証明書の管理」で証明書情報を確認できます。



「個人」タブにインストールした証明書が表示されていれば、正常にクライアント証明書が認識されているという事になります。


証明書から情報を取得


クライアント証明書を使ったSSL通信において、WEBアプリケーション側でクライアント証明書を情報を取得する事も可能です。
具体的には、JavaプログラムでHTTPリクエストから抽出します。


以下のプログラムでは、java.security.cert.X509Certificateを使って、HttpServletRequestから識別情報を取得しています。


private void getCert(HttpServletRequest request) {

    // -- クライアント証明書情報の取得 --//
    java.security.cert.X509Certificate[] certs = 
        (java.security.cert.X509Certificate[]) request.getAttribute(
        "javax.servlet.request.X509Certificate");

    // クライアント証明書からクライアント証明書識別子を取得
    Principal principal = certs[0].getSubjectX500Principal();

    // クライアント証明書識別子を取得
    X509Name name = new X509Name(principal.getName());
    Vector<?> cert = 
        name.getValues(X509ObjectIdentifiers.commonName);
    if (cert.size() == 1) {
        String code = cert.get(0);
    }

上記のプログラムで、クライアント証明書に格納されている「commonName」を取得することができます。


上記プログラムの用途としては、インストールされているクライアント証明書に格納されている情報(commonName)毎にアプリケーションの制御を変える場合です。
クライアント証明書Aをインストールしているブラウザからログインしたユーザと、クライアント証明書Bをインストールしているブラウザからログインしたユーザとで、上記で取得した情報をもとに制御の切替が可能となります。



WordPressプラグインの開発。コントローラを使って設定を保存する方法

WordPress

WordPressプラグインを開発する際、設定画面でさまざまコントローラを使う場面がでてきます。
テキストボックス・セレクトボックス・ラジオボタン、いろいろあります。


今回は、各コントローラを設定画面で使う場面を想定した際の実装方法について紹介します。


環境情報


  • WordPress 5.1.6

基本的なプログラム


WordPressプラグインを開発する場合、基本的なお作法があります。
今回の各コントローラを扱うプラグインプログラムについても、この部分は変わりません。


<?php
/*
  Plugin Name: ControlSample
  Plugin URI:https://sakusaku-techs.com/
  Description: 設定ウィンドウでのコントロールサンプル
  Version: 1.0.0
  Author: SakuSaku
  Author URI: https://sakusaku-techs.com/
  License: GPLv2
 */


/**
1:初期アクションの定義
*/
add_action('init', 'ControlSample::init');

class ControlSample {

    /**
    2:プラグイン定数の定義
    */
    const PLUGIN_ID         = 'control-sample';
    const SAMPLE_ACTION     = self::PLUGIN_ID . '-nonce-action';
    const SAMPLE_NAME       = self::PLUGIN_ID . '-nonce-key';
    const SAMPLE_DB_PREFIX  = self::PLUGIN_ID . '_';
    const SAMPLE_MENU_SLUG  = self::PLUGIN_ID . '-config';
    const COMPLETE_CONFIG  = 'update-date-complete';

    /**
    初期化処理
    */
    static function init() {
        return new self();
    }

    /**
    コンストラクタ
    */
    function __construct() {
        /**
        3:アクションの定義
        */
        if (is_admin() && is_user_logged_in()) {
            add_action('admin_menu', 
                [$this, 'set_controlSample_menu']);
            add_action('admin_menu', 
                [$this, 'set_controlSample_sub_menu']);
            add_action('admin_init', 
                [$this, 'save_config']);
        }
    }

    /**
    コントロールサンプルのメニュー追加
    */
    function set_controlSample_menu() {
        /**
        4:メニューの追加
        */
        add_menu_page(
            'コントロールサンプル',
            'コントロールサンプル',
            'manage_options', 
            'sample-index-slag',
            [$this, 'show_about_plugin'],
            'dashicons-format-gallery', 
            99
        );
    }

    /**
    コントロールサンプルのサブメニュー追加
    */
    function set_controlSample_sub_menu() {
        /**
        5:サブメニューの追加
        */
        add_submenu_page(
            'sample-index-slag', 
            '設定',
            '設定',
            'manage_options',
            'custom-index-banner-config',
            [$this, 'show_config_form']);
    }

    /**
    設定画面の項目データベースに保存する
    */
    function save_config() {
        if (isset($_POST[self::SAMPLE_NAME]) && 
            $_POST[self::SAMPLE_NAME]) {
            if (check_admin_referer(
                self::SAMPLE_ACTION, self::SAMPLE_NAME)) {
            /**
            6:データベースへの保存
            */
            }
        }
    }

    /**
    設定ウィンドウの表示
    */
    function show_config_form() {
        /**
        7:データベースからのデータ取得と画面表示
        */
    }
}
?>

1~5のプログラムは、WordPressプラグイン開発においては基本的な部分です。
各コントローラの表示と保存をおこなうために、6と7にプログラムとHTMLの記載をおこないます。


各コントローラのサンプルプログラム


WordPressプラグインの開発において、設定画面を設ける場合が多いかと思います。
その場合、何かしらの設定値を画面に入力して、WordPressのDBに保存する必要があります。


想定されるコントローラとして、以下のコントローラについてサンプルプログラムを紹介します。


  • テキストボックス
  • テキストエリア
  • セレクトボックス(プルダウン)
  • ラジオボタン
  • カラーピッカー

テキストボックス


テキストボックスは、全てのコントローラに対するプログラムの基本です。


保存メソッドである「save_config()」においてテキストボックスの内容をWordPressのDBに保存しています。
WordPressのDBに保存する際にキー値が必要となりますが、先頭にプレフィックスとして「self::SAMPLE_DB_PREFIX」を付加しているのがポイントです。
このプレフィックスの値が、本プラグインの値となります。


実際に「show_config_form()」において、このプレフィックスを使用してWordPressのDBから値を取得しています。



/**
設定画面の項目データベースに保存する
*/
function save_config() {
    if (isset($_POST[self::SAMPLE_NAME]) && 
        $_POST[self::SAMPLE_NAME]) {
        if (check_admin_referer(
            self::SAMPLE_ACTION, self::SAMPLE_NAME)) {
            update_option(self::SAMPLE_DB_PREFIX . 
                "_textbox", $_POST['prefix']);
            $completed_text = "設定の保存が完了しました。";
            set_transient(
                self::COMPLETE_CONFIG, $completed_text, 5);
            wp_safe_redirect(
                menu_page_url(self::SAMPLE_MENU_SLUG), false);
        }
    }
}

/**
設定ウィンドウの表示
*/
function show_config_form() {
    $textbox = 
        get_option(self::SAMPLE_DB_PREFIX . "_textbox");
?>
<div class="wrap">
  <h1>カスタムバナーの設定</h1>
  <form action="" method='post' id="my-submenu-form">
      <?php wp_nonce_field(
        self::SAMPLE_ACTION, self::SAMPLE_NAME) ?>
      
        <label for="prefix">テキストボックス:</label>
        <input type="text" name="prefix" value="<?= $textbox ?>"/>
      
      
        <input type='submit' value='保存' class='button button-primary button-large'>
      
  </form>
</div>
<?php
}

テキストエリア


テキストエリアは、テキストボックスとほぼ同等となります。

保存メソッドである「save_config()」にテキストエリアの値を保存し、表示メソッドである「show_config_form()」にテキストエリアの値の取得とHTMLへの表示をおこなっています。



/**
設定画面の項目データベースに保存する
*/
function save_config() {
    if (isset($_POST[self::SAMPLE_NAME]) && 
        $_POST[self::SAMPLE_NAME]) {
        if (check_admin_referer(
            self::SAMPLE_ACTION, self::SAMPLE_NAME)) {
            update_option(self::SAMPLE_DB_PREFIX . 
                "_textarea", $_POST['prefix_area']);
            $completed_text = "設定の保存が完了しました。";
            set_transient(
                self::COMPLETE_CONFIG, $completed_text, 5);
            wp_safe_redirect(
                menu_page_url(self::SAMPLE_MENU_SLUG), false);
        }
    }
}

/**
設定ウィンドウの表示
*/
function show_config_form() {
    $textbox = get_option(self::SAMPLE_DB_PREFIX . "_textbox");
?>
<div class="wrap">
  <h1>カスタムバナーの設定</h1>
  <form action="" method='post' id="my-submenu-form">
      <?php wp_nonce_field(
          self::SAMPLE_ACTION, self::SAMPLE_NAME) ?>
      
        <label for="prefix">テキストエリア:</label>
        <textarea rows="10" cols="60" name="prefix_area">
            <?= $textArea ?>
        </textarea>
      
      
<input type='submit' value='保存' class='button button-primary button-large'>
  </form>
</div>
<?php
}

セレクトボックス(プルダウン)


セレクトボックス(プルダウンメニュー)も、テキストボックスとテキストエリアとほぼ同一なのですが、HTMLの構成が異なりますので、おのずとプログラムも変わってきます。
セレクトボックスで選択された値についてのみ、WordPressのDBに保存をおこない、HTMLに反映をおこなっています。



/**
設定画面の項目データベースに保存する
*/
function save_config() {
    if (isset($_POST[self::SAMPLE_NAME]) && 
        $_POST[self::SAMPLE_NAME]) {
        if (check_admin_referer(
            self::SAMPLE_ACTION, self::SAMPLE_NAME)) {
            update_option(self::SAMPLE_DB_PREFIX . 
                "selectbox", $_POST['siteoption_select1']);
            $completed_text = "設定の保存が完了しました。";
            set_transient(
                self::COMPLETE_CONFIG, $completed_text, 5);
            wp_safe_redirect(
                menu_page_url(self::SAMPLE_MENU_SLUG), false);
        }
    }
}

/**
設定ウィンドウの表示
*/
function show_config_form() {
    $textbox = get_option(
        self::SAMPLE_DB_PREFIX . "_textbox");
?>
<div class="wrap">
  <h1>カスタムバナーの設定</h1>
  <form action="" method='post' id="my-submenu-form">
      <?php wp_nonce_field(
          self::SAMPLE_ACTION, self::SAMPLE_NAME) ?>
      
        <label for="prefix">セレクトボックス:</label>
          <select name="siteoption_select1" id="siteoption_select1">
              <option value="0" <?php selected(0, 
                get_option(self::SAMPLE_DB_PREFIX . "selectbox"));
              ?> >
                値0:デフォルト
              </option>
              <option value="1" <?php selected(1, 
                get_option(self::SAMPLE_DB_PREFIX . "selectbox"));
              ?> >
                値1
                </option>
              <option value="2" <?php selected(2, 
                get_option(self::SAMPLE_DB_PREFIX . "selectbox"));
              ?> >
                値2
                </option>
              <option value="3" <?php selected(3, 
                get_option(self::SAMPLE_DB_PREFIX . "selectbox" ));
              ?> >
                値3
                </option>
          </select>
      
      
<input type='submit' value='保存' class='button button-primary button-large'>
  </form>
</div>
<?php
}

ラジオボタン


ラジオボタンは、セレクトボックスとほぼ同等となります。


保存メソッドである「save_config()」にラジオボタンで選択されたの値を保存し、表示メソッドである「show_config_form()」において選択内容の反映をおこなっています。



/**
設定画面の項目データベースに保存する
*/
function save_config() {
    if (isset($_POST[self::SAMPLE_NAME]) && 
        $_POST[self::SAMPLE_NAME]) {
        if (check_admin_referer(
            self::SAMPLE_ACTION, self::SAMPLE_NAME)) {
            update_option(self::SAMPLE_DB_PREFIX . 
                "option", $_POST['siteoption_radio1']);
            $completed_text = "設定の保存が完了しました。";
            set_transient(
                self::COMPLETE_CONFIG, $completed_text, 5);
            wp_safe_redirect(
                menu_page_url(self::SAMPLE_MENU_SLUG), false);
        }
    }
}

/**
設定ウィンドウの表示
*/
function show_config_form() {
    $textbox = get_option(
        self::SAMPLE_DB_PREFIX . "_textbox");
?>
<div class="wrap">
  <h1>カスタムバナーの設定</h1>
  <form action="" method='post' id="my-submenu-form">
      <?php wp_nonce_field(
        self::SAMPLE_ACTION, self::SAMPLE_NAME) ?>
      
        <label for="prefix">ラジオボタン:</label>
          <label>
            <input name="siteoption_radio1" type="radio" value="0" 
                <?php checked(0, get_option(
                    self::SAMPLE_DB_PREFIX . "option")); ?> />
                    値1</label>
          <label>
            <input name="siteoption_radio1" type="radio" value="1" 
                <?php checked(1, get_option(
                    self::SAMPLE_DB_PREFIX . "option")); ?> />
                    値2</label>
          <label>
            <input name="siteoption_radio1" type="radio" value="2" 
                <?php checked(2, get_option(
                    self::SAMPLE_DB_PREFIX . "option")); ?> />
                    値3</label>
          <label>
            <input name="siteoption_radio1" type="radio" value="3" 
                <?php checked(3, get_option(
                    self::SAMPLE_DB_PREFIX . "option")); ?> />
                    値4</label>
      
      
<input type='submit' value='保存' class='button button-primary button-large'>
  </form>
</div>
<?php
    }

カラーピッカー


カラーピッカーは、他コントローラとちょっと異なります。
色選択ウィンドウを表示する必要があるので、クライアント側の作りこみが必要があります。


色選択ウィンドウは、JQueryUIの「wpColorPicker」を使用します。
「wpColorPicker」を制御するJavaScriptを準備して、サーバ上のプラグインフォルダに保存しておきます。


plguinfolder --- control-sample.php
             |
             --- js
                 |
                 --- date.js

PHPでは、プラグインフォルダに保存しておいたJavaScriptを読み込むプログラムを実装しておきます。



JavaScript

(function( $ ) {
    var options = {
        defaultColor: false,
        change: function(event, ui){},
        clear: function() {},
        hide: true,
        palettes: true
    };
    $('.color-picker').wpColorPicker(options);
})( jQuery );

PHP

add_action( 'wp_enqueue_scripts', 'load_javascript_func' );

class ControlSample {

/**
設定画面の項目データベースに保存する
*/
function save_config() {
    if (isset($_POST[self::SAMPLE_NAME]) && 
        $_POST[self::SAMPLE_NAME]) {
        if (check_admin_referer(
            self::SAMPLE_ACTION, self::SAMPLE_NAME)) {
            update_option(
                self::SAMPLE_DB_PREFIX . "_color", $_POST['color']);
            $completed_text = "設定の保存が完了しました。";
            set_transient(
                self::COMPLETE_CONFIG, $completed_text, 5);
            wp_safe_redirect(
                menu_page_url(self::SAMPLE_MENU_SLUG), false);
        }
    }
}

/**
JavaScriptの読み込み
*/
function load_javascript_func() {
     wp_enqueue_script( "jQuery", includes_url().'/js/date.js' );
}

/**
設定ウィンドウの表示
*/
function show_config_form() {
?>
<div class="wrap">
  <h1>カスタムバナーの設定</h1>
  <form action="" method='post' id="my-submenu-form">
      <?php wp_nonce_field(
          self::SAMPLE_ACTION, self::SAMPLE_NAME) ?>
      
          <label for="prefix">カラーピッカー:</label>
          <input type="text" name="color" 
            class="color-picker" value="<?= $color ?>" >
      
      
        <input type='submit' value='保存' class='button button-primary button-large'>
      
  </form>
</div>
<?php
    }
}
?>

カラーピッカーに関わらず、JavaScriptでクライアント側の制御をおこないたい場合は、上記の方法で機能を実現することが通常の方法になります。



Javaで配列をコピーする。シャーロンコピーとディープコピーの違いとは?

Java

Javaで配列をコピーする際、気を付けなければいけないポイントがあります。
シャーロンコピーとディープコピーの違いです。


この2つの挙動の違いを把握しておかないと、思わぬバグを埋め込んでしまうことなります。


今回は、シャーロンコピーとディープコピーの違いを、サンプルプラグラムをもとに説明していきます。


環境情報


  • OS:Windows10
  • Java:1.8.0_60

シャーロンコピーとは?ディープコピーとは?


シャーロンコピーとディープコピーとは何でしょうか?

まずは、この2つのコピーの違いについて説明します。


シャーロンコピー


C言語を経験した人の場合は、アドレスコピーときくとピンとくるかと思います。
要素を指定するアドレスをポインタを使用してコピーしており、要素がもつ値はコピーしていないです。


ディープコピー


値コピー、と言い換えることができます。
シャーロンコピーとは異なり、アドレスではなく、要素そのものをコピーします。


シャーロンコピーの概念が理解し辛い人が多い気がします。
C言語を使った人はわかるかと思いますが、「ポインタ」のイメージですね。


シャーロンコピーとディープコピーのサンプルプログラム


それでは、さっそくですがサンプルプログラムです。

例として、以下のサンプルプログラムになります。

  • コピー対象は、独自エントリクラス(Entry)の配列を「ArrayList」で保持する。
  • シャーロンコピー→ディープコピーの順番でコピーを実施。
  • コピー元配列の内容を表示。

サンプルプログラム


作成するクラスは2つになります。
「CopyTest.java」がコピーするメインクラス、「Entry.java」が配列要素となるエントリクラスです。


CopyTest.java

import java.util.ArrayList;

public class CopyTest {

    // メイン処理
    public static void main(String[] args){
        System.out.print("start: CopyTest\r\n");

        /**
        ①:エントリ内容の作成と確認
        */
        ArrayList<Entry> srcList = new ArrayList<Entry>();
        Entry newEnt1 = new Entry();
        newEnt1.setSei("Yamada");
        newEnt1.setNa("Taro");
        srcList.add(newEnt1);
        Entry newEnt2 = new Entry();
        newEnt2.setSei("Suzuki");
        newEnt2.setNa("jiro");
        srcList.add(newEnt2);
        System.out.print("エントリ内容の作成確認\r\n");
        dispList(srcList);

        /**
        ②:シャーロンコピー
        */
        ArrayList<Entry> shtList = srcList;
        Entry entry = shtList.get(0);
        entry.setSei("Satou");
        entry.setNa("Saburo");
        System.out.print("リストをコピー確認\r\n");
        dispList(srcList);

        /**
        ③:ディープコピー
        */
        ArrayList<Entry> deList = 
            new ArrayList<Entry>(srcList.size());
        for (Entry ent : srcList) {
            deList.add(ent.clone());
        }
        entry = deList.get(0);
        entry.setSei("Tanaka");
        entry.setNa("Shiro");
        System.out.print("リストをコピー確認\r\n");
        dispList(srcList);

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

    private static void dispList(ArrayList<Entry> dispList){
        for (Entry dispEnt : dispList) {
            System.out.print("sei = " + dispEnt.getSei() + "\r\n");
            System.out.print("na = " + dispEnt.getNa() + "\r\n");
        }
    }
}

Entry.java

public class Entry implements Cloneable {
    private String sei;
    private String na;

    // 姓の設定
    public void setSei(String sei) {
        this.sei = sei;
    }

    // 名の設定
    public void setNa(String na) {
        this.na = na;
    }

    // 姓の取得
    public String getSei() {
        return this.sei;
    }

    // 名の取得
    public String getNa() {
        return this.na;
    }

    // クーロン
    public Entry clone() {
        try {
            return (Entry) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

シャーロンコピーの実行結果


サンプルプログラムの②の部分がシャーロンコピーのサンプルプログラムです。
シャーロンコピーを実行した結果は以下になります。

sei = Satou
na = Saburo
sei = Suzuki
na = jiro

実際にシャーロンコピーしている部分は以下です。

ArrayList<Entry> shtList = srcList;

シャーロンコピーしたコピー先リスト(shtList)の内容を変更しています。
エントリの1つ目について、以下のように変更しています。

・Sei=Satou 
・Na=Saburo

シャーロンコピーしたコピー先リスト(shtList)を変更したにも関わらず、コピー元のリスト(srcList)が変更されています。
これはなぜかというと、コピーしたのはあくまでアドレスであるシャーロンコピーをおこなっているからになります。
変更先リストを変更しているにも関わらず変更元リストが変更されている、という事です。


ディープコピーの実行結果


サンプルプログラムの③の部分がディープコピーのサンプルプログラムです。
ディープコピーを実行した結果は以下になります。

sei = Satou
na = Saburo
sei = Suzuki
na = jiro
end: CopyTest

実際のディープコピーは、「Entry」クラスのcloneメソッドになります。

ArrayList<Entry> deList = new ArrayList<Entry>(srcList.size());
for (Entry ent : srcList) {
    deList.add(ent.clone());
}

要素を完全にコピーしています。
完全にコピーした要素に対して変更しているので、変更元の配列要素は変更されていないです。


これがディープコピーになります。
通常は、上記のようにcloneメソッドを準備して、要素を完全コピーする実装を準備しておきます。


よく、様々なウェブサイトで以下のように実装すればディープコピーされると記載されているのですが、それは間違いです。
気を付けてください。

ArrayList<Entry> deList = new ArrayList<Entry>(srcList);

まとめ


ディープコピーとシャーロンコピーの違いを正しく理解してプログラミングしましょう!


よくあるのが、ディープコピーしていると思っていたがシャーロンコピーで、コピー元の要素が変更されてしまった!というパターンです。
思わぬバグを作りこんでしまう事になります。



FTPサーバとの通信をJavaでおこなうサンプルプログラム

Java

システムで外部システムと連携する方法はいろいろありますが、FTPサーバを使ってファイルのやり取りで連携するパターンも多々あります。


FTPサーバを使ってファイルのやり取りをおこなう場合、プログラムからFTPサーバにログインして、ファイルのアップロードやダウンロードをおこなう必要が出てきます。


今回は、Javaを使ってFTPサーバとのファイル送受信をおこなうサンプルプログラムを紹介します。


環境情報


  • OS:Windows10
  • Java:Java1.8.0_60
  • FTPサーバ:IIS(インターネットインフォメーションサービス)

JavaでFTPを使うために、「commons-net-3.6.jar」を使用しています。


FTPサーバについては、以前の記事で作成したIIS(インターネットインフォメーションサービス)を使います。



FTPサンプルプログラム


サンプルプログラムを紹介します。
サンプルプログラムのクラスは2つです。


FTPに対する操作をおこなうクラスとして「FtpMng.java」を準備し、「FtpMng.java」のインスタンスを使って処理をおこなうクラスが「FtpSample.java」になります。


以下のクラスがFTPに対する操作をおこなうクラスである「FtpMng.java」です。
FTPに対する操作一つ一つに対してメソッドを準備しています。


FTPサーバに対しておこなっているのは、ファイルのアップロード(FTPサイトにファイルを置く)と、ファイルのダウンロード(FTPサイトからファイルをもらう)の2つになります。

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.SocketException;

public class FtpMng {

    private FTPClient   cli;

    // コンストラクタ
    public FtpMng() {
    }

    // 接続
    public boolean connect() {
        int rep;

        System.out.print("start: connect\r\n");
        try {
            cli = new FTPClient();
            cli.setDataTimeout(60000);
            cli.connect("localhost");
            rep = cli.getReplyCode();
            if (!FTPReply.isPositiveCompletion(rep)) {
                return false;
            } 
            System.out.print("end: connect\r\n");
            return true;
        } catch (SocketException e) {
            return false;
        } catch (IOException ie) {
            return false;
        }
    }

    // ログイン
    public boolean login() {
        System.out.print("start: login\r\n");
        try {
            if (!cli.login("ftp", "password")) {
                return false;
            }
            System.out.print("end: login\r\n");
            return true;
        } catch (IOException ie) {
            return false;
        }
    }

    // ダウンロード
    public void downLoad() {
        FileOutputStream    outputstream;
        boolean             isRetrieve;

        System.out.print("start: downLoad\r\n");
        try {
            outputstream =
                new FileOutputStream("download.txt");
            isRetrieve =
               cli.retrieveFile("download.txt", outputstream);
            outputstream.close();
            if (!isRetrieve) {
                System.out.print("error: downLoad\r\n");
            }
            System.out.print("end: downLoad\r\n");
            return;
        } catch (IOException ie) { 
            return;
        }
    }

    // アップロード
    public void upLoad() {
        FileInputStream     inputstream;
        boolean             isStore;

        System.out.print("start: upLoad\r\n");
        try {
            inputstream =
                new FileInputStream("upload.txt");
            isStore =
                cli.storeFile("upload.txt",
                inputstream);
            inputstream.close();
            if (!isStore) {
                System.out.print("error: upLoad\r\n");
            } 
            System.out.print("end: upLoad\r\n");
            return;
        } catch (IOException ie) {
            return;
        }
    }

    // 切断
    public boolean disConnect() {

        System.out.print("start: disConnect\r\n");
        try {
            if (cli != null && cli.isConnected()) {
                cli.disconnect(); 
            }
            System.out.print("end: disConnect\r\n");
            return true;
        } catch (IOException ie) {
            return false;
        }
    }
}

以下のクラスが実際にFTP処理をおこなう「FtpSample.java」です。
「FtpMng.java」に準備しているメソッドを順番に呼び出しているだけです。

public class FtpSample {

    public static void main(String[] args){
        System.out.print("start: FtpSample\r\n");
        FtpMng ftpClient = new FtpMng();

        // 1:接続
        ftpClient.connect();

        // 2:ログイン
        ftpClient.login();

        // 3:ダウンロード
        ftpClient.downLoad();

        // 4:アップロード
        ftpClient.upLoad();

        // 5:切断
        ftpClient.disConnect();

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

FTPサンプルプログラムの解説


それでは以下に解説していきます。
そんなに難しいプログラムではないですが。


接続・ログイン・切断


FTPサーバに対してファイルをアップロード・ダウンロードする前に、FTPサーバにログインしなければいけません。
手順としては、接続→ログイン、の順番になります。


まずは接続です。

cli = new FTPClient();
cli.setDataTimeout(60000);
cli.connect("localhost");
rep = cli.getReplyCode();
if (!FTPReply.isPositiveCompletion(rep)) {
    return false;
} 

正常に接続できたかどうか?については、getReplyCode()で返り値を取得し、FTPReplyクラスで返り値を検証することで判断することができます。


次にログインです。

if (!cli.login("ftp", "password")) {
    return false;
}

IDとパスワードを指定してログインを実行です。
これでFTPサーバにログインできます。


ID ftp
パスワード password

一連の処理をおこなった後に切断して処理終了です。

if (cli != null && cli.isConnected()) {
    cli.disconnect(); 
}

サンプルプログラムでは、一応、FTPClientインスタンスが存在しており、かつ、接続されている場合に切断しています。


ダウンロード


FTPサーバからファイルを取得します。

outputstream =
    new FileOutputStream("download.txt");
isRetrieve =
    cli.retrieveFile("download.txt", outputstream);
outputstream.close();

Javaのアウトプットストリームクラスを使用してFTPサーバからファイルをダウンロードしています。
ストリームのクローズは忘れずに、ですね。


アップロード


FTPサーバにファイルを格納します。


inputstream =
    new FileInputStream("upload.txt");
isStore =
    cli.storeFile("upload.txt",
        inputstream);
inputstream.close();

こちらはダウンロードとは逆の方式です。
Javaのインプットストリームクラスを使用してFTPサーバにファイルをアップロードしています。
こちらも、ストリームのクローズは忘れずに、です。


まとめ


FTPサーバに対するファイル操作は、Javaで実装することが可能です。
サンプルプログラムでは必要最低限のソースコードしか紹介していませんが、FTPコマンドで可能な操作はJavaが全て可能です。


まとめ
  • FTPサーバに対する操作は、Javaで全て実装可能。
  • サンプル以外のFTP操作についても可能



現場で使えるtailコマンド。ログ監視で必要なオプションなど。

Linux

運用・保守の業務をおこなっている人にとっては、とても馴染みがあるコマンドのtailコマンドについてまとめてみます。


筆者が一番使うタイミングは、やっぱりログ監視です。

tailは「尻尾」「末尾」の略ですが、tailコマンドを使えばファイルの終端を常に表示するので、稼働中システムのログを監視する際にとても便利です。


基本動作


tail ※ファイル名 で、ファイルの末尾を表示することができます。


$ tail file1.txt
テスト8
テスト9
テスト10

追記を監視(ラージFとスモールf)


基本動作の tail ※ファイル名 だけでは、ファイルが追記されいったとしてもコマンド上に追記内容が表示されません。


しかしこれだと、例えばアプリケーションのログ監視をおこなう場合に不便です。
アプリケーションがログに追記するたびに表示内容を更新する(常に末尾表示)ためには、-f オプションを使用します。


$ tail -f file1.txt
テスト8
テスト9
テスト10
$ tail -f file1.txt
テスト9
テスト10
テスト11 ← 追記されている。

上記は-f(スモールf)を使っていますが、ログ監視のことを考えると不便な事があります。
それはログのローテーションです。


指定しているファイルがリネームされて新しいファイル名となり、指定しているファイルが新規ファイルとなった場合、-f(スモールf)では末尾監視がおこなわれません。


tailコマンドで監視できない

ログローテーションのように、監視対象のファイルが新しく作成された場合も監視した場合は、-f(スモールf)ではなく-F(ラージF)を使います。
-F(ラージF)を使う事で、ファイル名が変わって監視対象ファイルが新規作成されたとしても、対象ファイルは監視が継続されます。


$ tail -F file1.txt
tail: `file1.txt' has become inaccessible: No such file or directory
tail: `file1.txt' has appeared;  following end of new file
tail: file1.txt: file truncated
ファイル1
ファイル2
ファイル3

行数を指定する(-n)


末尾の表示する行数を指定示することも可能です。


$ tail -n3 file1.txt
ファイル9
ファイル10
ファイル11

-fオプションを指定して、末尾N行を監視する、といったことも可能です。


$ tail -3f file1.txt
ファイル9
ファイル10
ファイル11

パイプで繋げてgrepで抽出


特にtailコマンドのオプションではないのですが、大量にログを出力するアプリケーションであれば、-fオプションを指定していてログを監視していてもログが流れるのが早くて目で追えないです。

その場合は、監視しておきたいキーワードを事前に決めておき、パイプ(|)で繋いでgrepコマンドで抽出するようにしておきます。


$ tail -f file1.txt | grep ※キーワード
ファイル12

上記のようにパイプで繋いでgrepを指定することで、grepで指定したキーワードが出現した時だけ表示してくれます。

とても効率的なログ監視をおこなうことができます。


まとめ


現場のエンジニアにとって、tailコマンドは必須のコマンドかと思います。
上手に使いこなして、日々の作業に役立ててください。




Windows10でFTPサーバを構築して、DOSコマンドで動作確認までおこなう。

FTP

FTPサーバを使った開発をおこなう場合、各開発メンバそれぞれにFTPサーバを準備するのは骨が折れる作業です。
そういった場合は、Windows10で標準的に使えるFTPサーバを使用する方法が一般的と言えます。


今回は、Windows10上でFTPサーバを構築する方法と、動作確認方法まで紹介していきます。


環境情報


  • Windows10

IISを使ってFTPサーバを構築する


IISはInternet Infomation Service(インターネット インフォメーション サービス)の略で、Windowsに標準装備されているサーバサービス群になります。
今回は、このIISを使ってFTPサーバを構築します。


IISを有効にする


IISはWindows10に標準搭載されていますが、デフォルトでは無効になっています。
まずはこれを有効にして、IISを使用できるようにします。


「すべてのコントロールパネル項目」→「プログラムと機能」から、『Windowsの機能の有効化または無効化』をクリックして、Windowsの機能ウィンドウをひらきます。


Windowsの機能の有効化または無効化

Windowsの機能ウィンドウにおいて、インターネットインフォメーションサービスの「FTP Service」と「IIS 管理コンソール」の2つをチェックし、OKボタンを押下して終了します。


FTP Service」と「IIS 管理コンソール」

変更の適用が始まりますので、しばらく待ちます。


変更の適用

「変更が完了しました」のメッセージが表示されれば、IISの有効化は完了です。



IISが有効化されたかを確認してみましょう。
Windowsのプログラムバーに「インターネットインフォメーションサービス(IIS)マネージャー」が表示されていれば、IISの有効化は成功です。


インターネットインフォメーションサービス(IIS)の有効化完了

FTPユーザを作成する


FTPのユーザは、Windowsのユーザとして準備する必要があります。
管理ツール → コンピュータの管理からユーザを選択し、新しいユーザを作成します。


今回は、以下の情報を入力しています。


ユーザ名 ftp
フルネーム FTPユーザ
説明 IIS接続用ユーザ
パスワード password

Windowsユーザの作成

作成ボタンを押下したら、ユーザが作成されていることを一覧で確認します。



FTPサイトの作成と設定


FTPサイトを作成し、ファイルの格納場所を確保します。


Windowsのプログラム検索でIISと入力するとIISが表示されるので、アプリケーションを選択してインターネットインフォメーションサービス(IIS)マネージャーを起動します。



接続ペーンで「サイト」を選択し、操作ペーンで「FTPサイトの追加」を選択します。



FTPサイトの追加ウィンドウが表示されます。


FTPサイト名は任意で構いません。
解りやすい名前をつける形でよいでしょう。
コンテンツディレクトリは、接続先のパスとなります。


今回は、以下の情報を入力しています。


FTPサイト情報の作成

FTPサイト名 FTP接続試験
物理パス D:\temp

入力が完了したら次へボタンを押下します。


バインドとSSLの設定ウィンドウに移ります。
バインドするIPアドレスはすべて「全て未割当」を選択します。


FTPサイトを自動的に接続するはチェックでよいです。


SSLは無しにします。


入力が完了したら次へボタンを押下します。


FTPサイトの追加

バインドとは、FTPでの接続を指定したIPでのみ許可する設定となります。


バインドに関しては、複数のNICでIPアドレスを複数所有していなければ、「全て未割当」でよいです。


認証情報の入力に進みます。


認証方法は匿名とし、匿名ユーザに対して読み取り・書き込みを許可するようにします。


入力が完了したら終了ボタンを押下します。



サイトの一覧に追加したサイトが表示されていれば、サイトの追加に成功です。



DOSコマンドを使って動作確認


FTPサーバで想定通りに構築できたかを確認します。
Windowsに標準搭載されているFTPコマンドを使って確認するのが一番早いです。


以下のFTPコマンドは、DOSプロンプトで実行します。


ftpモードにする(ftp)


ftpコマンドで、ftpモードに移行します。


D:\work>ftp
ftp>

サイトに接続する(open)


openコマンドでサイトに接続をおこないます。
「open ※ホスト名」の書式になります。


今回はローカルPC上でFTPサーバを構築しているので、ホスト名はlocalhostになります。


ftp> open localhost
DESKTOP-U7JE3FQ に接続しました。
220 Microsoft FTP Service
200 OPTS UTF8 command successful - UTF8 encoding now ON.

ユーザ名とパスワードを入力すればFTPサーバにログインできます。
最初の手順でWindowsユーザとして作成したユーザ名とパスワードを指定します。


ユーザー (DESKTOP-U7JE3FQ:(none)): ftp
331 Anonymous access allowed, send identity (e-mail name) as password.
パスワード:
230 User logged in.
ftp>

ローカルディレクトリの指定(lcd)


転送するファイルが格納されているディレクトリ(転送元)に移動します。


ftp> lcd d:\work
ローカル ディレクトリは現在 D:\work です。

リモートディレクトリの指定(cd)


転送先ディレクトリに移動します。


ftp> cd ./dest
250 CWD command successful.

現在のディレクトリ位置を確認(pwd)


cdで移動しましたが、意図したディレクトリに移動されているかを確認します。


ftp> pwd
257 "/dest" is current directory.

転送モードを指定(ascii bin)


転送モードはASCIIモード(ascii)とバイナリモード(bin)の二種類があります。
今回はファイルを転送しよう思いますので、asciiを指定します。


ftp> ascii
200 Type set to A.

ローカルディレクトリからFTPサーバへ転送する(put)


ファイルをFTPサイトにアップロードします。
アップロードはputコマンドを使用します。


ftp> put test.txt
200 EPRT command successful.
150 Opening data connection
226 Transfer complete.
ftp: 16 バイトが送信されました 0.01秒 1.60KB/秒。

「Transfer complete.」が表示されれば転送成功です。
実際に転送されているかは、FTPサーバをエクスプローラーで確認可能です。


FTPサイトをエクスプローラーで確認

FTPサーバからローカルディレクトリに転送する(get)


FTPサーバからファイルをダウンロードします。
ダウンロードはgetコマンドを使用します。


ftp> get test.txt
200 EPRT command successful.
125 Data connection already open; Transfer starting.
226 Transfer complete.
ftp: 16 バイトが受信されました 0.00秒 16000.00KB/秒。

「Transfer complete.」が表示されれば転送成功です。
ローカルディレクトリ(lcdで確認)にgetしたファイルが表示されていれば転送成功です。


FTPのコマンドを確認する(help)


この記事で紹介したコマンド以外にも、ftpコマンドは存在します。
全てのコマンドはhelpコマンドで確認できます。


ftp> help
コマンドは省略することができます。コマンド:

!               delete          literal         prompt          send
?               debug           ls              put             status
append          dir             mdelete         pwd             trace
ascii           disconnect      mdir            quit            type
bell            get             mget            quote           user
binary          glob            mkdir           recv            verbose
bye             hash            mls             remotehelp
cd              help            mput            rename
close           lcd             open            rmdir

まとめ


IISでFTPサーバを構築してから動作確認まで解説しました。


まとめ
  • IISはWindowsに標準搭載されているサーバサービス群
  • 構築後の動作確認はFTPコマンドで実施するのが早い

FTPコマンドを使用すると、実際のFTPの挙動を直感的に把握することもできるのでお勧めです。