PHP、Smarty、ケータイサイトについて発信中。書籍 「Smarty動的webサイト構築入門」(技術評論社) 好評発売中
RSS icon Home icon
  • phpMySplitDump


    ダウンロード

    ======================================================================
    ■phpMySplitDumpとは
    ======================================================================

    MySQLのテーブルが大きすぎると、phpMyAdminやmysqldumpでエクスポートできない場合があります。
    ・phpMyAdminでエクスポート中、タイムアウトしてしまった。
    ・mysqldumpでファイル保存しようとしても、ディスク容量が不足。

    phpmyadmin、mysqldumpは、範囲指定して、一部分をエクスポートできますが、
    分割数が多くなると手動操作は難しくなります。

    そこで、自動で分割エクスポートするために、phpMySplitDumpを作りました。

    ★サイトを「メンテナンス中」にしてから、本ソフトを実行することを前提にしています。★
    ★サイトを停止せず運用中のまま、MySQLバックアップを目的としていません。★

    きっかけは、OpenPNE2系のサーバ引越しでした。
    c_imageテーブルが2500行、3GB以上あり、phpMyAdminのエクスポートはタイムアウト。
    SSHログインしてのmysqldump出力は3GB以上になるはずですが、
    レンタルサーバ(1GBプラン)のディスク容量の残りは500MBでした。

    ======================================================================
    ■対象ユーザ
    ======================================================================

    サイト管理者向けのソフトです。

    次のスキルがあると、おもしろくも何ともない、このリードミーでも、
    インストール、確認、実行がスムーズに進む可能性が高まります。

    ・MySQLの接続パラメータ
    ・PHPスクリプトの簡単な編集
    ・phpMyAdminの操作
    ・wgetの操作
    ・コマンドプロンプトの操作

    ======================================================================
    ■動作環境
    ======================================================================
    これ以前であっても動いたり、これ以降であっても動かなかい可能性があることをご理解ください。

    □サーバー側
    ・Apache
    ・MySQL4以上
    ・PHPのsystem関数で、mysqldumpを実行できること。
    ・PHP4以上
    ・PEAR

    □クライアント側
    ・Windows XP
    ・GNU wget
    ・ディスク容量の余りが充分にあること
    DB使用容量の2倍が目安です。

    ※DB使用量は、phpMyAdminで確認してください。
    ・phpMyAdminにログインしてください
    ・左側のデータベース一覧から、目的のデータベースを選択します。
    ・右側のテーブル一覧のサイズ列の一番下の欄が、DB使用量です。

    ======================================================================
    ■インストール
    ======================================================================

    1) .htmysql.ini
    1-1) .htmysql.ini.sampleをコピーして、.htmysql.iniを作成してください。
    1-2) 目的のデータベースに合わせて編集してください。

    [client]
    host=localhost
    user=dummy_1234
    password=dummy_F4opGiCf
    database=dummy_openpne

    2) config.php
    2-1) config.php.sampleをコピーして、config.phpを作成してください。
    2-2) BASIC認証のユーザ名とパスワードを設定してください。

    $GLOBALS['BASIC_AUTH_USER'] = ”;
    $GLOBALS['BASIC_AUTH_PW'  ] = ”;

    2-3) サーバのmysqldumpのパスに合わせて、設定してください。

    $GLOBALS['MYSQLDUMP_EXE'] = ‘/usr/bin/mysqldump’;

    3) ファイルのアップロード
    *.sampleを除く全ファイルを、サーバ上へアップロードしてください。
    ファイル属性、ディレクトリ属性の設定はありません。
    ここでは、http://localhost/phpMySplitDump/index.php へ設置したと仮定します。

    ======================================================================
    ■確認
    ======================================================================

    1) .htmysql.ini を表示できないことを確認してください。
    Webブラウザで http://localhost/phpMySplitDump/.htmysql.ini を表示してください。
    このとき、
    Access forbbidden
    Error 403
    となることを確認してください。

    2) Webブラウザで http://localhost/phpMySplitDump/ を表示してください。
    エラーが表示されている場合は、インストールの .htmysql.ini と config.php の設定を確認してください。

    3) 次に、http://localhost/phpMySplitDump/ の次のリンクをクリックして、ページ移動できるか確認してください。
    エラーが表示されている場合は、インストールの .htmysql.ini と config.php の設定を確認してください。

    2-1) Step1 の「wgetrc.txt」リンク
    2-2) Step2 の「phpMySplitDump.txt」リンク
    2-3) Menuの「Table Status(before dump)」リンク
    2-4) Menuの「mysqldump list」リンク
    2-5) Menuの「Table Status(after dump)」リンク

    4) http://localhost/phpMySplitDump/ で、Menuの「mysqldump list」リンクをクリックして、
    http://localhost/phpMySplitDump/list.php を表示してください。

    次のリンクをクリックしてください。

    4-1) 先頭行の create_table.sql
    ・ダウンロードが始まること
    ・保存ファイルが、空(から)でないこと
    ・保存ファイルをテキストエディタで開き、目視確認で、mysqldumpの結果として正しいこと
    ・保存ファイルをテキストエディタで開き、「error」「warning」を検索し、みつからないこと
    を確認してください。

    4-2) 2行目の xxxx.sql
    ・ダウンロードが始まること
    ・保存ファイルが、空(から)でないこと
    ・保存ファイルをテキストエディタで開き、目視確認で、mysqldumpの結果として正しいこと
    ・保存ファイルをテキストエディタで開き、「error」「warning」を検索し、みつからないこと
    を確認してください。

    ======================================================================
    ■実行
    ======================================================================

    サイトを運用したまま、練習できます。
    本番前に練習して、ファイル数、ディスク容量、所要時間を調べておいてください。

    (引越しの)本番では、サイトに「メンテナンス中」表示をして、
    ユーザや管理者をサイトから追い出してから、この作業をします。

    1) Webブラウザで http://localhost/phpMySplitDump/ を表示してください

    2) Step1にある wgetrc.txt のリンクを右クリックして、保存してください。
    保存場所は、D:\host_db\wgetrc.txt と仮定します。

    3) Step2にある phpMySplitDump.txt のリンクを右クリックして、保存してください。
    保存場所は、D:\host_db\phpMySplitDump.txt と仮定します。
    次に、拡張子を .txt から .bat へ変更してください。
    これで、D:\host_db\phpMySplitDump.bat ができました。

    4) D:\host_db\phpMySplitDump.batを実行します。
    エクスプローラの場合は、D:\host_db\phpMySplitDump.batのアイコンをダブルクリックします。
    コマンドプロンプトの場合は、
    D:
    cd \host_db
    phpMySplitDump.bat
    と入力してください。

    5) D:\host_db\local_save\ の下に、sqlファイルが保存されていきます。
    最後に、何かキーを押してください。
    参考記事:
    Usagi Project Mynets>[質問] 1.1.1から1.2.0のコンバートエラー (2008/09/15)
    ターズの備事録>Openpne 移設 (2008-09-29)

    2009-11-06 aoki 2 comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(6)


      フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (6) 前回(5)の改良版

     前回の(5)では、ブラウザと直接つながっているサーバ側スクリプトがメイン処理をしました。connection:closeとflush()を使った、変則的な方法なので、ブラウザによっては挙動が違うかもしれません。例えばケータイです。softbank 931SHでは、期待どおりだったのは(2)だけで、(1)(3)(4)(5)は期待どおりではありませんでした。

     そこで、ブラウザ – task.php – task_server.php、という構成にします。task.phpからは、サーバ内通信で、task_server.phpへリクエストします。task_server.phpは、チケットを取得し、メイン処理をします。task.php と task_server.php は私の手の届く範囲ですから、多少変則的なことをしても、ブラウザの違いによる影響を受けません。

     オモテ向きのページ遷移は、form.html ⇒ task.php ⇒ show_status.php ⇒ thanks.html となります。オモテにはでてきませんが、task_server.php がメイン処理を担当します。

    5_11 5_21

     最初は、task.phpで、ここでのポイントは1つです。

     task_server.phpとの通信に、file_get_contents()を使わないようにします。fgets()で1行読み込んだら、すぐにfclose()します。

     task_server.phpの設置場所に合わせて、YOUR_HOST、YOUR_PATHを置き換えてください。また、fopenで、http:// を使えないphp設定のwebサーバもあります。その場合は、このスクリプトは動きません。

    <?php
    $url = "http://YOUR_HOST/YOUR_PATH/task_server.php";
     
    $fp = fopen( $url, "rb" );
    assert( $fp );
    $buf = fgets( $fp );
    fclose( $fp );
     
    $vars = unserialize( $buf );
     
    session_start();
    $_SESSION['ticket_id'] = $vars['ticket_id'];
    session_write_close();
    ?>
    <meta http-equiv="refresh" content="0; url=show_status.php" />

     次は、task_server.php です。チケットIDをserializeして返します。このとき、最後に、”\n” を追加します。これがないと、task_server.phpが終わるまで、呼び出し元の task.php の fgets($fp) が終わりません。

    <?php
    require( 'ticket.php' );
     
    $ticket = new Ticket;
    $ticket_id = $ticket->create( 'Start' );
     
    $vars['ticket_id'] = $ticket_id;
    $buf = serialize( $vars );
     
    ob_start();
    echo "$buf\n";
    $buf = ob_get_clean();
    $len = strlen( $buf );
     
    header( "connection: close" );
    header( "Content-length: $len" );
    echo $buf;
    flush();
     
    // メイン処理
    for ( $i = 0; $i < 10; ++$i ) {
     sleep( 1 );
     $ticket->update( $ticket_id, "Running\t$i/10" );
    }
     
    $ticket->update( $ticket_id, 'Finished' );
    exit();
    ?>

     ケータイの softbank 931SHでの結果は、(4)と同じく、metaタグのリロード回数の制限があるのか、途中で「ページを表示できません」と表示されます。その後、手動で「更新」すると、thanks.htmlへ移動しますので、サーバ側は無事にメイン処理を終えているようです。プログレスバーをあきらめて、ページ遷移を変更するのも手です。最近のケータイ機種なら、FlashやAjaxを使う方法もありそうです。

    実験ページ(6)

     このテーマは今回で終わりです。

     task.phpとtask_server.phpの接続が変則的で気になる人向けに、もう1つ方法を紹介します。

     task.php がバッチをキューに登録します。1分に1回起動するcronジョブや常駐サービスが、キューからバッチを取り出して、処理をします。これは、先祖がえり的なものを感じますね。

    2009-04-26 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(5)


      フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (5) いきなり難しい方法

     前回の(4)ではプログレスバーを表示しました。これまでの(1)~(4)は、ブラウザへの表示処理を工夫しました。しかし、ブラウザがメイン処理の終わりを待っているという点に切り込んでいません。

     これは例えるなら次のような状況です。鈴木さん(ブラウザ)が高橋商店(サーバ)の田中さん(メイン処理)に電話をかけて、至急の仕事を頼みました。ところが、鈴木さん(ブラウザ)は電話を切らずに、田中さん(メイン処理)の仕事が終わるまで、ぼんやりと待っています。

     通常は、いったん電話を切り、田中さんが仕事を終えてから、鈴木さんへ電話で報告します。今回は、この方法に近いものを目指しました。ただし、田中さん(メイン処理)から鈴木さん(ブラウザ)へ電話で報告する、という部分が難しいです。そこで、田中さん(メイン処理)は進行状況をホワイトボードに書き、鈴木さん(ブラウザ)は不定期に電話をかけて、田中さん以外の人にホワイドボードを見てもらう、と置き換えています。

     ページ遷移は、form.html ⇒ task.php ⇒ show_status.php ⇒ thanks.html となります。オモテにはでてきませんが、ホワイトボードに相当するのは、ticket.phpです。

    5_11 5_21

     最初は、task.phpで、ポイントは3つあります。

     1つめは、進行状況の仲介役のチケットです。最初にチケットを新規発行し、メイン処理の進行に合わせて、チケットのstatusを更新します。チケットIDをURLで引き回すと、第三者がURLでアクセスする可能性があるので、セッションに保存しました。

     2つめは、レスポンスヘッダのcontent-lengthです。これを設定することで、task.phpがメイン処理を終わっていないのに、ブラウザはページロードを完了します。つまり、次のshow_status.phpへすぐに移動できるというわけです。

     3つめは、レスポンスヘッダのconnection: closeです。これがないと、ブラウザがshow_status.phpへリクエストできても、サーバ側では、すぐにはshow_status.phpを実行できず、task.php のメイン処理の終了を待ちます。

    <?php
    require( 'ticket.php' );
     
    $ticket = new Ticket;
    $ticket_id = $ticket->create( 'Start' );
     
    session_start();
    $_SESSION['ticket_id'] = $ticket_id;
    session_write_close();
     
    header( "connection: close" );
     
    ob_start();
    print( '<meta http-equiv="refresh" content="0; url=show_status.php" />' );
    $buf = ob_get_clean();
    $len = strlen( $buf );
    header( "Content-length: $len" );
    echo $buf;
    flush();
     
    // メイン処理
    for ( $i = 0; $i < 10; ++$i ) {
     sleep( 1 );
     $ticket->update( $ticket_id, "Running\t$i/10" );
    }
     
    $ticket->update( $ticket_id, 'Finished' );
    exit();
    ?>

     次は、show_status.php です。セッションからチケットIDを取り出し、進行状況を問い合わせます。 

    <?php
    require( 'ticket.php' );
     
    // セッションからチケットIDを取り出し、進行状況を問い合わせる
    session_start();
    $ticket = new Ticket;
    $status = $ticket->get( $_SESSION['ticket_id'] );
    session_write_close();
    
    if ( 'Finished' == $status ) {
     // 終了していたら、thanks.htmlへページ移動する
     echo '<meta http-equiv="refresh" content="0; url=thanks.html" />';
     
    } else if ( preg_match( '/Running\s+([0-9]+)\/([0-9]+)/', $status, $matches ) ) {
     $tpl['rcd_pos'] = $matches[1];
     $tpl['rcd_cnt'] = $matches[2];
     $tpl['percent'] = floor(100 * $tpl['rcd_pos'] / $tpl['rcd_cnt']);
     
    ?>
    <meta http-equiv="refresh" content="1" />
    <style>
    .frame {
     border: 1px solid #000;
     width: 400px;
     height: 50px;
    }
    .progress {
     width: <?php echo $tpl['percent'] ?>%;
     height: 50px;
     background: #00f;
    }
    </style>
    処理中です<br />
    全件数 : <?php echo $tpl['rcd_cnt'] ?><br />
    処理済み: <?php echo $tpl['rcd_pos'] ?><br />
    <div class="frame">
      <div class="progress">
        <br />
      </div>
    </div>
    <?php
     
    } else {
     // 1秒毎に進行状況を表示する
     echo '<meta http-equiv="refresh" content="1" />';
     echo 'show_status<br />';
     echo "$status<br />";
    }
    ?>

     最後に、ticket.php です。MySQLとPEAR::DBを使いました。idとstatusの2カラムのテーブルを作成し、DB_USER、DB_PASS、DB_HOST、DB_NAMEを置き換えてください。

    <?php
    /*
    CREATE TABLE ticket (
    `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    `status` VARCHAR( 255 ) NOT NULL
    );
    */
    require_once( 'DB.php' );
     
    class Ticket {
     var $db;
     
     function Ticket() {
      $this->db = DB::connect( 'mysql://DB_USER:DB_PASS@DB_HOST/DB_NAME' );
      if ( PEAR::isError($this->db) ) {
       die( $this->db->getMessage() );
      }
      $res = $this->db->query( "SET NAMES 'UTF8'" );
      if ( PEAR::isError($res) ) {
       die( $sth->getMessage() );
      }
      $this->db->freeResult( $res );
     }
     
     function create ( $i_status ) {
      $query = "INSERT INTO ticket(id,status) VALUES(null,?)";
      $arr = array( $i_status );
    
      $sth = $this->db->prepare( $query );
      if ( PEAR::isError($sth) ) {
       die( $sth->getMessage() );
      }
      $res = $this->db->execute( $sth, $arr );
      if ( PEAR::isError($res) ) {
       die( $sth->getMessage() );
      }
    
      $this->db->freeResult( $res );
      $this->db->freePrepared( $sth );
      
      $id = $this->db->getOne( 'SELECT LAST_INSERT_ID() FROM ticket' );
      return $id;
     }
     
     function update ( $i_id, $i_status ) {
      $query = "UPDATE ticket SET status = ? WHERE id = ?";
      $arr = array( $i_status, $i_id );
    
      $sth = $this->db->prepare( $query );
      if ( PEAR::isError($sth) ) {
       die( $sth->getMessage() );
      }
      $res = $this->db->execute( $sth, $arr );
      if ( PEAR::isError($res) ) {
       die( $sth->getMessage() );
      }
      $this->db->freeResult( $res );
      $this->db->freePrepared( $sth );
     }
     function get ( $i_id ) {
      $query = "SELECT status FROM ticket WHERE id = ?";
      $arr = array( $i_id );
      
      $status = $this->db->getOne( $query, $arr );
      return $status;
     }
    }
    ?>

     今回の方法は、ブラウザを閉じたり、[中止]ボタンを押しても、メイン処理は停止しません。キャンセルをしたい場合は、キャンセルの仕組みをチケットサーバとメイン処理に仕込む必要があります。

    実験ページ(5)

    2009-04-19 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(4)


     フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (4) 難しい方法 (プログレスバーを表示する)

     今回は、10000回のループ処理で時間がかかっている場合に、ループ処理を複数のリクエストに分けます。データベースのクエリー1回で時間がかかっている場合には、今回の方法は使えません。

     人が10000件の一覧を見るときに例えると、10000件を1ページで見るのが、前回までの方法。100件づつ100ページに分けて、次へ次へで見ていくのが、今回の方法です。

     ページ遷移は form.html ⇒ task.php ⇒ thanks.html  となります。ただし、task.phpは、1回だけではなく、task.php   ⇒ task.php?rcd_pos=0 ⇒ task.php?rcd_pos=200 と何回も呼ばれます。

     次の例は、0.01秒 x 10000件の処理です。約2秒おきに進捗状況を表示します。

     4_1 4_2

     いろいろな実装方法があります。今回の実装では、セッション変数を使いました。クエリー変数 $_GET['rcd_pos']はループ処理には利用していません。また、100回づつで分けるのではなく、2秒ごとに分けました。

    <?php
    /*
     * task.php?rcd_pos=123
     */
    define( 'MAX_PROCESS_SEC', 2 );
    session_start();
    
    if ( ! isset($_GET['rcd_pos']) ) {
     $_SESSION['rcd_cnt'] = 10000;
     $_SESSION['rcd_pos'] = 0;
     $_SESSION['time_st'] = time();
    } else {
     $t0 = time();
    
     for ( ; $_SESSION['rcd_pos'] < $_SESSION['rcd_cnt']; ++$_SESSION['rcd_pos'] ) {
      if ( MAX_PROCESS_SEC <= time() - $t0 ) {
       break;
      }
      usleep( 10000 ); // 0.01秒
     }
    }
    
    if ( $_SESSION['rcd_cnt'] <= $_SESSION['rcd_pos'] ) {
     $url = "thanks.html";
    } else {
     $url = "task.php?rcd_pos=" . $_SESSION['rcd_pos'];
    }
    
    $tpl['rcd_cnt'] = $_SESSION['rcd_cnt'];
    $tpl['rcd_pos'] = $_SESSION['rcd_pos'];
    $tpl['elapsed_sec'] = time() - $_SESSION['time_st'];
    $tpl['percent'] = floor(100 * $_SESSION['rcd_pos'] / $_SESSION['rcd_cnt']);
    ?>
    <html>
    <head>
    <meta http-equiv="refresh" content="0; url=<?php echo $url?>" />
    <style>
    .frame {
     border: 1px solid #000;
     width: 400px;
     height: 50px;
    }
    .progress {
     width: <?php echo $tpl['percent'] ?>%;
     height: 50px;
     background: #00f;
    }
    </style>
    </head>
    <body>
    処理中です<br />
    全件数 : <?php echo $tpl['rcd_cnt'] ?><br />
    処理済み: <?php echo $tpl['rcd_pos'] ?><br />
    経過時間: <?php echo $tpl['elapsed_sec'] ?>sec<br />
    
    <div class="frame">
      <div class="progress">
        <br />
      </div>
    </div>

     ブラウザがリクエストしなければ、次の処理へ進まないので、途中停止が簡単です。サーバ側にキャンセルという仕組みを用意する必要がありません。ブラウザの[中止]ボタン、[閉じる]ボタン、キャンセルページへのリンク、どれでも途中停止します。

    実験ページ(4)

    2009-04-12 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(3)


     フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (3) 簡単な方法 (進行状況を表示する)

     初回の(1)は、flush()を使って「処理中です」をすばやく表示しますが、進行状況はわかりませんでした。今回は、textaeraとjavascriptを使って、進行状況を表示します。

     ページ遷移は form.html ⇒ task.php  となります。thanks.htmlへページ移動しないのは、進行状況を表示したままにしたかったからで、他の理由はありません。必要であれば、thanks.htmlへページ移動することも可能です。

    3_1 3_2

     task.phpは、進行状況表示用のtextareaを含むページを表示して、flush()します。その後、処理のところどころで、javascriptを吐き出します。そのjavascriptが、textareaに進行状況を表示します。デバグや確認用のprint文をちりばめるのと似ています。

    <html>
    <body>
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    <!-- dummy dummy dummy dummy dummy dummy dummy dummy  -->
    処理中です<br />
    <textarea id="progress" rows="15"></textarea>
    <?php
    flush();
     
    for ( $i = 0; $i < 10; ++$i ) {
     progress( "step1: $i" );
     sleep(1);
    }
     
    progress( "step2: " );
    sleep(1);
     
    progress( "step3: " );
    sleep(1);
     
    progress( "Finished" );
     
     
    function progress ( $i_msg ) {
     $msg = escape_js( $i_msg );
     print '<script type="text/javascript">';
     print "document.getElementById('progress').value += '$msg\\n';";
     print '</script>';
     flush(); //★重要★
    }
    
    function escape_js( $i_buf ) {
     // 参考:smartyのescape修正子 {$x|escape:javascript}
     // smarty/libs/plugins/modifier.escape.php
     $replace_pairs = array(
      '\\' => '\\\\' ,
      "'"  => "\\'"  ,
      '"'  => '\\"'  ,
      "\r" => '\\r'  ,
      "\n" => '\\n'  ,
      '</' => '<\/' 
      );
     $buf = strtr($i_buf, $replace_pairs);
     return $buf;
    }
    ?>
    </body>
    </html>

     今ある仕組みを少し改造することで、進行状況を表示できるようになります。

     また、サーバ側にキャンセルという仕組みを用意する必要がありません。ブラウザの[中止]ボタン、[閉じる]ボタン、キャンセルページへのリンク、どれでも途中停止します。

    実験ページ(3)

    2009-04-05 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(2)


     フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (2) まだまだ簡単な方法

     前回の(1)ではtask.phpへのリクエスト1回だけで、「処理中です」の表示と、長い処理をしていました。今回は、これを2回のリクエストに分けてみます。ページ遷移は、form.html ⇒ task_prepare.php ⇒ task_main.php ⇒ thanks.html  となります。

     task_prepare.php は、form.htmlから送信されたフォーム変数をセッションに保存します。これはほとんど時間がかからないので、すぐに「処理中です」と表示することができます。そして、本来の処理をする task_main.php へページ移動します。

    <?php
    // $_POSTや$_FILESや$_SESSIONなどの処理
    ?>
    <html>
    <head>
      <meta http-equiv="refresh" content="0; url=task_prepare.php" />
    </head>
    処理中です
    </html>

     task_main.php は、セッション変数を読み込み、本来の処理をします。その後、thanks.htmlへページ移動します。

    <?php
    sleep( 10 ); // 長い処理のフリ
    ?>
    <meta http-equiv="refresh" content="0; url=thanks.html" />

     ユーザから見ると、(1)とほとんど同じです。一瞬、アドレスバーが変化することに気づくかもしれません。開発者から見ると、task_prepare.php、task_main.phpともに、処理と表示を分離しやすくなりました。

    実験ページ(2)

    2009-03-29 aoki No comments 02.PHP
  • 処理時間が長いとき、すばやくページ表示したい(1)


     フォーム送信後、サーバ側の処理時間が長いとき、なかなか画面が変わらないことがあります。サーバ側の処理時間が長いときでも、すばやくページを表示するいくつかの方法について説明します。

    (1) もっとも簡単な方法

     ページ遷移は form.html ⇒ task.php ⇒ thanks.html  とします。

     task.phpは「処理中です」と表示し、本来の処理をし、thanks.htmlへページ移動します。

    <html>
    ~省略~
    処理中です
    <?php flush(); ?>
    <?
    sleep( 10 ); // 長い処理のフリ
    ?>
    <meta http-equiv="refresh" content="0; url=thanks.html" />
    </html>

     flush()を呼ぶと、すぐに「処理中です」がブラウザに返されるので、フォーム入力画面が「処理中です」に切り替わります。長い処理が終わった後に、thanks.htmlへページ移動するための<meta>タグがとんでもないところにありますが、IEもFirefoxも許容してくれます。

     最近は、処理と表示を分けていることが多いので、この方法は使えないかもしれません。また、htmlとphpが混在することになるので、デザイナ、プログラマの両方から嫌な顔をされるかもしれません。

     なお、IE6では、flush()前に、約300バイト以上の文字列が必要でした。

    実験ページ(対策なし)
    実験ページ(1) 

    2009-03-22 aoki No comments 02.PHP
  • PEAR::Net_UserAgent_Mobile用のi-mode機種情報xml


     Smarty動的Webサイト構築入門の5.3 Smartyとケータイで、PEAR::Net_UserAgent_Mobileパッケージを使っています。このパッケージは外部ファイルからi-mode機種情報を読み込むことができます。本の付録CDROMに、筆者作成のdocomo_netuamobile.xmlを収録しました。

     ところが、このdocomo_netuamobile.xmlには不具合があり、下から5~6行目の「SO902WP+」以降を正しく読み込みません。次の2点の修正が必要です。

     1点目は、docomo_netuamobile.xmlです。「SO902WP+」ではなく、「SO902WPplus」とします。次のファイルは、2008/9/21時点で、288機種あります。

    docomo_netuamobile.xmlのダウンロードページ

     2点目は、keitai_ini.phpを修正して、「SO902WP+」自体を認識できるようにします。SO902WP+の場合は、Net_UserAgent_Mobile内部の機種情報を使い、そうでない場合は、docomo_netuamobile.xmlを使うようにします。

    修正前
    7: $_SERVER['DOCOMO_MAP'] = dirname(__FILE__) . “/docomo_netuamobile.xml”;
    8: $agent = &Net_UserAgent_Mobile::factory();
    9: $display = $agent->getDisplay();

    修正後
    7:  if ( ! preg_match(‘/SO902iWP[+]/’, $_SERVER['HTTP_USER_AGENT']) ) {
    8:     $_SERVER['DOCOMO_MAP'] = dirname(__FILE__) . “/docomo_netuamobile.xml”;
    9:   }
    10: $agent = &Net_UserAgent_Mobile::factory();
    11: $display = $agent->getDisplay();

    Smarty動的Webサイト構築入門
    Amazon.co.jpの詳細ページへ

  • Softbank携帯で音声3gpをダウンロードできない


     Softbank携帯(802SH)で、音声3gpをダウンロードしようとすると、「エラーが発生しました。レスポンスが不正です。(WJ46098E)」と表示される現象がありました。動画3gpは問題なくダウンロード・再生できるのに、なぜ?

     3gpファイルは、PHPで吐き出しています。ファイル名は $_SERVER['PATH_INFO'] からとってきます。こんな感じです。
    <href=”download.php/xxx/yyy.3gp”>ダウンロード</a>

     実はセッションを使っていて、session_cache_limiter(‘nocache’)が原因でした。まる一日かかった、その途中経過は...

     まず、確認したことは、
    ・PHPを使わず、3gpファイルに直接アクセスしたら?
    →携帯にダウンロードできた。
    ・”download.php/xxx/yyy.3gp にwgetでアクセスしてみたら?
    →元ファイルと同じ内容で保存でき、再生もできた。
    なので、ファイルを読んで、吐き出す処理自体に問題はないようです。

     次に、レスポンスヘッダを比較しました。セッションを使っているので、セッションIDがクッキーにありますね。むむっ?「Cache-Control」がずいぶん長い!

    直接アクセス
    cache-control: no-cache
    
    PHP吐き出し
    Set-Cookie: PHPSESSID=********; path=/
    Expires: Thu, 19 Nov 1981 08:52:00 GMT
    Cache-Control: no-store, no-cache, must-revalidate, post-check=0,
    (続き) pre-check=0
    Pragma: no-cache
    

     テスト用のPHPを組んで、どの行が原因か調べました。結果は「Cache-Control」に「no-store」があるとエラーでした。技術資料http編に「no-store」が記載されていますが、全ての機種で対応していない、ということなのでしょうか。

    Cache-Control: no-store →×
    Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0 →○
    

     session_cache_limiter の値で、レスポンスヘッダがどう変わるか調べました。

    session_cache_limiter('public' )
    ↓
    Expires: Wed, 02 Jan 2008 09:43:55 GMT
    Cache-Control: public, max-age=10800
    
    session_cache_limiter('private')
    ↓
    Expires: Thu, 19 Nov 1981 08:52:00 GMT
    Cache-Control: private, max-age=10800, pre-check=10800
    
    session_cache_limiter('private_no_expire')
    ↓
    Cache-Control: private, max-age=10800, pre-check=10800
    
    session_cache_limiter('nocache')
    ↓
    Expires: Thu, 19 Nov 1981 08:52:00 GMT
    Cache-Control: no-store, no-cache, must-revalidate, post-check=0,
    (続き) pre-check=0
    

     もともとsession_cache_limiter()を呼んでいなかったので、デフォルトの’nocache’だったようです。’private_no_expire’ にしたところ、音声3gpファイルをダウンロード・再生ができるようになりました。


    音声3gpファイル用のディレクトリに置く .htaccess の例

    AddType audio/3gpp .3gp
    AddType audio/3gpp2 .3g2
    
    Header set cache-control "no-cache"
    Header set x-jphone-copyright "no-transfer"
    

     802SHでは、「x-jphone-copyright “no-transfer”」がない場合、
    ・動画3gp → ○ダウンロード・再生
    ・音声3gp → ×「エラーが発生しました。レスポンスが不正です。(WJ46098E)」
    でした。

    2008-01-02 admin No comments 02.PHP, 04.ケータイ
  • PEAR HTTP_Client


     PEARのHTTP_ClientとHTTP_Client_cookieManagerを使ったので、その覚書き。PHP4で使った。

    簡略化したサーバー構成図?
     下図のSV1のスクリプトで、HTTP_Clientを使った。SV2はセッションを使うので、SV2用のセッションを保存/復元もしたい。

    クライアント
    ↑↓
    SV1 ゲートウェイ的webサーバー 
    ↑↓
    SV2 実際にページのあるwebサーバー

    反則してしまった
     本当は反則だが、直接 $client->_cookies を読み書きした。本来は
    (1) $client->getCookieManager()で取得したインスタンスをセッションに保存する
    (2) 次回リクエストで、セッションから復元して、HTTP_Client()の第三引数に渡す
    のだが、セッションから復元できなかったので、やむなく $client->_cookiesを触った。原因は追求しなかった。
    参考までに
     実際のスクリプトから一部抜き出して、それなりに整えたもの。動作確認はしていない。

    function MW_http_client( $i_url ) {
      $header = array();
      $header['User-Agent'] = $_SERVER['HTTP_USER_AGENT'];
      $header['Referer'   ] = $_SERVER['HTTP_REFERER'   ];
    
      $client = new HTTP_Client( $null, $header );
      $client->_cookieManager->_cookies = $_SESSION['http_client_cookies'];
      $client->setMaxRedirects( 2 );
    
      switch ( $_SERVER['REQUEST_METHOD'] ) {
      case 'GET':
        $client->get( $i_url, $_GET );
        break;
    
      case 'POST':
        $client->post( $i_url, $_POST );
        break;
      }
    
      $res = $client->currentResponse();
      $_SESSION['http_client_cookies'] = $client->_cookieManager->_cookies;
    
      return $res;
    }
    
    $res = MW_http_client( "http://xxxx/yyyy/" );
    
    header( "Content-type: {$res['headers']['content-type']}" );
    print $res['body'];
    
    2007-07-24 admin No comments 02.PHP