My Photo

« April 2015 | Main | June 2015 »

May 31, 2015

PHPでメールを送信したら一部のOutlookで受信したメールでヘッダがおかしくなった

PHPから送信しているメールで
「件名が途中で切れて、本文の先頭にメールヘッダらしきものが付く」
という不具合が起きた。
この不具合、毎回必ず起きるわけではなく、また発生した場合もすべてのメール受信者のところで起きているわけではなかった(ほぼ同じ内容のメールを複数の人に送信している)。
現象を報告してきた人はOutlook2013を使っているとのことなので、自分のPCにOutlook2007と2013をインストールして同じ件名・本文のメールを受信した見たが、問題なく受信できた。ThunderbirdやGmailでも問題なし。

調査の結果として
「まあサーバ側の処理もよろしくなかったかもしれないけど、Outlookもちょっとおかしいんじゃね?」
という結論に^^;

まず、サーバ側でまずかったのは、改行コードとしてCRLFとLFが混在していたこと。
Subject以外のヘッダは

$content_type = "Content-Type: text/plain; charset=ISO-2022-JP\n";

のようにしている。改行コードはLF。
しかし、Subjectヘッダは

$subject = mb_encode_mimeheader($subject, 'ISO-2022-JP-MS');

としていた。mb_encode_mimeheader() を第3,4引数が省略可で、その場合、第4引数$linefeed のデフォルト値は "\r\n" 。
つまり、Subjectヘッダだけ改行コードがCRLFになっていたと思われる。
これを

$subject = mb_encode_mimeheader($subject, 'ISO-2022-JP-MS', 'B', "\r\n");

としたところ、問題は発生しなくなった。

もう少し詳しく調査をしてみた。
問題発生した環境でメールをファイルに保存してもらい、それをテキストファイルで開いてみると、Subjectヘッダ付近は以下のようになっていた。

Subject: =?iso-2022-jp?B?*******************************************************=?=
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit
X-Mailer: Microsoft Outlook 15.0
Thread-Index: AdCXa30bcJOZvjFWQ+GQbisiXjOi/Q==

=?ISO-2022-JP?B?***************************=?=

1行目の値をデコードするとメーラーに件名として表示されいる内容(途中でちょん切られた件名)と一致した。
Thred-Indexというヘッダの下に空行があり、その下の行はメーラーで本文の1行目として表示されているものと一致した。

あー、たぶんこういうことだ。

なぜ問題が発生する場合と発生しない場合があるかというと、問題の発生条件が件名の長さによるからだ。
mb_encode_mimeheader() はエンコードした結果が74文字より長いと改行を入れて分割する。これはメールの仕様上問題はない。
件名が短くて分割されない場合、メールの改行コードはすべてLFで問題が発生しない。
しかし、件名が長くてSubjectヘッダが分割されると、mb_encode_mimeheader() の引数$linefeed をデフォルトのままにしていたせいで、その分割のところだけ改行コードがCRLFになる。そしてそのヘッダをOutlookがうまく処理できなくて今回の問題が発生した。

根本的な原因は自分のコーディングにあったのだが、それでも問題が発生したのはOutlookだけだ。それにOutlookでも必ず発生するわけではない。
テキストファイルで開いたメールのヘッダでThread-IndexというヘッダはOutlookが付加する非標準のヘッダだそうだ。
そしてその下に空行が入って、さらに分割されたSubjectヘッダの後半がある。
Outlookでも現象が起きたり起きなかったりするのは、設定とか環境(Exchangeのメール以外のサービスを何か使っているとか)によってこのThread-Indexというヘッダを使う/使わないが変わって、それで使う場合にのみこの現象が発生するんじゃないかと。
実際、現象が発生しなかった自分のOutlook2013で受信したメールを見ると、Thread-Indexはなかった。

という訳で、原因は「Outlookが非標準のヘッダを使っていて、その処理がなんかおかしい」ということで。
#責任回避。

参考ページ:
【php】mail関数の改行コード注意点 at softelメモ
PHP: mb_encode_mimeheader - Manual
メールヘッダ一覧
メールのヘッダフィールド・マニアックス: 非標準 - 鳥さんの落書き帳

May 13, 2015

LaravelでHello World とか諸々

関連記事:
Laravel4のインストール

設定

とりあえずデバッグモードを有効化し、タイムゾーンとロケールを設定した。
app/config/app.php の3箇所を以下のように変更する。

…
'debug' => true,
…
'timezone' => 'Asia/Tokyo',
…
'locale' => 'ja',
…

Hello world

Hello world は、app/routes.php に以下のように記述する。

Route::get('/', function()
{
    return 'Hello world!';
});

トップページにアクセスすると、"Hello world!" と表示される。

ログ

Hello world にログを出力するコードを追加する(3行目)。
ログファイルはデフォルトでは app/storage/logs/laravel.log

Route::get('/', function()
{
    Log::info('log test'); 
    return 'Hello world!';
});

Laravel Debugbarのインストール

ログなど開発時に重宝する情報をクライアントの画面に表示してくれる Laravel Debugbar というパッケージをインストール。
composer を使う。composer の解説のページでは composer.json にインストールするパッケージの情報を追記するとあるが、composer.json 作るのか?と思ったら、Laravelのプロジェクトのディレクトリにあった。Laravelのプロジェクトを composer で作ったから、その時に自動的に作られたのかな。
composer.json のrequireに以下を追記する。

"barryvdh/laravel-debugbar": "1.8"

composer.json を変更した後、コマンドラインで "composer update" でインストールが行われる。
さらに app/config/app.php の2箇所に追記を行う。
providers に以下を追加する。

'Barryvdh\Debugbar\ServiceProvider',

aliases に以下を追加する。

'Debugbar'        => 'Barryvdh\Debugbar\Facade',

これで Debugbar が使用できようになった。画面は以下のようになる。

Debugbar

参考書籍:

Laravelエキスパート養成読本[モダンな開発を実現するPHPフレームワーク!] (Software Design plus) Laravelエキスパート養成読本[モダンな開発を実現するPHPフレームワーク!] (Software Design plus)
川瀬 裕久 古川 文生 松尾 大 竹澤 有貴 小山 哲志 新原 雅司

技術評論社  2015-04-21
売り上げランキング : 4108

Amazonで詳しく見る
by G-Tools

参考ページ:Laravel
PHPフレームワークLaravelやってみた | webOpixel
Laravel出力ログファイルの切り替え(またはローテーション) - IT屋だけど、なにか?
実行するSQLのクエリーをlaravel.log以外に吐いてみた - 生涯未熟

参考ページ:composer
Composer ドキュメント日本語訳

参考ページ:Laravel Debugbar
Laravel4 Debugbarをインストールする | ちらうら
Laravel4.2.x を使っていて laravel-debugbar も使っているなら「"barryvdh/laravel-debugbar": "1.8"」を忘れずに - Qiita
laravel4を使って最速簡易ブログ作成の方法 - Qiita

May 08, 2015

JSONのオブジェクトは順序性を持たない

jQueryでリクエストを投げてPHPで配列のデータをJSON形式で返すアプリケーションを考える。

サーバ側のプログラムのサンプル。

$data = array('5' => array('areaName' => '大森')
            , '7' => array('areaName' => '蒲田')
            , '3' => array('areaName' => '大井町'));

print json_encode($data);

クライアント側のプログラムのサンプル。data はPOSTパラメータでサーバ側では使っていないが、Ajaxでサーバ側にパラメータを渡す例として書いておいた。

var url = "./ajax.php";
var data = { "mode" : "hoge" };
$.post(url, data,
  function(data) {
    for (var key in data) {
      alert("key=" + key + ",areaName=" + data[key].areaName);
    }
  },
  "json"
);

これを実行するとクライアント側ではサーバ側の元のデータの順序、つまり「大森」、「蒲田」、「大井町」と表示されると期待してしまうが、実際は「大井町」、「大森」、「蒲田」の順で表示される。
#Firefoxの場合。他のブラウザでは試していない。
原因はPHPの json_encode() がPHPの配列をJSONのオブジェクトに変換してしまうためである。実際のレスポンスのボディは以下のようになっている(デコードしてインデントを付けている)。

{
  "5": {
    "areaName": "大森"
  },
  "7": {
    "areaName": "蒲田"
  },
  "3": {
    "areaName": "大井町"
  }
}

これは3つのプロパティを持つJSONオブジェクトであり、配列ではないのでプロパティに順序なんてない。実際の出力の順序を見るとプロパティ名の降順で出力しているように見える。たまたまかもしれないが。

対策としては、サーバ側で json_encode() を使うのをやめて、PHPの配列を受け取ってJSON形式の配列を返す処理を自前で書けばいい。
修正したコードを以下に示す。下記のサーバ側コードで json_encode() の代わりの関数 json_array_encode() は汎用的なものではなく、配列のデータの形式が決まっているものとしてその形式だけにとりあえず対応したものである。json_array_encode() ではareaNameの値をUNICODEコードポイントの文字列に変換するために json_encode() を使っている。

サーバ側

$data = array('5' => array('areaName' => '大森')
            , '7' => array('areaName' => '蒲田')
            , '3' => array('areaName' => '大井町'));

print json_array_encode($data);

exit;

function json_array_encode($data) {
	$buf = array();
	
	foreach ($data as $k => $v) {
		$buf[] = '{"areaId":"' . $k . '","areaName":' . json_encode($v['areaName']) . '}';
	}

	return '[' . implode(',', $buf) . ']';
}

クライアント側

var url = "./ajax.php";
var data = { "mode" : "aaa" };
$.post(url, data,
  function(data) {
    for (var key in data) {
      alert("areaId=" + data[key].areaId + ",areaName=" + data[key].areaName);
    }
  },
  "json"
);

修正後のレスポンスのボディは以下のようにオブジェクトではなく配列となっている。
表示の順序は「大森」、「蒲田」、「大井町」とサーバ側のPHPの配列の順序どおりになる。

[
  {
    "areaId": "5",
    "areaName": "大森"
  },
  {
    "areaId": "7",
    "areaName": "蒲田"
  },
  {
    "areaId": "3",
    "areaName": "大井町"
  }
]

参考ページ: PHPと異なり,JavaScriptの連想配列とfor in構文には順序の概念がないので注意すること - プログラミングとIT技術をコツコツ勉強するブログ

May 06, 2015

「ペルソナ4 ザ・ゴールデン」クリア

ベスト版が出てすぐ買ったP4Gをやっとこさクリア。
ちんたらやっててプレイ時間は80時間くらい。

正直、イマイチだった。

良かった点
・メニューとかシステムは使いやすかった。

イマイチだった点
・クエストがほとんどおつかい。めんどくさいだけ。
・難易度をNORMALでやったが簡単すぎる。例をあげると、P2の頃はハマ系やムド系であっという間に死んでしまい戦闘に緊張感があったが、今回は普通にプレイしてるとホムンクルスとか即死系を防ぐ消費アイテムが貯まってきてハマ系・ムド系魔法で死ぬことがなかった。
・ラスボス倒した後の2ヶ月くらいは必要か?全コミュコンプを目指すプレイをしてる人ならありがたいのかもしれないが、そうじゃなければ単にエンディングまでに余計な作業が増えるだけ。

ストーリーは悪くはないとは思おうが、すごくいいとか感動したとかはない。普通。
コミュやペルソナ合体を工夫して低レベルクリアとかいろいろ工夫した遊び方はあるかもしれないが、何週もしてやりこみたいとは思わなかった。

もうペルソナシリーズはいいや。P5はやらない。
ペルソナだけじゃなくて、こういうストーリーが一本道の古いタイプのRPGは今後やらないと思う。
軌跡シリーズも「零の軌跡」のストーリーがあまりにもひどくて見限ったし。

次何しようか。相当前に買って放置している「バイオハザード4」か。「メタルギアソリッドHD」はMSX2版の「メタルギア」が入ってるというのでやってみようか。
あと飛行機が好きなのでの飛行機のゲームも興味ある。フライトシミュレータはそろえるものが多そうなので、お手軽に「ぼくは航空管制官3」とか。

Laravel4のインストール

関連記事:
[CentOS 6.6]PHP5.5のインストール

Laravelに関する本が出たので購入。電子出版による本はすでにあったが、紙媒体では初のLaravel本だと思う。
本に従ってvirtualbox上のCentoOS 6.6 でLaravelプロジェクトを作成してみた。
最初に本の内容に入る前に、Laravelで必須なPHPのmbstringがインストールされていなかったので、以下のようにyumでインストールした。

yum --enablerepo=remi --enablerepo=remi-php55 install php-mbstring

ここから本の内容に従った作業。
現在、Laravelの最新バージョンは5だが、今回はバージョン4の環境を作る。今後、触るかもしれないWEBアプリがバージョン4で作られているので。

Lravelの取得

LaravelのインストールはPHPやPostgreSQLの「インストール」とは違うような気がする。プロジェクトをごとにLaravelの環境を作ると考えた方が合っている?
composerコマンドを下記を実行すると、指定したディレクトリに必要なファイルが用意される。ディレクトリ名はプロジェクトのディレクトリを指定する。絶対でも相対でもよい。

composer create-project laravel/laravel <ディレクトリ名> 4.2.*

今回はディレクトリをドキュメントルートのサブディレクトリ /var/www/html/ltest とした。

# composer create-project laravel/laravel /var/www/html/ltest 4.2.*
Warning: This development build of composer is over 30 days old. It is recommended to update it by running "/usr/local/bin/composer self-update" to get the latest version.
Installing laravel/laravel (v4.2.11)
  - Installing laravel/laravel (v4.2.11)
    Downloading: 100%         

Created project in /var/www/html/ltest
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing symfony/translation (v2.5.11)
    Downloading: 100%         
<中略>
laravel/framework suggests installing doctrine/dbal (Allow renaming columns and dropping SQLite columns.)
Writing lock file
Generating autoload files
Generating optimized class loader
Compiling common classes
Compiling views
Application key [**********************] set successfully.
#

「composerをアップデートしろ」的なワーニングが出たが、コマンドはたぶん正常に完了。
インストールの最後に表示される "Application key" は app/config/app.php が返す配列のキーが "key" という要素の値に保存されている。
プロジェクトのディレクトリは以下のような構成となる。

app
├─commands
├─config
├─controllers
├─database
│  ├─migrations
│  └─seeds
├─lang
├─models
├─start
├─storage
├─tests
└─views
bootstrap
public
vendor

上記のディレクトリの中で app/storage 以下はLaravelがファイルを保存できるように、以下のコマンドでパーミッションを777に変更しておく。

chmod -R 777 storage

プロジェクトのディレクトリをLinuxのユーザのホームディレクトリの配下、例えば /home/hoge/ltest とかにすると、SELinuxのせいで色々と面倒らしい。

バーチャルホストの設定

作成したプロジェクトのディレクトリ配下のpublicディレクトリがドキュメントルートとなるので、ここをドキュメントルートとするバーチャルホストの設定をする。
まず、httpd.conf の以下の2箇所を変更。

変更前: #ServerName www.example.com:80
変更後: ServerName www.into1st.jp:80
変更前: #NameVirtualHost *:80
変更後: NameVirtualHost *:80

ServerNameについてはコメントになってればいいと書いてあるWEB上の記事もあったが、こうしないと以下のようなワーニングが出力された。

# service httpd restart
httpd を停止中:                                            [  OK  ]
httpd を起動中: httpd: Could not reliably determine the server's fully qualified 
domain name, using localhost.localdomain for ServerName
CentOS 6.6のApacheでは設定ファイルは /etc/httpd/conf/httpd.conf である。この設定ファイルの中で /etc/httpd/conf.d/*.conf を読み込むようになっている。よって、バーチャルホストの設定として以下の3ファイルを /etc/httpd/conf.d に作成する。ファイル名は適当に決めた。

virtualhost-undefinedhost.conf は未定義のホスト名でアクセスされた場合にアクセスを拒否するための設定。

<VirtualHost *:80>
    ServerName any
    <Location />
        Require all denied
    </Location>
</VirtualHost>

virtualhost-main.conf はメインのホスト名の設定。/var/www/html をドキュメントルートとする。

<VirtualHost *:80>
    ServerName www.into1st.jp
    DocumentRoot /var/www/html
</VirtualHost>

virtualhost-virtual01.conf はLaravel4のプロジェクト用バーチャルホストの設定。

<VirtualHost *:80>
    ServerName www.ltest.jp
    DocumentRoot /var/www/html/ltest/public
    ErrorLog logs/virtual-error_log
    CustomLog logs/virtual-access_log combined env=!no_log
</VirtualHost>

これで、httpd を再起動すればOK、のはず。しかしまだワーニングが。

# service httpd restart
httpd を停止中:                                            [  OK  ]
httpd を起動中: Warning: DocumentRoot [/var/www/html/ltest/public] does not exist

SELinuxが原因らしい。面倒だなSELinux。
ちゃんと対応するなら /var/www/html/ltest/public にSELinuxのラベルの設定を行うらしいのだが、その対応はいつかすることにして、今回は一時的にSELinuxを無効にして再起動。

# getenforce
Enforcing
# setenforce 0
# getenforce
Permissive
# service httpd restart
httpd を停止中:                                            [  OK  ]
httpd を起動中:                                            [  OK  ]

これでサーバ側の設定は完了。
DNSの設定変更は面倒なので、クライアントPCのhostsファイルにバーチャルホストの設定に記述したドメインを以下のように追記する。

<サーバのIPアドレス> www.into1st.jp www.ltest.jp

これで http://www.ltest.jp/ にアクセスすると "You hava arrived." と表示される。
長かった^^;

参考書籍:

Laravelエキスパート養成読本[モダンな開発を実現するPHPフレームワーク!] (Software Design plus)Laravelエキスパート養成読本[モダンな開発を実現するPHPフレームワーク!] (Software Design plus)
川瀬 裕久 古川 文生 松尾 大 竹澤 有貴 小山 哲志 新原 雅司

技術評論社 2015-04-21
売り上げランキング : 4108

Amazonで詳しく見る
by G-Tools

参考ページ:Laravelについて
インストール 4.2.0 Laravel
CentOS 6.5 + PHP 5.6 + MySQL 5.6 + laravel4 環境を作る(part 2)|OISのブログ|株式会社オリエンタルインフォーメイションサービス
CentOS 6.6 で Laravel4 を使う準備 - Qiita
CentOS 6.5 + nginx 1.6.1 + Laravel 4.2のインストールメモ - Qiita
PHP - Laravel 4のインストール - Qiita

参考ページ:バーチャルホストについて
バーチャルホスト設定 - CentOSで自宅サーバー構築
11 | 5月 | 2013 | 其未来.com
複数のドメインを運営する (バーチャルホスト) - Linux で自宅サーバ [ Home Server Technical. ]

参考ページ:SELinuxについて
[CentOS][Apache]SELinux有効下でのドキュメントルートの変更 | ごった煮 - tips about programming and building a server
SELinuxを有効にしているときにありがちなpermission denied - kgbu's diary
SELinuxを無効化する | Smart

« April 2015 | Main | June 2015 »

April 2017
Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            
無料ブログはココログ

日本blog村

  • にほんブログ村 IT技術ブログへ
  • にほんブログ村 アニメブログへ
  • にほんブログ村 サッカーブログ アルビレックス新潟へ

好きな音楽家

メモ

XI-Prof