Fusicきばんブログ

Fusic基盤ユニットの非公式技術ブログ

CakePHP3でエラーを自由自在に処理するencount

基盤ユニットリーダーの櫻川です( @kozo )。

さて今回はまたCakePHPに戻って、自分が作成しているencount プラグインについて書かせてもらいます。

encountはこちらになります。 github.com

encount 何が出来るの?

PHPが出力する NoticeWarningException といったエラーに対して、簡単にフック処理するを作ることが出来るプラグインになります。
例えば、本番環境にリリースしたサービスにencountを入れておくと、Noticeが出たタイミングでメール通知を行うといったことが可能となります。

また、 Senderクラス というencount専用のクラスを実装することで、エラー時の処理を自由にカスタマイズすることが可能です。
例えば、「Slackに通知する」や「NoticeやWarningはメールに通知するけど、FatalはSlackに通知する」といったようなことも可能となります。

※ Railsだと、 exception_notification が近い分類になると思われます。

encountのインストール

お決まりのcomposer経由にインストールになります。

composer require fusic/encount

encountの設定

ErrorHandler をCakeデフォルトのものから、encountのものへ切り替えます。
config/bootstrap.php で設定されてます。

// config/bootstrap.php
<?php

// shell
use Encount\Console\EncountConsoleErrorHandler;
(new EncountConsoleErrorHandler(Configure::read('Error')))->register();

// web
use Encount\Error\EncountErrorHandler;
(new EncountErrorHandler(Configure::read('Error')))->register();

encountはデフォルトの状態だと、Notice等何らかしらのエラーが発生したタイミングでメールを送信します。
その為、メール送信用の設定をします。

// config/app.php
<?php

return [

    // Errorを修正
    'Error' => [
        'errorLevel' => E_ALL,
        // exceptionRendererを追加
        'exceptionRenderer' => 'Cake\Error\ExceptionRenderer',
        'skipLog' => [],
        'log' => true,
        'trace' => true,
        // encount用設定を追加
        'encount' => [
            'force' => false,
            'sender' => ['Encount.Mail'],
            'mail' => [
                'prefix' => '',
                'html' => true
            ]
        ],
    ],

    // Emailにerrorを追加する
    'Email' => [
        // encount用設定を追加
        'error' => [
            'transport' => 'default',
            'from' => 'from@example.com',
            'to' => 'to@example.com'
        ]
    ],

];

encountの確認

デフォルトの設定では、 開発時はエラーを通知しないようになってます。
ですので、 debug を0に設定して、 echo $a['test'] というような存在しないキーにアクセスしてNoticeを発生後、メールが送信されれば設定OKです。

※開発時でも通知を行いたい場合は、 app.phpに先ほど設定した、 Error.encount.forcetrue に設定してください。

Senderを使って拡張する

さて、デフォルトの設定ではなく、通知を自作したい場合は、 Senderクラス を作成することになります。
例としてエラー内容を Slackで通知するSender を作成します。

1.SenderClassを作成する

src/Sender/Slack.php

<?php
namespace App\Sender;

use Encount\Sender\SenderInterface;

class Slack implements SenderInterface
{
    public function send($config, $code, $errorType, $description, $file, $line, $context)
    {
        // エラーが発生した時の処理をここに書く
        $url = '{{Incoming WebHooks URLを記載する}}';
        $data = array(
            'text' => $description,
        );
        $options = array(
            'http' => array(
            'method' => 'POST',
            'header' => 'Content-Type: application/json',
            'content' => json_encode($data),
            )
        );
        file_get_contents($url, false, stream_context_create($options));
    }
}

2. SlackSenderを使うように設定する

config/app.php

<?php
    'Error' => [
        'errorLevel' => E_ALL,
        'exceptionRenderer' => 'Cake\Error\ExceptionRenderer',
        'skipLog' => [],
        'log' => true,
        'trace' => true,
        'encount' => [
            'force' => true,
            // 作成した SlackSender に切り替える
            'sender' => ['Slack'],
            // Mail + Slack両方送信する場合は、以下のように2個並べる
            // 'sender' => ['Encount.Mail', 'Slack'],
            'mail' => [
                'prefix' => '',
                'html' => true
            ]
        ],
    ],

3. 動作確認

先ほどと同じく、echo $a['test'] というような存在しないキーにアクセスしてNoticeを発生してSlackが送信されればOKです。

最後に

気になった方はぜひ!encount使ってみてください!
そして、encountへの プルリク、Issue、Watch、Starよろしくお願い致します。

CentOS5/RHEL5系でSHA-2/TLS1.2のアウトバウンドのhttps通信を行う方法を検討してみる

基盤ユニットの貞方(@sadapon2008)です。

今回は刺さる人には刺さるCentOS5/RHEL5系でSHA-2/TLS1.2のアウトバウンドのhttps通信の実現方法を検討してみたいと思います。

背景

CentOS5/RHEL5系はすでにサポートが切れていますが、諸事情でまだ稼働しているシステムがあったりします。 CentOS5/RHEL5に含まれるOpenSSLは0.9.8eのため、curlなどOpenSSLをベースにアウトバウンドのhttps通信を行う場合、SHA-2やTLS1.2には未対応です(SHA-2はOpenSSL 0.9.8o以降、TLS1.2はOpenSSL 1.0.1以降が必要)。 しかし、世の中の流れでhttps通信で使うサーバ証明書の署名アルゴリズムはSHA-1からSHA-2への移行が進んでいて、 TLSのバージョンもAPIサービスによっては1.2でしか受け付けないものも出てきています。 そこで、CentOS5/RHEL5系で外部とSHA-2/TLS1.2のhttps通信を行う方法を検討してみました。

案1. 最新のOpenSSLをソースコードからコンパイルしてインストールする

最初に思いつくのは、最新のOpenSSLをソースコードからコンパイルしてインストールすることです。 ただし、ディストリビューションの標準パッケージのOpenSSLを上書きするようなことをするとOSがぶっ壊れる可能性が高いため、 標準パッケージと競合しないようにインストールしないと危険です。 その場合、コンパイルしてインストールしたOpenSSLでhttps通信するためには、curlなどOpenSSLを利用するプログラムも ソースコードからコンパイルしてインストールする必要があります。

また、PHPなどサーバサイドのアプリケーションなどでそれらを利用するには、アプリケーションのソースコードの大幅な変更も必要になります。

案2. 別途リバースプロキシサーバを用意する

CentOS5/RHEL5より新しいOpenSSLを利用できるCentOS7/RHEL7などでリバースプロキシサーバを用意して、そちら経由でhttps通信を行うことも考えられます。 例えば、「https://api.hoge.com」のURLでアクセスしていたものが、「http://(リーバスプロキシサーバのIPアドレス)」のURLでアクセスするように構成することが可能です。

この方法の場合、PHPなどサーバサイドのアプリケーションはアクセスするURLの修正だけで済むため、案1.に比べたら変更量を抑えられます。

ただし、別途サーバを準備する必要があります。

案3. CentOS5/RHEL5上でJava 1.7のリバースプロキシサーバを動作させる

CentOS5/RHEL5のディストリビューンの標準パッケージにはJava 1.7が含まれています。 Java 1.7自体もすでにサポートは切れていますが、実はJava 1.7はSHA-2/TLS1.2に対応していますので、Javaで動くリバースプロキシサーバを動かせば、別途サーバを用意する必要がありません。

Javaはずぶの素人なのですが、下記のGithubのコードを参考にJettyの組み込みプロキシを使うプログラムを作成してみました。

github.com

作成したものがこちらになります。

github.com

gradlewを使っていますので、以下のようすればビルドできると思います。

$ sudo yum install java-1.7.0-openjdk-devel
$ chmod a+x gradlew
$ ./gradlew fatJar

ビルドに成功したら、以下のようにコマンドを実行すると「http://127.0.0.1:8080」で待ち受けするリバースプロキシとして動作します。

$ java -jar ./build/libs/simple_reverse_proxy.jar https://api.hoge.com 8080

この状態で下記のようにするとSHA-2/TLS1.2のサーバに対してhttps通信を行うことができます。

$ curl http://127.0.0.1:8080/

正直Jettyの中身はまだよくわかっていないところもあるのですが、superviosrなどと組み合わせればデーモン化したりもできそうです。

jQuery.tooltipの「出力エリアをずれてしまう」の原因と解決

基盤ユニットのイです。
結構軽い記事になりますが、よろしくお願いします。 ( _ _ )

概要

皆さんは、tooltipをよく利用していますか?
tooltipとは、マウスを当該イメージ画面や特定の対象に持っておけば、当該対象に関する詳細説明が表示される機能でございます。

const IMG_MESSAGE = 'はじめまして!イと申します。';
$('.ldh-img').tooltip({
    position: { my: "left+15 center", at: "right center" },
    content: IMG_MESSAGE,
    track: false
});

f:id:ldhdba:20160923083402p:plain

通常、🔗jQuery.tooltip、もしくは🔗Bootstrapで、機能を導入しています。
しかし、その後、テストや運用から以下のような「出力エリアをずれてしまう」の結果のため、困るかもしれません。

const TEST_CASE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$('.ldh-img').tooltip({
    position: { my: "left+15 center", at: "right center" },
    content: TEST_CASE,
    track: false
});

f:id:ldhdba:20160923080948p:plain

ということで、こいう結果の原因と解決方法を話しさせて頂きます。

原因

まず、いくつかのケースの結果をご覧して、どのケースで出力エリアをずれてしまうのか、確認しましょう。

const TEST_CASE_1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const TEST_CASE_2 = '12345678901234567890123456789012345678901234567890';
const TEST_CASE_3 = 'ひらがなカタカナひらがなカタカナひらがなカタカナひらがなカタカナ';
const TEST_CASE_4 = '漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字漢字';
const TEST_CASE_5 = '한글한글한글한글한글한글한글한글한글한글한글한글한글한글한글한글한글한글';
const TEST_CASE_6 = '@#()*$(*%()!#&%)(&!#*()&*)(!#&*(&!#(*&*()!$&*(!#&%*(!#&*!#$';
const TEST_CASE_7 = '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!';
const TEST_CASE_8 = '12345678901234567890123456789012345678 9012345 67890';
const TEST_CASE_9 = '12345678901234567890123456789012345678한901234567890';

$('.ldh-img').tooltip({
    position: { my: "left+15 center", at: "right center" },
    content: TEST_CASE_9,
    track: false
});

ケース①、連続英字
f:id:ldhdba:20160923083948p:plain

ケース②、連続数字
f:id:ldhdba:20160923084033p:plain

ケース③、連続ひらがな・カタカナ
f:id:ldhdba:20160923084059p:plain

ケース④、連続漢字
f:id:ldhdba:20160923084140p:plain

ケース⑤、連続ハングル
f:id:ldhdba:20160923084154p:plain

ケース⑥、連続特殊文字
f:id:ldhdba:20160923084242p:plain

ケース⑦、連続ビックリ文字
f:id:ldhdba:20160923084334p:plain

ケース⑧、スペース含め連続数字
f:id:ldhdba:20160923084507p:plain

ケース⑨、ハングル含め連続数字
f:id:ldhdba:20160928134400p:plain

テストケースが多かった感じがありますが、テストの結果をご覧すると、原因をすぐ理解しましたと思っております。
原因は、🌟スペース以外の1バイト文字がスペースなしで連続に並んでいると、それを一つの単語と判断してエリアを過ぎてしまう
という原因でした。

解決

解決できる方法としては、いろいろあると思いますが、自分が解決した方法を説明させていただきたいと思っています。

それは、CSSのword-breakで改行処理する方法でございます。
word-breakとは、単語意味の通り、単語を切るというものです。
基本的に、次の行に変わるときは、単語単位で変わることになっています。
word-breakは、エリアの範囲を過ぎたら改行するCSS設定でございます。

.ui-tooltip {
    word-break:break-all;
}

f:id:ldhdba:20160923095835p:plain

まとめ

jqueryから提供されている便利なオブジェクトがいろいろありますが、利用ケースによって予想以外の結果を出している場合があります。
その原因が起こる可能性が少ないだと無視するとまずいと思っております。
しっかり、利用する機能は色々なケースを確認しましょう!
以上です。

Darumaotoshi: 論理削除ではない復旧可能な削除

基盤ユニットの小山です( @k1LoW )。

会社の技術ブログは敷居が高いので、チームの非公式技術ブログをはじめることになりました。

よろしくおねがいします。

今回は、CakePHP3での削除プラグインDarumaotoshiの紹介をします。

論理削除と設計

データベース設計におけるいわゆる論理削除については、論理削除 Casual Talksというイベントがあるほど様々な議論があります。

皆さんが言われていることについては大きく頷くばかりで、「設計しっかり」というのが大前提です。

削除してしまったものを簡単に復旧したい(かもしれない)機能

ここからスコープの小さい話をします。

単純に削除機能があるアプリケーションを作ったとき、「削除データを記録しておきたい」「削除してしまったものを簡単に復旧したい(かもしれない)」という要求があった場合、論理削除はひとつの選択肢となると思います。

多くのフレームワークで論理削除プラグインが作られていますし、CakePHPでも、UseMuffin/Trashfusic/Reincarnationなど、いくつか存在します。

ただ、「削除データを記録しておきたい」「削除してしまったものを簡単に復旧したい(かもしれない)」という要求のためだけだと、 SELECTするときに常にWHERE句が追加で必要になるので、論理削除はあまり良い手段とは言えません。

実際はWHERE句の追加も論理削除プラグインがコード上隠蔽するのですが、それでも、いざフレームワークのORマッパーから外れてSQLを書くときにツラいです。

ただ、論理削除プラグイン以外の削除プラグインというのが、なかなかないというのも現状です。 (なぜなんですかね?)

というわけで、削除レコードをアーカイブ用のテーブルに退避させる方法をとったCakePHP3用の削除プラグインを作ってみました。

Darumaotoshi

github.com

Darumaotoshiは削除時に削除レコードを自動でアーカイブ化するプラグインです。

具体的には、アーカイブ用テーブル trash を作っておいて、CakePHPのbeforeDeleteイベント発火時に、 trash テーブルに削除レコードをアーカイブさせる形で削除レコードの情報を保持します。

<?php
// in the initialize() method
$this->addBehavior('Darumaotoshi.Darumaotoshi');

というふうにビヘイビアを有効にしてもらえれば、deleteメソッド発行時に自動でアーカイブ化も実行されます。

元のテーブルからはレコードは削除されているので、SELECTのWHERE句に影響もありません。

また、復旧するためのメソッドとして restore() を提供しているので、それを利用して削除したレコードを元に戻すことも可能です。

現在は trash テーブルに、シリアライズ化(JSON化)した削除レコードを雑にアーカイブ化するだけですが、同じスキーマの削除レコードテーブルへのアーカイブ化にも対応しようと思っています。

まとめ

CakePHP3用の削除プラグインDarumaotoshiを紹介しました。

データベース設計はしっかりしていきたいと思いました。

ちなみになぜ Darumaotoshi なのかというと、削除レコードを退避する様がだるま落としのイメージと似ていたからです。