crontabで実行時刻を適当に割り振るときの一案
どちらかというと、単なるネタ記事(何)。
cronで実行する処理で、以下の要件を満たすような場合に自分が使っている実行時刻の決め方の一つ。
- 1日1回実行されればよい
- 似たような処理が複数あるが、随時増えていく
- (同じコマンドのパラメータ違いを別々にcrontabのエントリにするとか)
- 実行時刻は問わないので、負荷分散のために適当にばらけさせたい
ただ単に「時」「分」を整然と並べても面白くないので、以下のように生成している。
- 「時」は0から23まで順番に
- 「分」は素数を順に使用(0から59までの間に17個ある)
せっかくなので(謎)、それを生成するPHPプログラムを書いてみた。
<?php $h = range(0, 23, 1); $m = array(); // 素数列を生成 for($p = 1; ($p = gmp_intval(gmp_nextprime($p))) < 60;) { $m[] = $p; } $mi = 0; $hi = 0; do { printf("%02d %02d * * *\n", $m[$mi], $h[$hi]); $mi = ($mi + 1) % count($m); $hi = ($hi + 1) % count($h); } while ($mi != 0 || $hi != 0);
使用している関数については、以下を参考に。
- gmp_nextprime
- gmp_intval
実行結果を整形して表にしてみると、以下のような感じ。(表示の都合上、crontabに書く「日」「月」「曜日」は省略)
1周目 | 2周目 | 3周目 | 4周目 | 5周目 | 6周目 | 7周目 | 8周目 | 9周目 | 10周目 | |
---|---|---|---|---|---|---|---|---|---|---|
00時台 | 02 00 | 19 00 | 47 00 | 11 00 | 37 00 | 03 00 | 23 00 | 53 00 | 13 00 | 41 00 |
01時台 | 03 01 | 23 01 | 53 01 | 13 01 | 41 01 | 05 01 | 29 01 | 59 01 | 17 01 | 43 01 |
02時台 | 05 02 | 29 02 | 59 02 | 17 02 | 43 02 | 07 02 | 31 02 | 02 02 | 19 02 | 47 02 |
03時台 | 07 03 | 31 03 | 02 03 | 19 03 | 47 03 | 11 03 | 37 03 | 03 03 | 23 03 | 53 03 |
04時台 | 11 04 | 37 04 | 03 04 | 23 04 | 53 04 | 13 04 | 41 04 | 05 04 | 29 04 | 59 04 |
05時台 | 13 05 | 41 05 | 05 05 | 29 05 | 59 05 | 17 05 | 43 05 | 07 05 | 31 05 | 02 05 |
06時台 | 17 06 | 43 06 | 07 06 | 31 06 | 02 06 | 19 06 | 47 06 | 11 06 | 37 06 | 03 06 |
07時台 | 19 07 | 47 07 | 11 07 | 37 07 | 03 07 | 23 07 | 53 07 | 13 07 | 41 07 | 05 07 |
08時台 | 23 08 | 53 08 | 13 08 | 41 08 | 05 08 | 29 08 | 59 08 | 17 08 | 43 08 | 07 08 |
09時台 | 29 09 | 59 09 | 17 09 | 43 09 | 07 09 | 31 09 | 02 09 | 19 09 | 47 09 | 11 09 |
10時台 | 31 10 | 02 10 | 19 10 | 47 10 | 11 10 | 37 10 | 03 10 | 23 10 | 53 10 | 13 10 |
11時台 | 37 11 | 03 11 | 23 11 | 53 11 | 13 11 | 41 11 | 05 11 | 29 11 | 59 11 | 17 11 |
12時台 | 41 12 | 05 12 | 29 12 | 59 12 | 17 12 | 43 12 | 07 12 | 31 12 | 02 12 | 19 12 |
13時台 | 43 13 | 07 13 | 31 13 | 02 13 | 19 13 | 47 13 | 11 13 | 37 13 | 03 13 | 23 13 |
14時台 | 47 14 | 11 14 | 37 14 | 03 14 | 23 14 | 53 14 | 13 14 | 41 14 | 05 14 | 29 14 |
15時台 | 53 15 | 13 15 | 41 15 | 05 15 | 29 15 | 59 15 | 17 15 | 43 15 | 07 15 | 31 15 |
16時台 | 59 16 | 17 16 | 43 16 | 07 16 | 31 16 | 02 16 | 19 16 | 47 16 | 11 16 | 37 16 |
17時台 | 02 17 | 19 17 | 47 17 | 11 17 | 37 17 | 03 17 | 23 17 | 53 17 | 13 17 | 41 17 |
18時台 | 03 18 | 23 18 | 53 18 | 13 18 | 41 18 | 05 18 | 29 18 | 59 18 | 17 18 | 43 18 |
19時台 | 05 19 | 29 19 | 59 19 | 17 19 | 43 19 | 07 19 | 31 19 | 02 19 | 19 19 | 47 19 |
20時台 | 07 20 | 31 20 | 02 20 | 19 20 | 47 20 | 11 20 | 37 20 | 03 20 | 23 20 | 53 20 |
21時台 | 11 21 | 37 21 | 03 21 | 23 21 | 53 21 | 13 21 | 41 21 | 05 21 | 29 21 | 59 21 |
22時台 | 13 22 | 41 22 | 05 22 | 29 22 | 59 22 | 17 22 | 43 22 | 07 22 | 31 22 | 02 22 |
23時台 | 17 23 | 43 23 | 07 23 | 31 23 | 02 23 | 19 23 | 47 23 | 11 23 | 37 23 | 03 23 |
11周目 | 12周目 | 13周目 | 14周目 | 15周目 | 16周目 | 17周目 | |
---|---|---|---|---|---|---|---|
00時台 | 05 00 | 29 00 | 59 00 | 17 00 | 43 00 | 07 00 | 31 00 |
01時台 | 07 01 | 31 01 | 02 01 | 19 01 | 47 01 | 11 01 | 37 01 |
02時台 | 11 02 | 37 02 | 03 02 | 23 02 | 53 02 | 13 02 | 41 02 |
03時台 | 13 03 | 41 03 | 05 03 | 29 03 | 59 03 | 17 03 | 43 03 |
04時台 | 17 04 | 43 04 | 07 04 | 31 04 | 02 04 | 19 04 | 47 04 |
05時台 | 19 05 | 47 05 | 11 05 | 37 05 | 03 05 | 23 05 | 53 05 |
06時台 | 23 06 | 53 06 | 13 06 | 41 06 | 05 06 | 29 06 | 59 06 |
07時台 | 29 07 | 59 07 | 17 07 | 43 07 | 07 07 | 31 07 | 02 07 |
08時台 | 31 08 | 02 08 | 19 08 | 47 08 | 11 08 | 37 08 | 03 08 |
09時台 | 37 09 | 03 09 | 23 09 | 53 09 | 13 09 | 41 09 | 05 09 |
10時台 | 41 10 | 05 10 | 29 10 | 59 10 | 17 10 | 43 10 | 07 10 |
11時台 | 43 11 | 07 11 | 31 11 | 02 11 | 19 11 | 47 11 | 11 11 |
12時台 | 47 12 | 11 12 | 37 12 | 03 12 | 23 12 | 53 12 | 13 12 |
13時台 | 53 13 | 13 13 | 41 13 | 05 13 | 29 13 | 59 13 | 17 13 |
14時台 | 59 14 | 17 14 | 43 14 | 07 14 | 31 14 | 02 14 | 19 14 |
15時台 | 02 15 | 19 15 | 47 15 | 11 15 | 37 15 | 03 15 | 23 15 |
16時台 | 03 16 | 23 16 | 53 16 | 13 16 | 41 16 | 05 16 | 29 16 |
17時台 | 05 17 | 29 17 | 59 17 | 17 17 | 43 17 | 07 17 | 31 17 |
18時台 | 07 18 | 31 18 | 02 18 | 19 18 | 47 18 | 11 18 | 37 18 |
19時台 | 11 19 | 37 19 | 03 19 | 23 19 | 53 19 | 13 19 | 41 19 |
20時台 | 13 20 | 41 20 | 05 20 | 29 20 | 59 20 | 17 20 | 43 20 |
21時台 | 17 21 | 43 21 | 07 21 | 31 21 | 02 21 | 19 21 | 47 21 |
22時台 | 19 22 | 47 22 | 11 22 | 37 22 | 03 22 | 23 22 | 53 22 |
23時台 | 23 23 | 53 23 | 13 23 | 41 23 | 05 23 | 29 23 | 59 23 |
参照はあいまい
いわゆるJavaの初心者がどつぼにはまりがちな、「参照はあいまい」と言われて戸惑うケース。
(NGコード)
package t2014_1008_01; import java.util.*; import java.sql.*; public class Main { public static void main(String[] args) { Date now = new Date(); System.out.println(now); // なにかDBへアクセスする処理をこの後に書く想定・・ } }
これをコンパイルすると、次のようなエラーメッセージが出力されます。
- 出力言語(環境)が日本語の場合
$ LANG=ja_JP.UTF-8 javac -d classes src/t2014_1008_01/Main.java src/t2014_1008_01/Main.java:9: Date の参照はあいまいです。java.sql の クラス java.sql.Date と java.util の クラス java.util.Date が両方適合します。 Date now = new Date(); ^ src/t2014_1008_01/Main.java:9: Date の参照はあいまいです。java.sql の クラス java.sql.Date と java.util の クラス java.util.Date が両方適合します。 Date now = new Date(); ^ エラー 2 個
- 出力言語(環境)が英語の場合
$ LANG=C javac -encoding UTF-8 -d classes src/t2014_1008_01/Main.java src/t2014_1008_01/Main.java:9: reference to Date is ambiguous, both class java.sql.Date in java.sql and class java.util.Date in java.util match Date now = new Date(); ^ src/t2014_1008_01/Main.java:9: reference to Date is ambiguous, both class java.sql.Date in java.sql and class java.util.Date in java.util match Date now = new Date(); ^ 2 errors
(Java6で試していますが、それ以前のバージョンだともっとメッセージが不親切だったりするかもしないかも・・)
(CentOS 6.5で試していますが、WindowsでもMacOSでも理屈は同じ。)
で、仮に、「java.util.Date」を使うことを想定しているとして、ネットでちゃちゃっと調べて、「あぁ、『java.util.』をつければいいんだな」と、以下のように修正します。
(NGコード)
package t2014_1008_01; import java.util.*; import java.sql.*; public class Main { public static void main(String[] args) { java.util.Date now = new Date(); System.out.println(now); // なにかDBへアクセスする処理をこの後に書く想定・・ } }
気づく人はすぐに気づくでしょうが、これだけでは解決しません。
「new Date()」の方の型名がまだ「あいまい」ですから・・
(コンパイラは、好意的な解釈はせず、あくまで機械的に解釈しますから・・)
(OKコード)
package t2014_1008_01; import java.util.*; import java.sql.*; public class Main { public static void main(String[] args) { java.util.Date now = new java.util.Date(); System.out.println(now); // なにかDBへアクセスする処理をこの後に書く想定・・ } }
このようにしないといけません。
「わぁ、エラーメッセージだ、ググってみよう」と一つの流れのように機械的に対処しようとすると、探し当てたサンプルが自分のはまっている問題にピッタリ合致しないものだった場合に、このような罠にはまります。
辛くても、最初のコードをコンパイルしたときのエラーメッセージを「しっかり」見てみると、「2個のエラーが出ている」ことが分かります。
- 一つは、変数宣言で型を指定するときの「Date」があいまい。
- もう一つは、オブジェクトをnewする際に指定する「Date」があいまい。
まぁ、そもそも、import文には、「ワイルドカード指定よりも個別指定の方が優先される」という仕様があるので、「java.util.Date」と「java.sql.Date」の両方を使う必要性がない限り、以下のように書けば十分なんですが・・
(OKコード)
package t2014_1008_01; import java.util.Date; // 個別指定 import java.util.*; // ワイルドカード指定 import java.sql.*; // ワイルドカード指定 public class Main { public static void main(String[] args) { Date now = new Date(); System.out.println(now); // なにかDBへアクセスする処理をこの後に書く想定・・ } }
そもそも、IDEも普及している現代では、私はワイルドカード指定「非」推奨派ですが・・
・・と言いながらも、時々めんどくさくなって、テキストエディタでコードを書くときがある奴(ぇ
switch文の罠
PHPで(知らずに)以下のようなコードを書いていてはまった。
<?php $sum = 0; foreach (range(1, 5) as $i) { $val = $i; printf("%3d\n", $val); switch ($val) { case 1: printf("%5d: %s\n", $val, "One"); break; case 2: printf("%5d: %s\n", $val, "Two"); break; case 3: printf("%5d: %s\n", $val, "Three"); continue; case 4: printf("%5d: %s\n", $val, "Four"); case 5: printf("%5d: %s\n", $val, "Five"); break; } $sum += $val; } printf("sum=%d\n", $sum);
実行すると以下のような結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=15
‥あれ、「$val == 3」のときはスキップされるから、「sum=12」になるはず‥
ためしに色んな言語で(時には無理矢理(謎))同じことをしてみる。
Javaの場合
public class Hoge { public static void main(String[] args) { int sum = 0; for (int i = 0; i < 5; ++i) { int val = i + 1; System.out.printf("%3d%n", val); switch (val) { case 1: System.out.printf("%5d: %s%n", val, "One"); break; case 2: System.out.printf("%5d: %s%n", val, "Two"); break; case 3: System.out.printf("%5d: %s%n", val, "Three"); continue; case 4: System.out.printf("%5d: %s%n", val, "Four"); case 5: System.out.printf("%5d: %s%n", val, "Five"); break; } sum += (val); } System.out.printf("sum=%d%n", sum); } }
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」になる。
C言語の場合
#include <stdio.h> int main(int argc, char* argv[]) { int i; int sum = 0; int val; for (i = 0; i < 5; ++i) { val = i + 1; printf("%3d\n", val); switch (val) { case 1: printf("%5d: %s\n", val, "One"); break; case 2: printf("%5d: %s\n", val, "Two"); break; case 3: printf("%5d: %s\n", val, "Three"); continue; case 4: printf("%5d: %s\n", val, "Four"); case 5: printf("%5d: %s\n", val, "Five"); break; } sum += val; } printf("sum=%d\n", sum); }
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
bash
#! /bin/bash sum=0 for ((i = 0; i < 5; ++i)); do val=$((i + 1)) printf "%3d\n" $val case $val in 1) printf "%5d: %s\n" $val "One" ;; 2) printf "%5d: %s\n" $val "Two" ;; 3) printf "%5d: %s\n" $val "Three" continue; ;; 4|5) case $val in 4) printf "%5d: %s\n" $val "Four" ;; esac printf "%5d: %s\n" $val "Five" ;; esac sum=$((sum + val)) done printf "sum=%d\n" $sum
break文に当たる「;;」を省略できないので、fall throughの部分は無理矢理だが。
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」
Perl
Perlは書けないので、以下の辺りを参考に。
use Switch; my $i; my $sum = 0; for ($i = 0; $i < 5; ++$i) { my $val = $i + 1; printf("%3d\n", $val); switch ($val) { case 1 { printf("%5d: %s\n", $val, "One"); } case 2 { printf("%5d: %s\n", $val, "Two"); } case 3 { printf("%5d: %s\n", $val, "Three"); next; } case [4, 5] { switch ($val) { case 4 { printf("%5d: %s\n", $val, "Four"); } } printf("%5d: %s\n", $val, "Five"); } } $sum += $val; } printf("sum=%d\n", $sum);
同じくfall throughの部分は無理矢理。
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=15
‥あれ、「sum=15」だ‥
下記で追調査。
Ruby
Rubyも書けないので、以下の辺りを参考に。
ちなみに、脱線するが、この辺りが面白かった。
sum=0 for i in 1..5 do val = i; printf("%3d\n", val); case val when 1 printf("%5d: %s\n", val, "One"); when 2 printf("%5d: %s\n", val, "Two"); when 3 printf("%5d: %s\n", val, "Three"); next; when 4, 5 case val when 4 printf("%5d: %s\n", val, "Four"); end printf("%5d: %s\n", val, "Five"); end sum += val; end printf("sum=%d\n", sum);
同じくfall throughの部分は無理矢理。
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
一旦まとめると‥
言語 | switch(case)文の中でのループスキップ |
---|---|
PHP | ×(※1) |
Java | ○ |
C言語 | ○ |
bash | ○ |
Perl | ×(※2) |
Ruby | ○ |
と、こんな感じになった。
‥って書いちゃうと誤解を招くので、さっさと追調査。
(※1)PHPの場合の仕様
実は、マニュアルの中にこんなことが書いてあった。
注意: 他の言語とは違って、 continue命令は switchにも適用され、breakと同じ動作をします。 ループの内部でswitchを使用しており、 外側のループの処理を続行させたい場合には、continue 2 を使用してください。
‥おぉ!
ということで、以下のように書き直してみる。
<?php $sum = 0; foreach (range(1, 5) as $i) { $val = $i; printf("%3d\n", $val); switch ($val) { case 1: printf("%5d: %s\n", $val, "One"); break; case 2: printf("%5d: %s\n", $val, "Two"); break; case 3: printf("%5d: %s\n", $val, "Three"); continue 2; case 4: printf("%5d: %s\n", $val, "Four"); case 5: printf("%5d: %s\n", $val, "Five"); break; } $sum += $val; } printf("sum=%d\n", $sum);
実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
この構文、もちろん単なる多重ループのときにも有効で、変なラベル(何)をつける必要がないというメリットがある。一方で、ぱっと見ではどこに飛ぶのかが分からず、ループの数を数え間違えるとひどい事になるというデメリットがある。
(※2)Perlの場合の仕様
(PHPと同じように書いてみたときのエラーメッセージからたどり着いたのは内緒(謎))
Perlでは、ラベルによって、どのループに対する「next」なのかを指定できる。
そもそも、PerlのSwitchモジュールにおける「next」はfall throughのためのものらしい。
つまり、先のPerlプログラム中の「case 3」よりも下に、$valが3のときにマッチするcase句があったら違う結果を引き起こしていたというわけだ。怖い怖い‥
というわけで、上記を踏まえて書き直したのが以下。
use Switch; my $i; my $sum = 0; loop: for ($i = 0; $i < 5; ++$i) { my $val = $i + 1; printf("%3d\n", $val); switch ($val) { case 1 { printf("%5d: %s\n", $val, "One"); } case 2 { printf("%5d: %s\n", $val, "Two"); } case 3 { printf("%5d: %s\n", $val, "Three"); next loop; } case 4 { printf("%5d: %s\n", $val, "Four"); next; } case [4, 5] { printf("%5d: %s\n", $val, "Five"); } } $sum += $val; } printf("sum=%d\n", $sum);
Switchモジュールのfall throughの仕様をちゃんと使ってみた。
また、この「ラベル」の概念はJavaでも同じですね。(もっと言えば、悪しきモノとして封印されているC言語のgotoとか(略))
で、実行結果。
1 1: One 2 2: Two 3 3: Three 4 4: Four 4: Five 5 5: Five sum=12
うん、「sum=12」。
関数やメソッドの引数のタイプヒント指定の罠
久しぶりにネタとしてメモしておきたい事象にぶつかったのでメモ。
「罠」とは言っても、熟練のPHPerにとっては当たり前のことなんだろうけど‥
<?php function hoge_int(int $val) { var_dump(__FUNCTION__, $val); } hoge_int(10);
さて、これを実行してみると‥
PHP Catchable fatal error: Argument 1 passed to hoge_int() must be an instance of int, integer given, called in C:\temp\hoge.php on line 7 and defined in C:\temp\hoge.php on line 3
一瞬、何のことだか意味が分かりません。
渡しているのはint(integer)値だし、タイプヒント指定と食い違っているようには見えない。
そこで、エラーメッセージを元に検索してみると、以下のサイトを見つけた。
まさかと思いながら、以下のコードを試してみる。
<?php function hoge_int(int $val) { var_dump(__FUNCTION__, $val); } final class int { private $_val; public function __construct($val) { if (is_int($val)) { $this->_val = $val; } else { trigger_error('No, you fool!', E_USER_NOTICE); } } public function __toString() { return (string)$this->_val; } } hoge_int(new int(10));
すると、以下の出力を得ることができる。関数が正常に呼ばれた証拠。
string(8) "hoge_int" object(int)#1 (1) { ["_val":"int":private]=> int(10) }
さて、先のエラーメッセージをよーく読んでみると、こんな風に書いてある。
must be an instance of int, integer given
「instance of int」を渡すべきところで「integer」を渡している、と。
そこで、さらに以下のようなことをしてみる。
<?php function hoge_int(int $val) { var_dump(__FUNCTION__, $val); } final class int { private $_val; public function __construct($val) { if (is_int($val)) { $this->_val = $val; } else { trigger_error('No, you fool!', E_USER_NOTICE); } } public function __toString() { return (string)$this->_val; } } final class integer { private $_val; public function __construct($val) { if (is_integer($val)) { $this->_val = $val; } else { trigger_error('No, you fool!', E_USER_NOTICE); } } public function __toString() { return (string)$this->_val; } } hoge_int(new integer(10));
すると、こんなエラーメッセージが。
PHP Catchable fatal error: Argument 1 passed to hoge_int() must be an instance of int, instance of integer given, called in C:\temp\hoge.php on line 35 and defined in C:\temp\hoge.php on line 3
今度は、「instance of int」を渡すべきところで「instance of integer」を渡している、と書かれている。
おぉ、いわゆるオブジェクト型でないといけないということじゃないか‥
あんまり話を引っ張っても別に面白くないので、上記サイトからもリンクされているPHPのマニュアル「Type Hinting/タイプヒンティング」を見てみる。
よく読めばちゃんと書いてあるじゃないか‥
Type hints can not be used with scalar types such as int or string.
タイプヒントは int や string といったスカラー型には使えません。
マニュアルによれば、タイプヒントとして指定可能なのは以下のものらしい。
また、タイプヒントが付いたパラメータにはNULLを渡すことはできないが、パラメータのデフォルト値として「= null」と指定しておけばnullを渡すことも可能らしい。
じゃあ、スカラー型の指定を強制したい場合はどうするかというと、冒頭に挙げたサイトにも書いてあるが、
<?php function hoge_int($val) { if (!is_int($val)) { trigger_error('No, you fool!', E_USER_ERROR); } var_dump(__FUNCTION__, $val); } hoge_int(10);
というコートを書くしかないらしい。
なんか、Autoboxing/Unboxingが実装される前のJavaでプリミティブ型の値をラッパークラスを使ってオブジェクトにしてからListに放り込むのめんどくさい‥なんてことをしていたのを思い出した‥
よかった探しリース
←左手【よかった探しリース】右手→
今年も「よかった探しリース」に参加させていただきます。
今年は後半から復活の兆しが見え、自分にできることをマイペースでやっていこうと思い始めている今日この頃です。
- 家族が大過なく過ごせたこと
- 私自身もだいぶ調子が戻ってきました
- 古巣に近いところに転職したが、皆温かく迎えてくれたこと
- と同時に、前職で大変なこともあったけど、無駄な経験ではなかったこと
- 「艦これ」楽しい
- でも、リアル戦争はダメ!絶対!
- ドラマ「ドクターX」(テレビ朝日系)の第二期も面白い(現在進行形(謎))
- アニメ「ログ・ホライズン」(NHK)が意外と面白い
- エンディング曲を歌う歌手「Yun*chi」の今後に期待
- HTML5/CSS3を勉強し始めて、やってみると意外に面白いなと気づいたこと
- さっそく実践投入中
- 映画「風立ちぬ」を久々の映画館で鑑賞‥映画を観て泣いたのも久々な気がする
- やっぱり、リアル戦争は悲しみしか生まないな‥
牛の歩み(何)で少しずつ追加していきます。
音楽ファイルや動画ファイルの取得にも認証を導入したときにぶつかった問題
もう既に2年近く前、Android 2.3がまだ新しいと言われていた頃の話だが、当時は相当苦しんだので、検証記録として残してみる。
はじめに
とあるWebアプリケーションの開発を担当している人から、「Androidの実機だと音が再生されない。どうにも原因が分からないので助けて欲しい」という相談を受け、その辺の知識もほとんどないままにヘルプすることになった。
話を聞いてみると、以下の状況ということが分かった。
下準備
せっかくだからということで、今回、audioタグとvideoタグ、比較としてimgタグを使った検証をしようとテストプログラムを作っていたら、動画に関しては生半可な対応では再生されなかったので(どうやらHTTP 206(Partial Content)を使用するらしい)、Apache httpdのxsendfileモジュールを使用した。
導入に関しては、ここでの説明は手抜きするが、以下のサイトを参考にした。
ざっくり書くと、以下のような感じ。(CentOS 6.4での実行例)
# yum groupinstall "Development Tools" # yum install httpd httpd-devel # curl -o mod_xsendfile.c https://tn123.org/mod_xsendfile/mod_xsendfile.c # apxs -cia mod_xsendfile-0.12/mod_xsendfile.c # vi /etc/httpd/conf.d/xsendfile.conf <IfModule mod_xsendfile.c> XsendFile on XsendFilePath /var/www/html </IfModule> # service httpd configtest # service httpd restart
認証無しのケースでの確認
「そもそも再生されない」っていうことだと困るので、まずは以下のプログラムで試す。
<?php $user = '-'; $time = time(); $msg = array(); $msg[] = "HTML5Test[{$_SERVER['REQUEST_METHOD']}]"; $msg[] = "\"{$user}\""; if (isset($_GET['file'], $_GET['type'])) { $size = filesize($_GET['file']); header("Content-Length: {$size}"); header("Content-Type: {$_GET['type']}"); header("X-Sendfile: {$_GET['file']}"); $msg[] = "\"file={$_GET['file']}&type={$_GET['type']}\""; } else { $msg[] = "\"file=view&type=text/html\""; ?> <div> <img src="?file=image1.png&type=image/png&time=<?php echo $time;?>" /> </div> <div> <audio src="?file=audio1.mp3&type=audio/mpeg&time=<?php echo $time;?>" autoplay="autoplay" controls="controls"> audio not supported </audio> </div> <div> <video src="?file=video1.mp4&type=video/mp4&time=<?php echo $time;?>" autoplay="autoplay" controls="controls" width="320" height="240"> video not supported </video> </div> <?php } $msg[] = "\"{$_SERVER['HTTP_USER_AGENT']}\""; $msg[] = "\"{$_SERVER['REQUEST_URI']}\""; trigger_error(implode(' ', $msg), E_USER_NOTICE); ?>
timeパラメータは、一応のキャッシュ対策。
これで、以下のケースを試してみる。
- PCのGoogle Chrome
- Android 2.3(Galaxy S)の標準ブラウザ
- Android 4.2(Galaxy S4)の標準ブラウザ
実際にアクセスすると、Apache httpdのエラーログに以下のようなログが記録される。(PCのGoogle Chromeでの出力例)
[Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=view&type=text/html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/ [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=image1.png&type=image/png" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=image1.png&type=image/png&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=audio1.mp3&type=audio/mpeg" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=audio1.mp3&type=audio/mpeg&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=video1.mp4&type=video/mp4&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=video1.mp4&type=video/mp4&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=video1.mp4&type=video/mp4&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php
ちなみに、書いてあるIPアドレスはすべてプライベートアドレスなので、アタックしようとしても無駄よん、念のため(謎)。
オレオレ認証処理を追加して検証
今回、認証機構を作るのが目的ではないので、ユーザIDを入力してPOSTすると、それをセッションに保存しておき、そのデータの有無で認証済みかどうかを判定するという手抜きな仕組みで代用する。(PHPのセッションはクッキーに保存されたIDで識別する設定になっていて、今回の要件には合っているので)
<?php session_start(); if (isset($_POST['logout'])) { unset($_SESSION['authorized']); } if (isset($_POST['username'])) { $_SESSION['authorized'] = $_POST['username']; } $user = isset($_SESSION['authorized']) ? $_SESSION['authorized'] : '-'; $time = time(); $msg = array(); $msg[] = "HTML5Test[{$_SERVER['REQUEST_METHOD']}]"; $msg[] = "\"{$user}\""; if (isset($_SESSION['authorized'])) { if (isset($_GET['file'], $_GET['type'])) { $size = filesize($_GET['file']); header("Content-Length: {$size}"); header("Content-Type: {$_GET['type']}"); header("X-Sendfile: {$_GET['file']}"); $msg[] = "\"file={$_GET['file']}&type={$_GET['type']}\""; } else { $msg[] = "\"file=view&type=text/html\""; ?> <div> <img src="?file=image1.png&type=image/png&time=<?php echo $time;?>" /> </div> <div> <audio src="?file=audio1.mp3&type=audio/mpeg&time=<?php echo $time;?>" autoplay="autoplay" controls="controls"> audio not supported </audio> </div> <div> <video src="?file=video1.mp4&type=video/mp4&time=<?php echo $time;?>" autoplay="autoplay" controls="controls" width="320" height="240"> video not supported </video> </div> <hr /> <form action="" method="post"> <input type="submit" name="logout" value="Logout" /> </form> <?php } } else { $msg[] = "\"file=login&type=text/html\""; ?> <form action="" method="post"> <input type="text" name="username" /><br /> <input type="submit" value="Authz" /> </form> <?php } $msg[] = "\"{$_SERVER['HTTP_USER_AGENT']}\""; $msg[] = "\"{$_SERVER['REQUEST_URI']}\""; trigger_error(implode(' ', $msg), E_USER_NOTICE); ?>
認証無しのケースから変わったのは以下。
- 3〜10行目でオレオレ認証情報の取得などをしている。一応、ログアウト機能も実装。
- 17行目で認証済みかどうかの条件判定
- 46〜59行目でログアウトのためのフォームと認証のためのフォームを、それぞれの場合分けに従って表示。
これで先ほどと同じケースを試してみる。(今度は、すべてのケースでのログを、見やすいように余分な情報を除去して載せてみる)
HTML5Test[GET] "-" "file=login&type=text/html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php" in /var/www/html/test-auth.php on line 65 HTML5Test[POST] "hhelibex" "file=view&type=text/html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=image1.png&type=image/png" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=image1.png&type=image/png&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=audio1.mp3&type=audio/mpeg" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php
HTML5Test[GET] "-" "file=login&type=text/html" "Mozilla/5.0 (Linux; U; Android 2.3.6; ja-jp; SC-02B Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/ HTML5Test[POST] "hhelibex-2.3" "file=view&type=text/html" "Mozilla/5.0 (Linux; U; Android 2.3.6; ja-jp; SC-02B Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex-2.3" "file=image1.png&type=image/png" "Mozilla/5.0 (Linux; U; Android 2.3.6; ja-jp; SC-02B Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "/test-auth.php?file=image1.png&type=image/png&time=1386308864" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "-" "file=login&type=text/html" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386308864" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-2.3" "file=video1.mp4&type=video/mp4" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308864" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-2.3" "file=video1.mp4&type=video/mp4" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308864" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-2.3" "file=video1.mp4&type=video/mp4" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308864" in /var/www/html/test-auth.php on line 65
- Android 4.2(Galaxy S4)の標準ブラウザ
HTML5Test[GET] "-" "file=login&type=text/html" "Mozilla/5.0 (Linux; Android 4.2.2; ja-jp; SC-04E Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19" "/test-auth.php" in /var/www/html/test-auth.php on line 65 HTML5Test[POST] "hhelibex-4.2" "file=view&type=text/html" "Mozilla/5.0 (Linux; Android 4.2.2; ja-jp; SC-04E Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex-4.2" "file=image1.png&type=image/png" "Mozilla/5.0 (Linux; Android 4.2.2; ja-jp; SC-04E Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19" "/test-auth.php?file=image1.png&type=image/png&time=1386309043" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex-4.2" "file=audio1.mp3&type=audio/mpeg" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=audio1.mp3&type=audio/mpeg" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65
ログだけではちょっと分からないと思うが、以下のような動作をしている。
- PCのGoogle Chromeでは、音声、動画ともに自動再生される。
- Android 2.3では、音声は自動再生される(しようとする)が、動画は再生ボタンを押さないと再生されない。
- Android 4.2では、音声、動画ともに自動再生される。
- ただ、複数のメディアの同時再生はできないようで、音声再生中に動画の再生が始まると、音声の再生が一時停止するらしい。逆も同じ。
ちなみに、動画へのアクセスのログが多いのは、再生時に複数回に分けて分割して取得していたりするからだと思われる。
で、パッと見ただけでは分かりにくいかもしれないが、Android 2.3の音声ファイル取得のログが以下のようになっている。
HTML5Test[GET] "-" "file=login&type=text/html" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386308864" in /var/www/html/test-auth.php on line 65
まず、ユーザ名が取得できていないことから、セッション識別に必要なクッキーが送られていないと推測される。「file=login」と出力されていることからも、ベースとなるHTML文書の取得とセッションを共有できていないことが分かる。
と、それ以前に、UAが「stagefright/1.1」となっていて、HTMLファイルや画像ファイルとは異なるUAによってアクセスされていることも分かる。
Android 4.2では「stagefright/1.2」が使用され、ユーザ名もちゃんと取れていることから、Android 2.3におけるバグなのかな、と推測される(Android 3.xの端末がないのでそこの検証はできないが‥)
margin/border/paddingの指定方法メモ
CSSのmargin/border/paddingは、四辺を一括で指定する方法から四辺を個別に指定する方法まであって、どの形式でどの辺が影響を受けるのかがしょっちゅう分からなくなるので、自分用のメモ。
一応、以下のブラウザで確認。
- Internet Explorer 10
- Safari(Windows版) 5.1
- Google Chrome 31
- Firefox 25
- Opera 18
本当はブログ記事上で表現できればよかったのだが、はてな記法をエスケープしたりするのが面倒だったので、画面キャプチャを貼り付け(ぉ
ちなみに、使用したソースは以下のとおり。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>margin/border/padding</title> </head> <body> <style type="text/css"> .my_root { background-color: #888; margin: 0; border-width: 0; padding: 10px; } .my_frame { border-width: 2px; border-color: yellow; border-style: solid; display: inline-block; } .my_sample { border-style: solid; background-color: #fff; } .my_sample1_1 { margin: 10px; border-width: 20px; border-color: #f00; padding: 40px; } .my_sample2_1 { margin: 10px; border-width: 10px; border-color: #f00; padding: 10px; } .my_sample2_2 { margin: 10px 20px; border-width: 10px 20px; border-color: #f00 #0f0; padding: 10px 20px; } .my_sample2_3 { margin: 10px 20px 30px; border-width: 10px 20px 30px; border-color: #f00 #0f0 #00f; padding: 10px 20px 30px; } .my_sample2_4 { margin: 10px 20px 30px 40px; border-width: 10px 20px 30px 40px; border-color: #f00 #0f0 #00f #ff0; padding: 10px 20px 30px 40px; } </style> <div class="my_root"> <div class="my_frame"> <pre class="my_sample my_sample1_1"> <外から順に margin border padding > margin: 10px; border-width: 20px; border-color: #f00; padding: 40px; </pre> </div> <div style="clear: both;"></div> <div class="my_frame"> <pre class="my_sample my_sample2_1"> <四辺すべて> margin: 10px; border-width: 10px; border-color: #f00; padding: 10px; </pre> </div> <div class="my_frame"> <pre class="my_sample my_sample2_2"> <上下><左右> margin: 10px 20px; border-width: 10px 20px; border-color: #f00 #0f0; padding: 10px 20px; </pre> </div> <div style="clear: both;"></div> <div class="my_frame"> <pre class="my_sample my_sample2_3"> <上><左右><下> margin: 10px 20px 30px; border-width: 10px 20px 30px; border-color: #f00 #0f0 #00f; padding: 10px 20px 30px; </pre> </div> <div class="my_frame"> <pre class="my_sample my_sample2_4"> <上><右><下><左> margin: 10px 20px 30px 40px; border-width: 10px 20px 30px 40px; border-color: #f00 #0f0 #00f #ff0; padding: 10px 20px 30px 40px; </pre> </div> </div> </body> </html>