Hatena::ブログ(Diary)

風柳メモ このページをアンテナに追加 RSSフィード Twitter

2015-07-15

SVGでテキストの縦位置(baseline)を調整する方法を調べてみた

SVGで、テキストの縦位置(baseline)を揃える処理を書こうとしてはまったので、覚え書き。


経緯

コピィ・ライターを作成していてふと、

「あれ? 基本的にテキストを配置してバナーを作るものだし、SVGと相性良いんじゃ? 設定ファイルを JSON で書き出しているところをちょっといじれば、SVG で書き出すこともできるようになるかも?」

と思い立ったので、処理を追加してみたところ、テキストの baseline を揃えるところで苦労したというもの。

そもそも、SVGをいじるのはこれが初めてなので、誤解をしているところはあるかも……変なところがあればご指摘願いたい。


どこではまったか? 「え……またIEかよっ!」

HTML5 Canvas の場合、textBaseline プロパティを指定すればテキストの baseline が指定できていた。

同じようなものが SVG でも無いか探してみると、text 要素等で指定できる dominant-baseline プロパティがこれに相当するようだ。


HTML5 Canvas と SVG のテキストの baseline の対応(今回対象としたもののみ)
baseline(基底線)Canvas
(textBaselineプロパティ)
SVG
(dominant-baselineプロパティ)
The top of the em squaretoptext-before-edge
The middle of the em squaremiddlecentral
The bottom of the em squarebottomtext-after-edge

em square=文字の構造的な大きさを表す四角形、EM box・em quad 等とも。

■参考

textBaseline プロパティ - Canvasリファレンス - HTML5.JP

svg要素の基本的な使い方まとめ


それで、実際やってみたところ……

のように、IE では効いていないように思われた。


それで調べてみると、確かにサポートされていないようだ。

IE9 Mode, IE10 Mode, IE11 Mode, and EdgeHTML Mode (All

Versions)

The dominant-baseline attribute is not supported.

[MS-SVG]: [SVG11] Section 10.9.2, Baseline alignment properties

ではどうするか?

とりあえず、IE でなんとかする方法を探してみたところ、

The specification defines central like that:

central

This identifies a computed baseline

that is at the center of the EM box.

We can take an EM box of known font size and measure its bounding box to compute the center.

internet explorer 9 - How to center SVG text vertically in IE9 - Stack Overflow

とあり、具体的なソースコードが書かれている。


どうやら、SVG中の script 要素にて、EM box の座標と大きさを元に central 相当の位置を決めて、transform 属性にて位置を調整する、ということらしい。


  • SVG 中の text 要素に指定した座標を (Tx, Ty)
  • 実際に描画された当該 text 要素の EM box(外枠) の左上の座標を (Rx, Ry)、高さを Rh

とすると、

text-before-edge 相当位置Ry + (Ty - Ry)
central 相当位置Ry + ( (Ty - Ry) - (Rh / 2) )
text-after-edge 相当位置Ry + ( (Ty - Ry) - Rh )

になると思われる。


transform 属性には、

translate(<tx> [<ty>])
tx, ty による 並進 を指定する。
<ty> が与えられていない場合、 0 とみなされる。

座標系, 変換, 単位 ? SVG 1.1 (第2版)

のような変換定義を指定してやればテキストの移動が可能なので、例えば central の場合には

tx = 0, ty = (Ty - Ry) - (Rh / 2)

を指定してやればよい。

tx, tyは、EM box左上端からの相対位置なので注意。


コピィ・ライターでは、具体的には、以下のようなスクリプトを SVG 内に挿入した。

var useragent = window.navigator.userAgent.toLowerCase();
if (useragent.indexOf('msie') < 0 && useragent.indexOf('trident') < 0) {
    return; // IE 以外は何もしない
}
window.onload = function() {
    var elm_text_list = document.getElementsByTagName('text');
    for (var ci=0, len = elm_text_list.length; ci < len; ci++) {
        var elm_text = elm_text_list[ci]
        ,   text_y = parseInt(elm_text.getAttribute('y'))
        ,   rect = elm_text.getBBox()
        ,   rect_y = rect.y
        ,   rect_height = rect.height
        ,   offset_y = (text_y - rect_y) - (rect_height / 2); // central の場合
        
        elm_text.setAttribute('transform', 'translate(0, ' + offset_y + ')');
    }
};

なお、IE 以外の場合には、別途 SVG の text 要素に対して dominant-baseline プロパティを設定してあるため、スクリプトでは処理を行わない。


結果

実際に上記手法を適用して作成したSVG画像が、Chrome と IE でどう見えるかを示す。

赤線は基線(SVGのテキスト要素で指定したY座標)、テキストの周囲の四角はEM box(スクリプトで描写)

Google Chrome (バージョン 43.0.2357.132 m)

f:id:furyu-tei:20150715001639j:image

Windows 7 上で確認した限りでは、Firefox や Opera の最新版でもほぼ同様に見えた。


IE11

f:id:furyu-tei:20150715001640j:image


注意点

  • SVG ファイルを HTML中に貼り付ける際、IMG要素 を使用すると、SVG内のスクリプトも実行されないため、上記の小細工が適用されない。
    これはCSSのbackground-image等で指定する場合でも同様。
  • OBJECT要素や IFRAME要素で指定してやれば、意図通りに表示される。
    ただし、IFRAMEだと扱いが比較的面倒であるため(CSSで拡大縮小しづらい等)、OBJECT要素を使用するのがベター。

HTML5・A要素(リンク)のdownload属性に関する覚え書き

これもコピィ・ライター作成時に、

  • 動的に生成した画像をボタンをクリックしてダウンロード

する機能を実現する過程で、HTML5・A要素(リンク)の download 属性について調べたことに関する覚え書き。


HTML5・A要素(リンク)の download 属性とは

download HTML5

この属性は、ユーザがリンクをクリックするとリソースをローカルファイルとして保存することを促されるように、リソースをダウンロードするために使用されるハイパーリンクであることをページ作者が意図して記述します。属性に値が指定された場合、ユーザがリンクをクリックしたときに開く保存プロンプトの、デフォルトのファイル名として解釈します (もちろん、ユーザは実際にファイルを保存する前にファイル名を変更できます)。使用可能な値に制限はありません (ただし / および \ はアンダースコアに変換して、特定のパスヒントを防ぎます) が、多くのファイルシステムには、ファイル名に使用できない文字があることを考慮する必要があります。ブラウザがファイル名を調整するかもしれません。

補足:

  • この属性は、ユーザが簡単に JavaScript を使用するプログラムで生成されたコンテンツ (例えばオンラインのお絵かき Web アプリを使用して描いた画像) をダウンロードするため、blob: URL および data: URL とともに使用できます。
  • この属性で指定したものと異なるファイル名を Content-Disposition: HTTP ヘッダで与えている場合は、この属性より HTTP ヘッダが優先します。
  • この属性を指定するとともに Content-Disposition:inline を指定している場合、Firefox はファイル名と同様に Content-Disposition を優先しますが、Chrome は download 属性を優先します。
  • Firefox 20 では、この属性は同一生成元のリソースへのリンクにのみ受け入れられます。

a 要素 - HTML | MDN

とりあえず、Can I use... Support tables for HTML5, CSS3, etcで調べてみると、最新のFirefox・Chrome・Operaはサポートしている模様。

またIEは無いのか……と思ったが、代替手段(window.navigator.msSaveOrOpenBlob())でなんとかなりそうではあった。

Safari? えっと、知らない子ですね……。


実験

  • [A] 画像ファイルを Data URL に変換したもの
  • [B] 画像ファイル(サイト内)へのリンク
  • [C] 画像ファイル(外部サイト)へのリンク

三種類の A(リンク)要素を用意し、それぞれに download 属性でファイル名を指定した場合のテストを行った。

IE用には別途、スクリプトで window.navigator.msSaveOrOpenBlob() を使った細工をしてある。

それぞれのリンクをクリック(タップ)した結果、以下のようになった。

Google Chrome
43.0.2357.134 m
Firefox
39.0
Opera
30.0.1835.125
IE11Chrome for Android
43.0.2357.93
[A]
[B]×
[C]××
  • ○:download属性で指定したファイル名でダウンロード
  • △:download属性は無視され、href属性を元にしたファイル名でダウンロード
  • ▲:download属性で指定したファイル名でダウンロード
    IE用に、リンクに onclick トリガを設定し、window.navigator.msSaveOrOpenBlob() を使った細工を入れている
  • ※:別窓(タブ)で画像が開く
    IE用に、リンクに onclick トリガを設定し、XMLHttpRequest で画像を取得させ、エラーが発生したら別窓(タブ)で開く細工を入れている
  • ×:hrefで指定された先にページ遷移

外部サイト上のファイルの場合の動作はセキュリティがらみの制限なのだろうと推測されるが、Chrome for Android でサイト内ファイルへのリンクまでページ遷移してしまうのは謎。なぜ PC 版と動作を変える必要があったのか?


関連する処理など

canvas 要素の Data URL への変換

参考:toDataURL() メソッド - Canvasリファレンス - HTML5.JP

var dataURL = canvas.toDataURL(type); // canvas は HTML5 Canvas の DOM要素、typeは画像の種別('image/png', 'image/jpeg'等)

データの Blob オブジェクトへの変換

参考:Blob - Web API インターフェイス | MDN

var blobObject = new Blob([data], {'type' : mimeType}); // data は元データ、mimeType は元データの MIMEタイプ('image/svg+xml'等)

Data URL の Blob オブジェクトへの変換

参考:Canvas に描いた画像を png などの形式の Blob に変換する方法: Tender Surrender

function make_blob_from_dataurl(dataurl) {
    if (!dataurl.match(/^data:(.*?);base64,(.*)$/)) {
        return null;
    }
    var type = RegExp.$1, base64 = RegExp.$2;
    
    var bin = atob(base64), bin_length = bin.length;
    var buffer = new Uint8Array(bin_length);
    for (var ci=0; ci < bin_length; ci++) {
        buffer[ci] = bin.charCodeAt(ci);
    }  
    var blobObject = new Blob([buffer.buffer], {type: type});
    
    return blobObject;
}


ファイルを Blob オブジェクトとして取得

参考:バイナリデータの送信と受信 - XMLHttpRequest | MDN

var xhrObject = new XMLHttpRequest();
xhrObject.open('GET', url, true);
xhrObject.responseType = 'blob';
xhrObject.onload = function(event) {
    var blobObject = xhrObject.response;
    // ◆ 以下、blobObject を用いた処理を記述
};
xhrObject.onerror = function(event) {
    //※(許可されていない)外部サイトのファイルを取得しようとすると以下のようなエラーが発生(IEの例)
    //  SEC7118: (取得しようとしたURL) の XMLHttpRequest には Cross Origin Resource Sharing (CORS) が必要です。
    //  ファイル: (元ファイル)
    //  SEC7120: 元の http://(元ドメイン) が Access-Control-Allow-Origin ヘッダーに見つかりません。
    //  ファイル: (元ファイル)
    //  SCRIPT7002: XMLHttpRequest: ネットワーク エラー 0x80070005, アクセスが拒否されました。
    
    window.open(url);   // 次善の策として、例えばポップアップで開く
};
xhrObject.send();


Blob オブジェクトの Blob URL への変換

参考:window.URL.createObjectURL - Web API インターフェイス | MDN

var blobURL = (window.URL || window.webkitURL).createObjectURL(blobObject);

Blob オブジェクトをファイルとして保存したり開いたりするイベントの発生(IE10+)

参考:msSaveOrOpenBlob method (Internet Explorer)

window.navigator.msSaveOrOpenBlob(blobObject, filename);

その他気付いたことなど

  • A要素(リンク)の場合、jQuery によってクリックイベントを発生( .click() や .trigger('click'))させても、ダウンロードやページ遷移は行われない。
    これらを発生させたい場合には、MouseEvent を作成するか、DOM の .click() をコールする。

2015-07-12

コピィ・ライター(CopieWriter) - はてなコピィもどきのバナーを作成可能なサービスを作ってみた

はてなコピィのように、テキストを配置してバナーを作成でき、途中で保存/再開も可能なサービスを作ってみました。

http://furyu.nazo.cc/CopieWriter/

CopieWriter: コピィ・ライター

GitHub のリポジトリはこちら
サービスと言いつつ、ZIPをダウンロード・解凍したフォルダの index.html を開けば、ローカルでも動作します。


画面サンプル

http://furyu.nazo.cc/CopieWriter/?setting=samples/%E3%81%AF%E3%81%A6%E3%81%AA%E3%82%B3%E3%83%94%E3%82%A3%E3%82%82%E3%81%A9%E3%81%8D.json


特徴

長所
  • JavaScript だけで動作している(クライアントサイドだけの処理で、サーバーとの通信は行わない)ため、比較的さくさく動作する。
  • フレーズ数の上限は設けていない(ただし、多すぎると重くなる)。
  • 途中保存して中断(設定をダウンロード)や再開(設定の読み込み)が可能。
  • 設定ファイル(JSON形式)内の値を編集すれば、バナーの大きさやフォントも変更可能。
短所
  • サーバーサイドの処理は一切ないため、はてなコピィのような一覧ページやリンクページといったものは自動では作れない。
  • はてなコピィとのデータ上の互換性はない。
  • 同じく、操作性も異なるので、慣れるまで若干とまどうかも。
  • ブラウザによって出力結果が異なったり、うまく動作しない可能性あり。
    基本、PC版のブラウザのみ対応。IE11・Chrome・Firefox・Operaの最新版であれば多分動作する。ただしSafariでは多分うまく動作しない。

経緯

はてなコピィはテキストを自由に配置してバナーを作れるということで、お気に入りのサービスです。

最近はごぶさたしているものの、これまでも結構な数のコピィを作成しています


ですが、作成途中の保存は出来なかったりフレーズ数の制限によって、しばしば涙を飲みました。*1


以前から、いつの日かこれらを解消できたらなぁ……と思っていましたが、昨日ふと

「『テキストを配置してバナーをつくり保存する』くらいの機能なら、モダンブラウザ*2なら canvas なりを使えば、クライアントサイドのみで実現できるのではないか?」

と思い立ち、そのまま勢いで作ってみたものです。

*1:「いや、本来アドバンスモードでお絵かきするのは本来の使い方じゃないんんじゃ?」という意見は聞きません

*2:そろそろ死語?

2015-07-06

歌詞検索サービスの歌詞(テキスト)コピー禁止手法について調べてみた

動画投稿(共有)サイトやブログ等への歌詞掲載が実施しやすくなりつつある一方で、歌詞検索サービス等ではまだ歌詞(テキスト)のコピー禁止措置が取られているところが大半の模様。

このコピー禁止のための手法について、幾つかの歌詞検索サービスで調べてみた(対象はPC向けサービスのみ)。

おまけで、解除用のブックマークレットも試作してみた

その後、ユーザースクリプト化してみた

JASRAC による個人ブログ等における歌詞掲載利用許諾の概要

恥ずかしながら知らなかったのだが、個人ブログ等への歌詞掲載について、JASRAC では2012年末より、サービス運営事業者が JASRAC と許諾契約を締結することで、ユーザーが個別に許諾を得ることなく歌詞の掲載が出来るようになっている模様。

そこで、動画投稿(共有)サービスにおいてユーザーがJASRAC管理楽曲をアップロードする行為をサービス運営事業者に許諾することで動画投稿(共有)サービスにおけるJASRAC管理楽曲の適正な利用を推進している事例にならって、ユーザーが開設するブログ等でJASRAC管理楽曲の歌詞を掲載利用することにつき、ユーザーに替わってサービス運営事業者に許諾する際の条件を定めた次第です。なお今回の措置は非商用配信における歌詞掲載利用を対象としたものであり、大量の歌詞を掲載する歌詞閲覧サービスや広告料収入を得るなどの目的で行う配信利用は除かれます

ブログサービス等の運営事業者に対し、個人ブログ等における歌詞掲載利用を許諾することについて

太字は引用者修正。

JASRAC と利用許諾契約を締結しているUGC(User-Generated Contents)サービス(動画投稿(共有)サイト・ブログサイト等)のリストも公表されている。

動画投稿(共有)サイトやブログサービス等のUGC(User-Generated Contents)サービスでJASRAC管理楽曲を利用されることについて、一般ユーザーの皆さまからのお問い合わせが多いことから皆さまの利便性を考慮し、JASRACと利用許諾契約を締結しているUGCサービスのリストを公表します。


JASRACと利用許諾契約を締結している以下のサービスでは、一般ユーザーの皆さまが個別にJASRACへ利用許諾手続きを行なわなくともJASRAC管理楽曲を利用したUGC(動画・歌詞)をアップロードすることが可能です。

利用許諾契約を締結しているUGCサービスリストの公表について

つまり、アメーバやSeesaa、Yahoo!、ライブドア等のブログを使っている人は、個別に許諾を得ることなく、自分のブログに歌詞をアップロードできるということ(大量掲載・広告料収入目的等の利用は不可)。

あれ、はてな は……?



歌詞検索サービスのコピー禁止状況

とりあえず、『歌詞 検索』で検索をかけてみて、現時点で表示されるもののうち、上から5つのサービスについて調べてみた。


歌詞(テキスト)のコピー禁止手法としては、以下のようなものが使用されている模様。

  • テキスト選択無効化
  • コンテキストメニュー無効化(いわゆる右クリック禁止)
  • 歌詞部分の非テキスト化(FlashやCanvasによる表示、画像化など)

各サービス毎のコピー禁止手法一覧

サービス名テキスト選択無効化コンテキストメニュー無効化FlashCanvas画像備考
歌詞検索サービス 歌ネット画像はSVG形式
うたまっぷ 歌詞を無料で検索表示IE11等ではFlash使用
歌詞検索サービス 歌詞GET
歌詞ナビ 無料歌詞検索サービス
歌詞検索J-Lyric.net

コピー解禁用ユーザースクリプト(Greasemonkey / Tampermonkey)[2015/07/07追記]

下記のブックマークレットを集約してユーザースクリプト化・対象サービスも追加。

【歌詞解禁(KashiKaikin)】 歌詞検索サービスの歌詞テキストコピーを可能に

Greasemonkey / Tampermonkey が入っていれば ↑ をクリックすることでインストール可。

furyutei/KashiKaikin ? GitHub

コピー解禁用ブックマークレット(Hatena::Let

コピー禁止手法を調べたついでに、解除方法についても検討し、ブックマークレットを試作。

今のところ、上記のいずれのサービスでも、ユーザー側でブックマークレットを実行すれば、歌詞テキストのコピーが可能になる。

■ 集約版(2015.07.08 追記: 下の各々のブックマークレットの機能をひとつに集約したもの・ユーザースクリプト版と同等)

歌詞解禁(KashiKaikin) - Hatena::Let



※以下は旧バージョン。特に理由がないかぎり、↑の集約版の使用を推奨。

歌詞検索サービス 歌ネット

歌ネット(Uta-Net)の歌詞抽出 - Hatena::Let

例) トゥッティ! - 北宇治カルテット - 歌詞 : 歌ネット

f:id:furyu-tei:20150706143428j:image


うたまっぷ 歌詞を無料で検索表示

うたまっぷ歌詞無料検索の歌詞抽出 - Hatena::Let

例) 北宇治カルテット(黄前久美子(黒沢ともよ)/加藤葉月(朝井彩加)/川島緑輝(豊田萌絵)/高坂麗奈(安済知佳))/歌詞:トゥッティ!/うたまっぷ歌詞無料検索

f:id:furyu-tei:20150706143429j:image


歌詞検索サービス 歌詞GET

歌詞GETのコンテキストメニュー解禁 - Hatena::Let

例) トゥッティ!/北宇治カルテット [黄前久美子 (CV:黒沢ともよ), 加藤葉月 (CV:朝井彩加), 川島緑輝 (CV:豊田萌絵), 高坂麗奈 (CV:安済知佳)] - 歌詞検索サービス 歌詞GET

f:id:furyu-tei:20150706143430j:image


歌詞ナビ 無料歌詞検索サービス

歌詞ナビの歌詞抽出 - Hatena::Let

例) トゥッティ! 北宇治カルテット - 歌詞ナビ

f:id:furyu-tei:20150706143431j:image


歌詞検索J-Lyric.net

J-Lyric.netのコンテキストメニュー解禁 - Hatena::Let

例) 北宇治カルテット(黄前久美子(黒沢ともよ)/加藤葉月(朝井彩加)/川島緑輝(豊田萌絵)/高坂麗奈(安済知佳)) トゥッティ! 歌詞

f:id:furyu-tei:20150706143427j:image

2015-04-10

さくらのレンタルサーバ・共有SSLで .htaccess によりSSLのみのアクセス許可を設定する方法

さくらのレンタルサーバで共有SSLを使用する際に、SSLのみのアクセス許可を設定(SSLアクセス強制のため、http://〜 は https://〜 へリダイレクト)するための .htaccess の記述方法を調べてみた。


設定する .htaccess の内容

<IfModule mod_rewrite.c>
RewriteEngine On
# --- SSLアクセスでない場合(${ENV:HTTPS} が 'on' でなく、且つ、%{HTTP:X-Sakura-Forwarded-For} が未設定の場合)には
#     https://%{SERVER_NAME}%{REQUEST_URI} へリダイレクト
RewriteCond %{ENV:HTTPS} !^on$
RewriteCond %{HTTP:X-Sakura-Forwarded-For} ^$
RewriteRule . https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
</IfModule>


なお、WordPress を設置している場合(例として /wordpress/ 下)

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /wordpress/

# --- SSLアクセスでない場合(%{ENV:HTTPS} が 'on' でなく、且つ、%{HTTP:X-Sakura-Forwarded-For} が未設定の場合)には
#     https://%{SERVER_NAME}%{REQUEST_URI} へリダイレクト
RewriteCond %{ENV:HTTPS} !^on$
RewriteCond %{HTTP:X-Sakura-Forwarded-For} ^$
RewriteRule . https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /wordpress/index.php [L]

</IfModule>

のようにすればよいと思われる。

WordPress の元の .htaccess の RewriteBase 行と最初のRewriteCond 行の間に、SSLアクセス強制用の RewriteCond・RewriteRule を挟み込む。 /wordpress/(二か所)は環境にあわせて書き換えること。


また、wp-config.php の「/* That's all, stop editing! Happy blogging. */(/* 編集が必要なのはここまでです ! WordPress でブログをお楽しみください。 */)」直前に、

if ( isset($_SERVER['HTTP_X_SAKURA_FORWARDED_FOR']) ) {
    $_SERVER['HTTPS'] = 'on';
    $_ENV['HTTPS'] = 'on';
}

/* That's all, stop editing! Happy blogging. */

という記述を追加しておく。


解説(覚え書き)

さくらのレンタルサーバで共有SSLを使用する場合、以下の点に留意する必要がある。


.htaccess からの参照時
  • %{SERVER_PORT} には、SSLかそうでないかによらず '80' が設定される。
    このため、%{SERVER_PORT}ではSSL接続かどうかの判別はできない。
    「RewriteCond %{SERVER_PORT} ^80$」や「RewriteCond %{SERVER_PORT} !^443$」は常に真となるために、リダイレクトループが発生してしまう。
  • SSLアクセス時には通常、 %{ENV:HTTPS} には 'on' が、%{HTTP:X-Sakura-Forwarded-For} にはクライアント(リクエスト元)のIPアドレスが設定される。*1
    ただし、SSLアクセスした場合であっても、mod_rewrite.c の RewriteRule によりリライトされるケースでは、リライト後には %{ENV:HTTPS} が未設定となってしまう。
    RewriteRuleの[R]フラグによりhttps://〜にリダイレクトされた場合には 'on' が設定される。

上記の .htaccess では、リダイレクトするかどうかの判別に、

RewriteCond %{ENV:HTTPS} !^on$

だけでなく、

RewriteCond %{HTTP:X-Sakura-Forwarded-For} ^$

のような条件(%{HTTP:X-Sakura-Forwarded-For}が未設定)を設定している。


これをせずに %{ENV:HTTPS} のみで判定してしまうと、例えば WordPress を設置したサイト上の http://〜/wordpress/year/month/day/ というページ(URL)にアクセスした場合、

URL%{REQUEST_URI}%{ENV:HTTPS}%{HTTP:X-Sakura-Forwarded-For}mod_rewrite.cで実行されるルール結果
1http://〜/wordpress/year/month/day//wordpress/year/month/day/(未設定)(未設定)RewriteRule . https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L][A] リダイレクト
2https://〜/wordpress/year/month/day//wordpress/year/month/day/'on'<IPアドレス>RewriteRule . /wordpress/index.php [L][B] リライト
3https://〜/wordpress/year/month/day//wordpress/index.php(未設定)<IPアドレス>RewriteRule . https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L][C] リダイレクト
4https://〜/wordpress/index.php/wordpress/index.php'on'<IPアドレス>RewriteRule ^index\.php$ - [L][D](このまま)

のように、意図しないURLの PATH の書き換えが発生してしまう。

このため、[C] (3→4)のリダイレクトを発生させないように、条件を追加している。

むしろ「RewriteCond %{ENV:HTTPS} !^on$」の方は無くても現状では動作する。


PHP からの参照時
  • SSLアクセス時には、$_SERVER['HTTP_X_SAKURA_FORWARDED_FOR'] および $_ENV['HTTP_X_SAKURA_FORWARDED_FOR'] にはクライアント(リクエスト元)のIPアドレスが設定される。
  • SSLアクセスした場合であっても、mod_rewrite.c の RewriteRule によりリライトされるケースでは、$_SERVER['HTTPS'] と $_ENV['HTTPS'] が設定されない。
    RewriteRuleの[R]フラグによりhttps://〜にリダイレクトされた場合には 'on' が設定される。

WordPress 等では、SSL 接続かどうかを $_SERVER['HTTPS'] もしくは $_ENV['HTTPS'] の状態で判別している。

このため、上記のように $_SERVER['HTTP_X_SAKURA_FORWARDED_FOR'] が設定されているかどうかで SSL かどうかを判別し、SSL の場合には $_SERVER['HTTPS'] および $_ENV['HTTPS'] に 'on' を設定するような処理を追加してやる必要がある。


参考

*1:将来的にさくら側で仕様変更が行われると、%{HTTP:X-Sakura-Forwarded-For}による判別が出来なくなる可能性はある。

2015-03-14

Google App Engineのデプロイ時に認証エラーがでる場合の対策

久しぶりに GAE のアプリケーションをいじった後で、デプロイしようとすると、

Password for username: Use an application-specific password instead of your regular account password.
See http://www.google.com/support/accounts/bin/answer.py?answer=185833
However, now the recommended way to log in is using OAuth2. See
https://developers.google.com/appengine/docs/python/tools/uploadinganapp#Python_Password-less_login_with_OAuth2
2015-03-14 09:00:00,000 ERROR appcfg.py:2448 An error occurred processing file '': HTTP Error 401: Unauthorized. Aborting. 
Error 401: --- begin server output ---
Must authenticate first.
--- end server output ---

のようなエラーが出てしまった。


どうやら、アプリケーションパスワードを取得するか、OAuth2 認証を使用しろ、ということらしい(推奨はOAuth2)。


アプリケーションパスワードを取得して使用する場合には Google App Engine Launcher でもそのまま使えるが、OAuth2 認証の方だと Launcher で使用する方法が見つからなかった。コマンド プロンプトから appcfg.py を使用するしかないのかも。


アプリケーションパスワードを取得して使用する場合

エラーメッセージ内で指示されているSign in using App Passwords - Accounts Helpに書かれている通り、アプリ パスワードのページに行き、必要に応じてアプリケーションで使用しているGoogleアカウントでログイン後、アプリ パスワード(16文字)を生成する。

『端末を選択』の項目は使用している端末を選択または入力(『Windows パソコン』等)、『アプリを選択』はその他『(名前を入力)』を選んで適当な名前(『Google App Engine』など)を付ければよい。

f:id:furyu-tei:20150314114346j:image

表示されたアプリ パスワード(スペースは含まない16文字)は、他人に知られないように覚えるかメモしておく。

「このパスワードを覚えておく必要はないので……」とか書かれているが、覚えておく必要、あるよ……。


取得したアプリ パスワードは、これまで使っていたユーザーパスワードの代わりに、デプロイ時に指定する(emailはそのまま使用する)。


OAuth2 認証を使用する場合

エラーメッセージ内で指示されているPassword-less Login with OAuth2に従って、コマンド プロンプトより、

appcfg.py --oauth2 update myapp/

とする。

myapp/ は、自分のアプリケーションのフォルダを指定。

設定によっては、appcfg.py だけだと実行できないかもしれない。その場合は「python "C:\Program Files (x86)\Google\google_appengine\appcfg.py"」のようにフルパス指定して実行する。

すると、ブラウザで認証画面が開くので、画面の指示に従って認証すれば、デプロイすることが出来る。


いちいち認証手順を行いたくない場合、2回目以降は、

appcfg.py --oauth2_refresh_token=<token> update myapp/

のように token を指定して実行すればよい。


この token は、ユーザーフォルダ(%UserProfile%)にある .appcfg_oauth2_tokens に記述されているので、これをコピーして使う。

type %UserProfile%\.appcfg_oauth2_tokens

として、表示されたデータ(JSON形式)のうち、

...  "refresh_token": "<token>", ... 

のようになっている refresh_token を使用する。