ブログのサーバー引っ越したら記事のURLが日本語URLになって色々はまった件

P1000495 MTOS(Movable Type Open Source)のMovable Type version 4.38環境で文字コードはUTF-8で運用でのことです。(以降MT)

core serverのcore-miniプランを使ってたんですけど、中華からの攻撃とかの複合的な要因で割りと高負荷になって、制限をかけられたのでSaaSesのOsukini Serverに逃げ出したわけです。

SaaSes|クラウド、ホスティング、VPS、専用サーバー、データセンターのパイオニア

で、選択したOSは CentOS release 6.3(Final)なわけです。

[kumacchi@rad-xen-vweb10 ~]$ cat /etc/redhat-release
CentOS release 6.3 (Final)
[kumacchi@rad-xen-vweb10 ~]$

で、どういうことが起こったのかというと、引っ越したので色々問題が起きたのはもちろんですが、OSがUTF-8が通る様になってMTが幸か不幸か日本語ファイル名使えるようになってしまって、MTは記事タイトルが記事のファイル名になるのですが、結果的に記事のURLが日本語URLになってしまったわけです。どういうことかというと。

例えば下記の記事のURLを見て下さい。

記事のタイトルは
Google Nexus 7 32GB版を買いました。 – KUMA TYPE

ですが、記事のURLは

【悲報】建築中のツバメの巣が崩壊!

です。

サーバーを引っ越す前の記事のURLは日本語が通らないので、記事タイトルから日本語を除去して英数字だけを連結した意味のない文字列になっていたわけです。上のURLだと「版を買いました。」という日本語の部分が欠落したわけです。

それが新しいサーバーだとどうなるかというと、下記記事タイトルの場合、

戸馳島をハイキング(熊本県宇城市三角町) その10 – KUMA TYPE

URLは、
http://blog.kumacchi.com/2013/02/戸馳島をハイキング熊本県宇城市三角町_その10.html

となります。

つまり、記事のタイトルが日本語も含めて、ほぼそのままURLになっています。

それによりどのようなことが起こるかというと

20131702

The requested URL /http://blog.kumacchi.com/2013/02/戸馳島をハイキング熊本県宇城市三角町_その10.html was not found on this server.

記事のリンクをクリックしてもエラーで、リンクできなくなります。なぜかURLが文字化けしています。

一体何が起こっているのかというと、一言で言うとurlエンコードによる弊害が起こっています。

具体的には、ブログ中のリンクや生成されるRSSの中で以下の様なことが起こっています。

http%3A%2F%2Fblog.kumacchi.com%2F2013%2F02%2F%E5%9C%92%E8%8A%B8%E5%AE%9F%E5%AE%B6%E3%81%AE%E7%95%91%E3%81%AE%E5%86%99%E7%9C%9F2012%E5%B9%B408%E6%9C%8808%E6%97%A5.html

URL中のhttp://blog.kumacchi.com/の部分までエンコードされてしまっています。

これでは、リンクに失敗してしまいます。最近までRSSのURLがこのような事態になっていることに気付かず、ずっと放置状態でした。

MTのテンプレートでは記事のURLを表すときに、<$mt:EntryPermalink$>というタグを使います。そして、URLに英数字以外の文字が含まれていた時にエンコードする様に、encode_url="1"というオプションがテンプレートで指定されている場合が有ります。(又は自分で追加します。)

<a href="<$mt:EntryPermalink encode_url="1"$>" rel="bookmark">

で、URLに日本語が含まれていると、エンコード処理が走り、ドメインの部分までエンコードしてしまうようです。これって正常な動作なの?バグじゃないの?

とりあえずの対処として私は、テンプレート中のencode_url="1"のオプション指定を外しました。

<a href="<$mt:EntryPermalink$>" rel="bookmark">

すると、リンクは正常に動作するようになり、とりあえずリンクは動作するので見かけ上は正しく動いているように見えていました。しかし、問題はそう単純ではありませんでした。

20131695

私のブログのサイドに付いている「よく読まれている記事」を表示するブログパーツです。みると、タイトルではなくURLのまま表示されている箇所が有ります。タイトル自動設定機能を利用していて、RSS内のURLと閲覧されているページのURLをマッチングして、RSSから取得した記事のタイトルを設定して表示するようになっています。しかし、その機能が動作していないということはURLのマッチングに失敗しているということになります。つまり、RSS内のURLはエンコードしていないので文字列がUTF-8の文字コードでそのまま渡っていますが、アクセスしてくるURLは検索エンジンやブラウザの内部機能でしっかりエンコードされたURLでアクセスしてくるわけです。なのでURLのマッチングに失敗して、ブラウザか検索エンジンで日本語部分がエンコードされたURLがリストに残るわけです。

そういうわけで、encode_url="1"を外したら外したで、それによる問題が別に発生しました。

20131701

20131700

20131699

しかも日本語URLをブラウザが通すからと言ってエンコードしないでそのまま使っていると、HTML Validator さんには文法がおかしいと怒られまくる訳です。エラーの数が多いとブラウザのエラー解析でページの表示速度にも影響してくるのでそのままにしておくわけにも行かないわけです。根本的な対策を考えます。

ここまでは淡々と書いてますが、実際には問題点に気がつくまで時間と調査をかなり要しています。

一番簡単な解決策は日本語URLを使わないようにすることです。

20131703
MTではテンプレートの設定で記事のパス名を変更することができます。デフォルトはyy/mm/ベースネーム.htmlになっていると思われますが、これを/yy/mm/記事番号.htmlなどにするのが手軽でいいと思います。しかし、URLが変わるのでこれを変更すると全ページを検索エンジンに覚えなおしてもらわないといけなくなるので、SEO的には痛い事になります。また、URL自体が言葉として意味をなしているのはSEO的にも有利だといわれていますので、それが意味のない数字になってしまうのは、やっぱりそれはそれでSEO的に痛かったりします。でも、やっぱり一番簡単に出来て、後々の事を考えると数字にしてしまうのもいいと思います。利点としては記事のタイトルを弄っても記事のURLが変化しない点が上げられます。まあ、間違えた時以外に弄るものでもないですけど。

今更変えたくない、どうしても日本語で使いたいという場合の対応策です。

とりあえず、手っ取り早いのはencode_url="1"を外してしまう事です。HTMLの文法を無視、ブログパーツ等で不具合が出ても我慢が出来るならまあそれでもいいと思います。

もう一つの方法は、MTのソースを弄ってhttp://の部分や「-」「_」「~」「.」などがエンコードされないように修正を加える事です。もしかするとセキュリティー的に問題が出るリスクが有ります。

で、私は最初ドメインまでエンコードされてしまっていることが原因と気づかずにエンコードの文字コードなどその辺りに問題があると思っていたので、色々検討はずな事をしていました。

下記サイト様のプラグインを組み込んで、試してみたりしていたわけです。

Movable Typeのutf8_encode_urlプラグイン/楽

まあ、直るわけがないんですけど、URL中の「http://ドメイン名」までエンコードされていることが原因と気付いたためutf8_encode_urlプラグインのURLencodeのソースを以下のように修正した所、うまくいきました。

sub URLencode {
    my $str = shift;
#   $str =~ s/([^\w ])/’%’.unpack(‘H2’, $1)/eg;
    $str =~ s/([^\w \/\:\.\,\-,~,#])/’%’.unpack(‘H2’, $1)/eg;
    $str =~ tr/ /+/;
    return $str;
}

下記の様にURL中の「/」や「:」などまでエンコードされてリンクが機能しなくなってしまっていたのが、

<link rel="alternate" type="text/html" href="http%3A%2F%2Fblog.kumacchi.com%2F2013%2F02%2F%E5%9C%92%E8%8A%B8%E5%AE%9F%E5%AE%B6%E3%81%AE%E7%95%91%E3%81%AE%E5%86%99%E7%9C%9F2012%E5%B9%B408%E6%9C%8808%E6%97%A5.html" />

「/」や「:」などのURLを表すのに必要な文字をエンコードしなようにしてリンクが機能するようになりました。またHTML Validatorさんにも怒られなくなりました。

<link rel="alternate" type="text/html" href="http://blog.kumacchi.com/2013/02/%e5%9c%92%e8%8a%b8%e5%ae%9f%e5%ae%b6%e3%81%ae%e7%95%91%e3%81%ae%e5%86%99%e7%9c%9f2012%e5%b9%b408%e6%9c%8808%e6%97%a5.html" />

 

この場合はutf8_encode_urlを利用して

<a href="<$mt:EntryPermalink utf8_encode_url="1"$>" rel="bookmark">

の様に$mt:EntryPermlinkのオプションにutf8_encode_urlを付加することで実現しましたが、MTのソース自体を弄る場合は、

mt/lib/MT/Util.pmの下記の部分を同じように弄れば実現できると思います。

sub encode_url {
    my ($str) = @_;
    $str =~ s!([^a-zA-Z0-9_.~-])!uc sprintf "%%%02x", ord($1)!eg;
    $str;
}

 

もう一つ問題があります、utf8_encode_urlを修正したものを組み込んでRSS中のURLのエンコードはうまくいったのでそれに味をしめて、ブログ内のリンク用のURLの出力にもutf8_encode_urlオプションを追加して同じように問題が解決することを期待しましたが、うまくいきませんでした。

しばらく原因に気づかずはまったのですが、そのうち気が付きました。RSSは静的に出力しているけど、ブログの記事はダイナミックパブリッシングだったということです。MTは本来perl言語で書かれたプログラムです。しかし、ダイナミックパブリッシングはphpで動くという変なことになっているCMSです。その為perlで書かれたプラグインはダイナミックパブリッシングのページでは基本的に動作しません。それがMTが廃れWordpressに追いぬかれた原因の一つにもなっています。そうなんですよ。ダイナミックパブリッシングなのでいくらperlで出来たプラグインを使っても変化があるわけがなかったんです。

そこには気付きましたが、phpは得意じゃないのでどうしたら良いのかわからない。とりあえずperlと同じようにライブラリがあるはずだとMTのディレクトリを覗いて見るわけです。

phpのencode_ulは下記にモジュールが有りました。mt/php/lib/modifier.encode_url.php

phpは使いこなしていないので、utf8_encode_urlのソースを参考にしようと思いましたが、unpack関数がphpではどうなってるのかよくわからないので困りました。どちらかと言うとmtのencode_urlのソース内のordの方がphpにも有りそうで参考になりそうな気がします。まあ、結局の所下記のように修正しました。urlencodeとrawurlencodeを置き換えて、これは対応には直接関係なく、より好ましい程度の違いです。あとは単純に

    $text = preg_replace(‘/%22/’, ‘~’, $text);
    $text = preg_replace(‘/%2F/’, ‘/’, $text);
    $text = preg_replace(‘/%3A/’, ‘:’, $text);
    $text = preg_replace(‘/%23/’, ‘#’, $text);

を追加しました。最初はurlencodeの部分を自分で実装しようと考えたのですが、よくよく考えてみたら、エンコードされてしまった文字でまずいものを戻してあげればいいじゃない。ということで、発想を転換して、エンコードされてしまった文字をもとに戻すという単純な方法に落ち着きました。

function smarty_modifier_encode_url($text) {
#   $text = urlencode($text);
    $text = rawurlencode($text);
    $text = preg_replace(‘/\+/’, ‘%20’, $text);
    $text = preg_replace(‘/%22/’, ‘~’, $text);
    $text = preg_replace(‘/%2F/’, ‘/’, $text);
    $text = preg_replace(‘/%3A/’, ‘:’, $text);
    $text = preg_replace(‘/%23/’, ‘#’, $text);

    return $text;
}

 

urlencodeとrawurlencodeの違いや、エンコード後のコードに付いては下記サイトの文章を参考にさせて頂きました。
JavaScriptとPHPのURLエンコード

まあ、そんなこんなでブログのHTMLソースの文法が、HTML Validatorさん的にかなり好ましい状態になりました。久しぶりにValidatorでチェックするといつの間にやらHTML文法的におかしくなっているところや<div>タグが一個足りなくなったりしていました。たまにはチェックしたほうがいいですね。


■追記:2013年03月06日

$text = preg_replace(‘/%23/’, ‘#’, $text);
など、「#」をデコードする処理を追加。
これが、ないとコメント一覧で正しくリンクできなくなります。

タグ : ,