WWW」カテゴリーアーカイブ

SSL証明書

いつからか、さくらのレンタルサーバでも名前ベースのバーチャルホストで SSL が使えるようになっていた (SNI) ので、このサイトにも導入してみた。

“無料のSSL証明書StartSSLを活用する” を参考に、StartSSL の SLL 証明書を取得、さくらのサーバに証明書を設置した。

これでいけるかと思ったら、エラーが出て接続できない。

www.uconst.org への接続中にエラーが発生しました。
The OCSP server has no status for the certificate. 
(エラーコード: sec_error_ocsp_unknown_cert) 

接続できないのは Firefox からで、IE8 からだと接続できる。でも IE8 だと単に OCSP を見に行ってないだけかもしれない。

ググったら こんなツイート を見つけた。CA への登録が完了してないっぽい。しょせん無料だし気長に待つか。

(追記 15:20)
今つながった。つながるまで3時間半くらいかかったかな。

Apache 2.4 で PHP (mod_php) が動かなくなってた件

先日の件はPHP-FPM に切り替えることで対処。

pkgsrc/www/php-fpm をインストール。
/etc/rc.conf に php_fom=YES を追加して php-fpm を起動。

/usr/pkg/etc/httpd/httpd.conf で以下のモジュールを有効に。

LoadModule proxy_module lib/httpd/mod_proxy.so
LoadModule proxy_fcgi_module lib/httpd/mod_proxy_fcgi.so

php5_module の設定が残っている場合は削除。

Apache の ProxyPassMatch ディレクティブを使ってPHP ファイルへのアクセスを php-fpm へ転送する設定。ProxyPassMatch の第1引数は URL のルートからのパスにマッチする正規表現、第2引数の fcgi://127.0.0.1:9000 以降は対応する PHP ファイルの絶対パスを指定する。

ProxyPassMatch ^/~alice/(.*\.php)$ fcgi://127.0.0.1:9000/home/alice/public_html/$1

…userdir が絡むと面倒だな。

pkgsrc-2014Q1 で Apache 2.4 と PHP 5.5 が動かなくなった

pkgsrc-2014Q1 がリリースされたのでアプリケーションをアップデートしていたところ、表題のように Apache が起動できなくなった。

$ sudo /etc/rc.d/apache start
Starting apache.
[Tue Apr 22 16:57:14.926914 2014] [:crit] [pid 19113:tid 3214934016] Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe.  You need to recompile PHP.
AH00013: Pre-configuration failed

tech-pkg 012766 で既に話題には出ていたようだ。

で、対処方法としては AP-PHP の代わりに PHP-FPM を使え ってことなのかな。めんどくさいなあ。

そもそも PHP-FPM って何やねん、というレベルなのだが、ぐぐったら こんなページ が見つかったので、あとで参考にさせてもらうことにしよう。

ということで、まだ壊れて動かないまま。

Google Apps Script で Bubble Chart は使えないの?

ふと思い立って Google Apps Script の学習を開始。

Google スプレッドシートのグラフ(バブルチャート bubble chart)の制御をしたかったんだけど、いろいろと解説ページをググってスプレッドシートにスクリプトでチャート(散布図 scatter chart)を貼り付ける、っていうのはできた。

function myFunction() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var app = UiApp.createApplication();

  var data = Charts.newDataTable()
  .addColumn(Charts.ColumnType.NUMBER, "X")
  .addColumn(Charts.ColumnType.NUMBER, "Y")
  .addRow([1, 1])
  .addRow([2, 0])
  .build();

  var chart = Charts.newScatterChart()
  .setDataTable(data)
  .build();

  app.add(chart);
  sheet.show(app);
}

でも、バブルチャートの作り方がわからない。 Charts に newBubbleChart() というメソッドはないようだ。

Google Chart Tools にはバブルチャートが含まれているのに、Google Apps Script の Charts Services には含まれていないってどういうこと?

WordPress に移行した

タイトルのとおり、このブログを WordPress に移行した。

これまで MovableType を使ってたんだけれども、どんどんブログ向きではない方向へ改変されているような気がして。静的なページを吐き出すところは気に入ってたのだけれども、時代的にそういうことを気にするものではないかもしれない。

本文の移行は手軽にできたのだが、パーマリンクの移行がうまくいかなかったので、修正が終わるまで古いページも残しておく。

各所のブログにあった手順を参考にしたんだけど、番号がずれてしまったのであきらめた。下書きページとか混じってたのが悪かったのかもしれない。検証はしてない。

Apache2.4

pkgsrc から Apache-2.4.3 (pkgsrc/www/apache24) をインストールした。

Apache-2.2.x といろいろ変わっていて戸惑う。必要なときにしかいじらないので、ちょっとしたデフォルト設定の変更だけでもつまづきの元だ。解説してくれてるページがあると良いのだが。

とりあえず、気づいたところだけでもメモしておく。

  • NameVitualHost が不要になった。<VirtualHost> ディレクティブだけで良い、ってことかな?このあたりの解説が見当たらないのでよくわからない。
  • デフォルトの httpd.conf で / に “Require all denied” が設定された。いくつかのディレクトリには “Require all granted” が設定されているものの、独自のディレクトリを追加する場合は “Require all granted” を設定しなければならない。
  • デフォルトの httpd.conf で mod_rewrite の読み込みがコメントアウトされた。
  • mod_cgi がなくなった(NetBSD の pkgsrc でデフォルト設定でインストールした場合)。mod_cgid をつかうようだ。
  • 起動時に “Failed to enable the ‘httpready’ Accept Filter” という warning が出る。FreeBSD ではカーネルモジュール accf_http.ko を読み込むと Accept Filter とやらが動作するそうだけど、NetBSD にはないよね?

    Apache 2.4 のドキュメントには Linux と FreeBSD のみサポートするとなっているのに、なんで組み込まれてしまっているのか。

    設定ファイル群を grep -i accept しても関連しそうな設定が見当たらないし、どうしたら無効にできるのだろう?

Zend Framework と Smarty (2)

Zend Framework の view エンジンとして Smarty を使用するやり方は、ネット上にいくらでも紹介されているし、このブログでも先日ネタにした (Zend Framework と Smarty)。

しかし、Bootstrap で Smarty を組み込むと、エラーなどで例外が発生した場合にも Smarty を使用してレンダリングすることになる。

標準の (Zend_Tool で作成した場合だったかな?) ErrorController と view スクリプト error.phtml の組み合わせは、view エンジンが Smarty に置き換えてしまっているので、そのままでは動作しない。そこで、例えば、view スクリプト error.phtml を Smarty 用に書き換えた error.tpl のようなものを自分で用意してやらなければならない。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Zend Framework Default Application</title>
</head>
<body>
<h1>An error occurred</h1>
<h2><?php echo $this->message ?></h2>
<?php if ('development' == APPLICATION_ENV): ?>
<h3>Exception information:</h3>
<p>
<b>Message:</b> <?php echo $this->exception->getMessage() ?>
</p>
<h3>Stack trace:</h3>
<pre><?php echo $this->exception->getTraceAsString() ?>
</pre>
<h3>Request Parameters:</h3>
<pre><?php echo var_export($this->request->getParams(), true) ?>
</pre>
<?php endif ?>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Zend Framework Default Application</title>
</head>
<body>
<h1>An error occurred</h1>
<h2>{$message}</h2>
<!-- ?php if ('development' == APPLICATION_ENV): ? -->
<h3>Exception information:</h3>
<p>
<b>Message:</b> {$exception->getMessage()}
</p>
<h3>Stack trace:</h3>
<pre>{$exception->getTraceAsString()}
</pre>
<h3>Request Parameters:</h3>
<pre>
{foreach from=$request->getParams() key=k item=v}
'{$k}' => '{$v}'
{/foreach}
</pre>
<!-- ?php endif ? -->
<div>smarty</div>
</body>
</html>

だが、エラーをハンドリングする場合は Smarty ではなく、Zend_View の方でレンダリングして欲しいと思うケースだってあるだろう。

ここでは、ErrorController の中で View エンジンを Zend_View に設定しなおす方法について紹介する。

結論を先に言うと、下のようなコードを追加すれば良い。

<?php
class ErrorController extends Zend_Controller_Action
{
public function errorAction()
{
if (null !== $this->view and get_class($this->view) != 'Zend_View') {
$view = new Zend_View();
$this->view = null;
$viewRenderer = $this->getHelper('ViewRenderer');
$viewRenderer->setView($view)
->setViewSuffix('phtml')
->initView();
}
$errors = $this->_getParam('error_handler');
switch ($errors->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
// 404 error -- controller or action not found
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = 'Page not found';
break;
default:
// application error
$this->getResponse()->setHttpResponseCode(500);
$this->view->message = 'Application error';
break;
}
$this->view->exception = $errors->exception;
$this->view->request   = $errors->request;
}
}

errorAction() が呼び出された時点で、$this->view は Zend_View_Smarty クラスのオブジェクトが設定されている。これを単純に Zend_View に置き換えても動作しない。$this->getHelper(‘ViewRenderer’) ($this->_helper->viewRenderer と結果は同じ) で取得できる ViewRenderer に View をセットし、かつ、View の初期設定を行わなければならない。

初期設定のための情報は ViewRenderer 内にあって、initView() ($viewRenderer->initView()) を実行すると ViewRenderer に設定された View に対する初期化が行われる。また、アクションコントローラの view ($this->view) が null だと、下のようにアクションコントローラの view に設定してくれる。

// Register view with action controller (unless already registered)
if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
$this->_actionController->view       = $this->view;
$this->_actionController->viewSuffix = $this->_viewSuffix;
}

今回調べていて感じたのだが、Zend Framework は複数の view エンジンを動的に切り替えて使うようには設計されていないようだ。

上書きする形で置き換えることはできるが、一旦設定されてしまったあとの内部の状態をキャンセルしたり、元に戻したりするのに洗練された方法が見当たらない。

なので、今回のように、Bootstrap で設定したものをアクションコントローラ内で更に設定し直すよりも、Bootstrap では変更しないでアクションの最後のほうで設定する方針としたほうが良いかもしれない。

iframe の高さを中身に合わせる

html の iframe 要素の高さを iframe の中身の高さに一致させ、iframe のスクロールバーを出さなくても全部表示されるようにする、という話。

javascript を使えば出来るらしい。

例えば、
jquery-iframe-auto-height
というのがあって、JQuery のプラグインになっている。

<script type="text/javascript" src="jquery-1.7.min.js"></script>
<script type="text/javascript" src="jquery.iframe-auto-height.plugin.1.5.0.min.js"></script>
$('iframe').iframeAutoHeight();

やってみると、IE8では動かない。いや、ヘッダの中で記述すると最初の読み込み時には動作しなくて、リロードしたときに動作する。そしてボディの iframe の後に記述すると最初の読み込み時から動作する。Firefox7 はどっちに書いても動作する。

<html>
<head>
(略)
<script>
$(document).ready(function () {
$('iframe').iframeAutoHeight();
});
</script>
</head>
<body>
(略)
<iframe src="my_iframe.html" scrolling="no" frameborder="0"></iframe>
<script>
$('iframe').iframeAutoHeight();
</script>
(略)

iframe の中身の読み込みのタイミングがブラウザ間で異なるのに起因するのかな、と思うのだけれど、イベントとして確実に捕まえるにはどうしたらよいかはわからない。

IE8の互換モード

職場のイントラネットのページレイアウトをCSSで作っていたら、どうも思ったように動作しない。

職場のページなので、ユーザは職場の人。うちの職場もしばらく前にようやく IE6 から IE8 に切り替わった(OS はまだ Windows XP だけれども)ので、CSS によるレイアウトがずいぶん楽になった。

ところが、Firefox 7 の表示結果と若干異なる。最初は 8 とはいえ、所詮 IE だから仕方がないか、とも思っていたのだが、:before { content: “text”; } のようなものも動作しない。しかも、ページによっては正常に動作するという不可解な挙動がみられた。

どういうことかというと、IE8 には IE7 などに対応する「互換モード」というのがあるのだが、イントラネットのページでは、「互換モード」がデフォルトになっているのだ(ブラウザの設定で変更することはできる)。

つまり、html ページの置かれている場所がイントラネットとみなされるかどうか、で挙動が変わってしまう、ということだ。

旧来のページを継続して使う、というケースではこのふるまいは確かにありがたいが、新規にページを作る際には邪魔な設定だ。

ユーザは自分だけではないので、ブラウザの方の設定を変えるわけにもいかない。どうすればいいのか調べてみたところ、
HTTP ヘッダか meta タグで、”X-UA-Compatible” に “IE=egde” を指定すれば良いということだった。HTTP ヘッダによる方法だとサイト全体、meta タグによる方法だとページ単位での
指定に向いている。

今回は、確かに古いページも多いので、ページごとに指定する方法を選ぶことにした。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
(略)
参考

Zend Framework で大きいファイルをダウンロード

表題の通り、Zend Framework 上で大きいファイルをダウンロードするページを作成する手順について考えてみた。

ここでいう「大きいファイル」とは、PHP の出力バッファのサイズを超えるもので、そういう大きいデータを単純に出力しようとすると途中までしか出力されない。

生の PHP だと、そういう場合には出力バッファを切るとか、適当に制御するとかすれば良いのだが、Zend Framework 上で、となると、当然ながら Zend Framework の作法に則った操作が必要となる。

調べてみると、大きく2通りの方針がありそうだ。

一つは、そういう出力を行う Zend Framework に対応したクラスを作成すること。もう一つは、Zend Framework の通常のフローによる出力をキャンセルして、自前でコントロールすることだ。

前者は、アクションコントローラの中でちょっと出力データをダウンロードさせたい、という用途には、やや大げさな気がしたので、比較的簡単そうな後者の方針を取ることにした。

ポイントは、次の通り。

  • 通常のフローによる出力を停止する。
  • ブラウザのダウンロードダイアログが表示されるよう、適切な HTTP レスポンスヘッダを出力する。
  • PHP の出力バッファを停止し、直接データを出力する。
<?php
class PublishController extends Zend_Controller_Action
{
(略)
public function releaseAction()
{
(略)
// Response::sendResponse() が FrontController から
// 呼び出されないようにする
$this->getFrontController()->returnResponse(true);
// Response オブジェクトの取得
$response = $this->getResponse();
// HTTP レスポンスヘッダの出力
$response->clearAllHeaders()
->setHeader('Content-Description', 'File Transfer')
->setHeader('Content-Type', 'application/octet-stream')
->setHeader('Content-Disposition',
sprintf('attachment; filename="%s"', $filename))
->setHeader('Content-Transfer-Encoding', 'binary')
->setHeader('Expires', '0')
->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->setHeader('Pragma', 'public')
->sendHeaders();
// 以前に何かが出力されようとしていた場合に備えて消しておく
$response->clearBody();
// PHP のバッファをクリアし、バッファを無効にする
while (ob_get_level() > 0) {
ob_end_clean();
}
// データの出力
$h = fopen('ファイル', 'rb');
while (!feof($h)) {
$c = fread($h, 4096);  // 4KB毎に入出力する。最適なサイズは?
$response->setBody($c);
$response->outputBody();
}
fclose($h);
}
}

HTTP レスポンスヘッダは PHP の readfile() のマニュアル を参考にした。

上の例では、Content-Length を出力していないが、外部コマンドの出力などのように事前にサイズが取得できない場合のケースを考えていたため。静的なファイルだったら、ファイルサイズを Content-Length に出力する方が当然良い。

他所でよく見られる例では、アクションコントローラ内で

$this->_helper->viewRenderer->setNoRender();

を実行して、

$this->getFrontController()->returnResponse(true);

を実行していない。
しかし、後者を実行しないと、通常のフローとしてフロントコントローラの dispatch 後に Response::sendResponse() が呼び出されることになる。
ここで HTTP レスポンスヘッダを出力しようとするが、アクションコントローラですでに HTTP レスポンスヘッダを出力しているので、例外が発生し、例外メッセージが(データはすでに出力しているので)データの末尾に付加されてしまう。正しいデータサイズに対応した Content-Length をつけていれば無視されるのかもしれないが、この例のように Content-Length を何らかの理由でつけていないと、データの末尾にゴミが付いてしまうことになる。

続きを読む