php、おまえだったのか。 いつもhttpメッセージを 運んでくれたのは。

51
PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。 PHPカンファレンス福岡2016 HTTPメッセージ – PHPであつかう場合の再入門 @sasezaki

Upload: sasezaki

Post on 07-Jan-2017

2.662 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

PHP、おまえだったのか。

いつもHTTPメッセージを

運んでくれたのは。

PHPカンファレンス福岡2016HTTPメッセージ – PHPであつかう場合の再入門@sasezaki

Page 2: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

● ※ PHPカンファレンス福岡2016にて利用したスライドです。http://psr7.net/sasezaki/phpconfuk2016/ も参照ください

● 概要PHPがWebのために開発され利用され20年。直観的なものであったPHPのAPIも複雑性を生み出す一因となってしまいました。抽象化の理解とその導入が煩雑さを避けるために必要となります。HTTPメッセージ利用について、PHP自体の機能とPHPプロジェクトでのアーキテクチャの変遷を踏まえつつ、開発者が意識しておきたい点を話したいと思います。SAPI・ストリーム・出力バッファリング・Middlewareについて考えていきます。

Page 3: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

4.0 よりちょっと前の頃の話(~2000年)

Page 4: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

CGICommon Gateway Interface

Page 5: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

use CGI qw(:standard);print header;print start_html('Form Example'), h1('My Example Form'), start_form, "Name:", textfield('name'), p, "Age:", textfield('age'), p, submit, end_form;if (param()) { print "Hi ", em(param('name')), "You are ", em(param('age')), "years old ";}print end_html;

HTMLフォーム (Perl CGI.pm利用)

『例解PHP』(2001年)序文より

(Rasmus Lerdorf)

Page 6: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

『当時筆者が感じたことと言えば、Perlインター

プリタを毎回起動するオーバーヘッドが馬鹿に

ならないことと、CGIプログラムをCで書くのは

とても退屈な作業だということでした。しか

し、それでもいろいろなプログラムをCで書いて

るうちに、同じコードを何度も繰り返し書いて

いることに気付きました。つまり、CGIスクリプ

トのHTML部分とCコードを分離する簡単なラッ

パーさえあれば、HTMLを変更してもCコードを

再コンパイルしなくて済む悟ったわけです。』

『例解PHP』(2001年)序文より

(Rasmus Lerdorf)

Page 7: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

<HTML><header><title>Form Example</title></header><body><h1>My Example Form</h1><form action="<?echo $PHP_SELF?>" method="POST">Name : <input type="text" name="name">Age : <input type="text" name="age"><input type="submit"></form><?if ($name):?>Hi <?echo $name?>, you are <?echo $age?> years old<?endif?></body></html>

HTMLフォーム (PHP)

『例解PHP』(2001年)序文より

(Rasmus Lerdorf)

Page 8: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

<HTML><header><title>Form Example</title></header><body><h1>My Example Form</h1><form action="<?echo $PHP_SELF?>" method="POST">Name : <input type="text" name="name">Age : <input type="text" name="age"><input type="submit"></form><?if ($name):?>Hi <?echo $name?>, you are <?echo $age?> years old<?endif?></body></html>

HTMLフォーム (PHP)

『例解PHP』(2001年)序文より

(Rasmus Lerdorf)

XSS!

Page 9: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

PHPにはその昔、リクエスト値をとことん楽に扱える機能にregister globalsやマジッククオートといったものがありました。。

Page 10: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

忘れろ!

Page 11: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

HTTPメッセージを平文で渡してやってもええんやで。

Page 12: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

サーバーリクエスト (Incoming Request )

Webサーバ

HTTPリクエスト

PHPスクリプトコード

・ $_SERVER,$_GET,$_POST,$_COOKIE,$_FILES ・ apache_request_headers() ・ php://input

Page 13: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

サーバーリクエスト

Webサーバ

HTTPリクエスト

PHPスクリプトコード

・ $_SERVER,$_GET,$_POST,$_COOKIE,$_FILES ・ apache_request_headers() ・ php://input

request startup

php_output_activate

sapi activate

zend_compile

zend_activate

zend_activate_modules

Page 14: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

かくして、PHPはHTTPリクエストと末永く暮らしていきましたとさ。

めでたし。めでた.....

Page 15: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

Warning: POST Content-Length of ... bytes exceeds the limit of

ヒー

Page 16: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

PHP、おまえだったのか。

いつもpost_max_sizeから

かばってくれていたのは

Page 17: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

HTTPリクエスト

Webサーバ

HTTPリクエスト

PHPスクリプトコード

・ $_SERVER,$_GET,$_POST,$_COOKIE,$_FILES ・ apache_request_headers() ・ php://input

スクリプト実行前に、ini値のpost_max_sizeをチェックし、最大値をこえる場合は、$_POST・$_FILESの値は空

sapi activate

Page 18: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

最大サイズ・最大長などの制限を設けてる● HashDosで知られるmax_input_varsの導入はPHP 5.3.9 から● もちろん、サーバソフト側でも制限設定項目はあり

● ApacheのLimitRequestBody ディレクティブなど

POSTメソッドでの場合の$_POSTへの変換● sapi_activateでのsapi_read_post_dataのコールにて取得

php-src/main/SAPI.c 参照● php.ini のenable_post_data_reading にて$_POST や $_FILESへの格納を無効化可能 (PHP 5.4より)

$_SERVERとリクエストヘッダー● ヘッダーが"HTTP_”プリフィクスなどはCGIの環境変数由来● Just In Timeでの利用時でのグローバル変数としての評価

http://php.net/ini.core#ini.auto-globals-jit

“有効にした場合、SERVER および ENV 変数はスクリプトの開始時ではなく、 最初に使用された時 (Just In Time) に作成される。”( php_variables.cのphp_startup_auto_globalsなど参照)

サーバーリクエストとスーパーグローバル

Page 19: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

PHPのひょうじゅんAPIつらぽよ

Page 20: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

E_WARNING: Cannot modify header information - headers already sent by (output started at ...

Page 21: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

スーパーグローバルを直に触るのはつらいheader()を先に呼ばなきゃいけないのがつらいファイル配列操作がつらいリクエストURIの操作がつらい

『PSR-7: HTTP Message Meta Document』から意訳&抜粋

Page 22: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

アプリケーション実装者が行いたいことは、リクエストを受け取って、レスポンスを生成する

Page 23: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

function dispatch($request,$response);

Page 24: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

HTTPリクエスト

ディスパッチャ ( index.php )

ブートストラップ

コントローラ

ルーティング

リクエスト

レスポンス

View / テンプレート

リクエストルーター利用でのプロジェクト構成(例)

Page 25: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

かくして、PHP開発者はHTTPメッセージと末永く暮らしていきましたとさ。

めでたし。めでた.....

Page 26: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

Fatal error: Allowed memory size of 6291456 bytes exhausted (tried to allocate 2097153 bytes)

ぶー

Page 27: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

PHP、おまえだったのか。

出力バッファリング制御をお

こなってくれてたのは

Page 28: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

メッセージボディは、文字列で決定やな。 * * @return string */ public function getContent() {

HTTPメッセージコンポーネント設計者

・・・なんや問題あるんか?

Page 29: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

何が問題? 

メモリを制限なく消費する可能性→ ヘッダやボディに最大長についてRFC規定あっただろうか? 例えばApacheの場合、LimitRequestBodyにて許可バイト数設定

開発者は諦めてアクションでecho→ getContent()にてコールバックを許容した場合、 戻り値の方が一致しない

Streamリソース利用の発想が抜けている→ HTTPクライアントとして、ストリームは利用しているのに...

Page 30: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

SAPIでの出力データの流れ(概略イメージ)

request_shut_down

php_output_end_all

php_output_stack_pop

php_output_write

Page 31: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

レスポンスボディの出力・フラッシュ標準の設定では、echoがあれば直ちに送信や文字列すべてを出力はしないパフォーマンスのためにデフォルトのphp.iniではoutput_buffering = 4096 に設定されている。※ CLIは除く

ob_start() コールバック関数ob_start() での引数、またはphp.iniでのoutput_handlerの指定により、出力バッファの内容を操作できる。 ob_gzhandler()関数など

テンプレートエンジン / Viewレンダラーでの応用出力内容を文字列として取得するために、ob_start() ob_get_contents()を行っている。ここでもメモリ使用量増大の可能性

出力出力バッファリング制御制御

Page 32: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

せや、streamや!

* * @return stream */ public function getContent() {

HTTPメッセージコンポーネント設計者

Page 33: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

抽象化 I/OPHP 4.3から登場したresourceオブジェクト普段のファイルシステムやhttpなどのスキーマは、デフォルトのラッパーにすぎない

入出力ストリームphp://temp により、メモリならびにテンポラリファイルへの読み書きが行える

ストリームフィルタストリームはカスタムフィルタを作成し、登録できる

PHPPHPにおにおけるStreamStream

Page 34: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

レスポンス作成時の例外・エラー処理レスポンス作成時の例外・エラー処理 ストリームや出力バッファリング制御 の利用により、メモリ利用量を抑えレスポンス出力時に処理を実行することは可能でしょう。 ただし、レスポンスボディ作成時の例外(DBコネク

ションエラーなど)などを考慮すると一度テンポラリーに書き出しておき、そのストリームリソースを再度渡すなどの対応も考慮すべきでは。

Page 35: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。
Page 36: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

HTTPメッセージコンポーネントがおおすぎる

Page 37: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

• Cake\Network\{Request,Response}• CI_Input, CI_Output• Nette\Http\{Request,Response}• PHPixie\HTTP\{Request,Responses}• Symfony\Component\HttpFoundation\{Request,Response}• yii\web\{Request,Response}• Zend\Http\{Request,Response}

Rob Allen 『HTTP, PSR-7 and Middleware』から

Page 38: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

依存を抑え、リクエスト・レスポンスを扱う処理

を相互運用するには?

Page 39: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

HTTPレスポンス

ディスパッチャ

ブートストラップ

コントローラ

ルーティング

リクエスト

レスポンス

View / テンプレート

セッション(独自の)

Authentication

ミドルウェア・ランナー

MiddlewareMiddleware

Middleware

Middleware

ミドルウェア利用でのプロジェクト構成例

while (! $stream->eof()) { echo $stream->read(8192);}

Page 40: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

リクエストを受け取って、レスポンスを合成し、次の処理へ渡す

Page 41: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next);

Page 42: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

psr-7はHTTPメッセージの値についてのinterfaceを定義している従来のレスポンスクラスでは、send()メソッドなんて用意していた

サーバーリクエストには、アプリケーションでの相互運用のために attributesプロパティが用意されているミドルウェアシグネチャでの議論・検討の余地従来のEventManagerを利用したプラグインとの使い分けは?HTTPクライアントでのミドルウェアはどうあるべきか?インターフェイスなどにて別途psrを定義すべきでは?

PSR-7 とミドルウェア

Page 43: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

PSR-7 PSR-7 に対する批判に対する批判

Page 44: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

最後に

● HTTPリクエストに対し、どうHTTPレスポンスを返すか?データの流れはどのような工夫が必要だったか?PHP内部と周辺プロジェクトの状況を簡単に俯瞰してみました。

●普段のコーディングでは内部の動作についてはあまり意識していないかも知れません。しかし、現実的な問題(アプリケーションロジック)に専念するためにも、再度振り返っていただければと思います。

Page 45: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

おわりです

ご清聴ありがとうございました

Page 46: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

イラスト素材●http://www.wanpug.com/●http://www.irasutoya.com/●http://hiyokoyarou.com/

Page 47: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

参考文献● php と sapi と zendengine2 と..http://www.slideshare.net/do_aki/php-and-sapi-and-zendengine2-and

● PHP による hello world 入門http://tech.respect-pal.jp/php-helloworld/

● PHP output buffer in deephttp://jpauli.github.io/2014/12/19/php-output-buffer-in-deep.html

● PSR-7: HTTP message interfaceshttp://www.php-fig.org/psr/psr-7/

● PSR-7: HTTP Message Meta Documenthttp://www.php-fig.org/psr/psr-7/meta/

● A Case for Higher Level PHP Streams in PSR-7http://mtdowling.com/blog/2014/07/03/a-case-for-higher-level-php-streams/

Page 48: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

For Further Reading- HTTP -

● 『ハイパフォーマンス ブラウザネットワーキング――ネットワークアプリケーションのためのパフォーマンス最適化』https://www.oreilly.co.jp/books/9784873116761/

● HTTP/2 Frequently Asked Questionshttp://http2.info/faq.html

● なぜH2Oを作るのか 〜HTTP/2の未踏性〜https://www.youtube.com/watch?v=ykp0fZigChs

● 新しいHTTPの話をしよう (HTTP/1.1 RFCs)http://blog.hmm.jp/entry/new-http1.1-rfcs

● メタ変数群 HTTP_* (CGI)https://wiki.suikawiki.org/n/HTTP_$1022

Page 49: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

For Further Reading- PHP -

● The Php Life Cyclehttp://www.slideshare.net/laruence/the-php-life-cycle

● Phpをいじり倒す10の方法http://www.slideshare.net/moriyoshi/php10

● php.ini-recommendedで、variables_orderがGPCSである理由と、PHP5のauto_globals_jithttp://d.hatena.ne.jp/i_ogi/20071217/1197912203

Page 50: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

For Further Reading- PHPのストリーム -

● ZF-6736: Allow writing the response body into a streamhttp://framework.zend.com/issues/browse/ZF-6736

● PHP Iterators and Streams are awesomehttp://fabien.potencier.org/php-iterators-and-streams-are-awesome.html

● Writing and using php streams and socketshttp://www.slideshare.net/auroraeosrose/writing-and-using-php-streams-and-sockets-zendcon-2011

● PHP stream for beginnershttp://hnw.jp/pdf/phpcon-20100925.pdf

● Good Parts of PHP and the UNIX Philosophyshttp://www.slideshare.net/taketyan/good-parts-of-php-and-the-unix-philosophy

● Prototype for an object oriented streams API in PHPhttps://github.com/DaveRandom/php-streams

Page 51: PHP、おまえだったのか。 いつもHTTPメッセージを 運んでくれたのは。

For Further Reading- ミドルウェア -

● 次世代の Rack や WSGI を考えてみるhttp://qiita.com/kwatch/items/67657fef43666479bb99

● HttpKernel middlewareshttps://igor.io/2013/02/02/http-kernel-middlewares.html

● beberlei/http-client-middlewarehttps://github.com/beberlei/http-client-middleware

● Overview and Features - Expressivehttps://zendframework.github.io/zend-expressive/getting-started/features/