Dockerコンテナ内から外部通信が向いたときにDNSが解決できない問題を解消する
はじめに
どうも、最近海外ドラマで特にNetflix系のドラマにドハマりしているけんつです。
最近、Laradockを使うときにコンテナをビルドしたらDNSが解決できない問題に苦しめられていました。
なんかすごく詰まったのにあっさりと解決してしまったのでまとめます。
環境
- Ubuntu16.04
- Docker version 18.09.0, build 4d60db4
- docker-compose version 1.23.0, build c8524dc1
問題
Laradockで色々とコンテナをビルドしていた時にこんなエラーが頻発して全然ビルドできなかった
Step 3/12 : RUN apk --update add wget curl git build-base libmemcached-dev libmcrypt-dev libxml2-dev zlib-dev autoconf cyrus-sasl-dev libgsasl-dev supervisor ---> Running in 46add29caab7 fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.8/main: temporary error (try again later) WARNING: Ignoring APKINDEX.adfa7ceb.tar.gz: No such file or directory fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.8/community: temporary error (try again later) WARNING: Ignoring APKINDEX.efaa1f73.tar.gz: No such file or directory ERROR: unsatisfiable constraints: autoconf (missing): required by: world[autoconf] build-base (missing): required by: world[build-base] cyrus-sasl-dev (missing): required by: world[cyrus-sasl-dev] git (missing): required by: world[git] libgsasl-dev (missing): required by: world[libgsasl-dev] libmcrypt-dev (missing): required by: world[libmcrypt-dev] libmemcached-dev (missing): required by: world[libmemcached-dev] libxml2-dev (missing): required by: world[libxml2-dev] supervisor (missing): required by: world[supervisor] wget (missing): required by: world[wget] zlib-dev (missing): required by: world[zlib-dev] ERROR: Service 'php-worker' failed to build: The command '/bin/sh -c apk --update add wget curl git build-base libmemcached-dev libmcrypt-dev libxml2-dev zlib-dev autoconf cyrus-sasl-dev libgsasl-dev supervisor' returned a non-zero code: 11
なぜかfetchできないが、普通にターミナルからcurlなりwgetすると取得できたからdockerコンテナ 内部でDNSを解決できていないのかと考えていた。
それとworkspaceのビルドする際にcomposerを走らせる段階でこんなエラーもでる
Changed current directory to /home/laradock/.composer Loading composer repositories with package information [Composer\Downloader\TransportException] The "https://packagist.org/packages.json" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
composer diagをしてみてもこの通り
Checking platform settings: OK Checking git settings: OK Checking http connectivity to packagist: WARNING [Composer\Downloader\TransportException] The "http://packagist.org/packages.json" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution Checking https connectivity to packagist: WARNING [Composer\Downloader\TransportException] The "https://packagist.org/packages.json" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution Checking github.com rate limit: FAIL [Composer\Downloader\TransportException] The "https://api.github.com/rate_limit" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution Checking disk free space: OK Checking pubkeys: FAIL Missing pubkey for tags verification Missing pubkey for dev verification Run composer self-update --update-keys to set them up Checking composer version:
完全にご臨終してしまう
解決策
↑これをやったら想定した動作をした。
実際には全ておこなったわけではなくて
「dockerのホストであるUbuntuでは、デフォルトでdnsmasq が有効になっていて、 /etc/resolv.confが下記のように自分を見る設定になっている。」
この部分を見て /etc/resolv.conf に
nameserver 8.8.8.8 nameserver 8.8.4.4
を追記したて Docker を再起動するだけで、DNSでコケることがなくなった
おわりに
いつからこうなったんだ…
Nagleアルゴリズムと遅延ACK
- はじめに
- 本題に入る前に
- Nagleアルゴリズムとは
- The small-packet problem の解決法としてのNagleアルゴリズム
- 遅延ACKの前に…
- 遅延ACK
- Nagleアルゴリズムと遅延ACK
- おわりに
はじめに
どうも、最近嫌な予感が段々と確信になっていくタイプのサイコホラー映画に弱いという事がわかったけんつです。
諸事情で、Linuxカーネルモジュールとして実装したEchoサーバのコードを見なおしていたらNagleアルゴリズムと遅延ACKというものに出会いました。
気になったので色々と調べているとその仕組みが面白いなと思ったのでまとめます。
Nagleアルゴリズムとは
ネーグルアルゴリズムと言うみたい。
RFC 896「IP/TCPインターネットワークにおける輻輳制御」*2で制定されており、その名の通り輻輳制御に関するアルゴリズムである。
そもそも、これが何故必要かということはある問題がキーになっているとRFCには記載されている。
The small-packet problem の解決法としてのNagleアルゴリズム
この問題を解決するためにNagleアルゴリズムは存在している。
Nagleアルゴリズムは以下の3つの条件を満たすまで、パケットをバッファにためて条件を満たした段階で送信することで効率的にデータを送信するアルゴリズムである。
つまり、「Nagleアルゴリズムでは最大セグメントサイズ以下の複数の送信メッセージを一つに束ね、まとめて送信する。特に、送信パケットで送信側が ACK を受け取っていないのがある場合、送信するに値するまで送信側はバッファリングを行い、そして、一度にまとめて送信する。」アルゴリズムとなる*3
- 未送信データが最大セグメントサイズ以上になる
- 過去の送信パケットで ACK が未受信の物がなくなる
- タイムアウトになる
このアルゴリズムで注意したい点は送信を遅延させる時間が200~500msであるという点(BDS系は最大200ms)。
これは次にまとめる遅延ACKとの相性が最高に悪く、オンラインゲームやその他のリアルタイム性を要求されるネットワークに於いて
そもそも、Nagleアルゴリズムがレイテンシを犠牲にしている分余計に遅延が発生してしまうという致命的な問題を発生させる。
遅延ACKの前に…
遅延ACKの前に前提としていくつかTCPについて知らなければならないことがあるのでそっちを先に紹介する。
ウィンドウ
TCPにはウィンドウという概念がある。
これは何かというと、TCPでは送信されてきたデータを一時的にバッファに溜め込んでアプリケーションはそこからデータを取り出し処理していく。
これをウィンドウといい、一度に溜め込める量をウィンドウサイズという。
そしてウィンドウサイズは、送信側が確認応答を待たずに一度に送信できる最大量でもある。
またこのウィンドウサイズはフロー制御によって変更される場合があり常に一定というわけではない。
スライディングウィンドウ
スライディングウィンドウについては以下のサイトがわかりやすくまとまっている。
beginners-network.com
このサイトでは、9000byteのデータを送信したい場合でウィンドウサイズが3000byteの場合を想定している。
まず9000 byteを 1セグメント 1000byte ずつ送信する場合3つのパケットを一度に送信できる。
受信側がACKを送信側に送った場合、3001byte~6000byte分のデータを送信する。
このように、相手側のウィンドウサイズに応じて送信するデータの範囲をずらしていくことをスライディングウィンドウという。
しかし、実際には1000byte送ってスライディングウィンドウすることも可能なのだがそれをしてしまうと1セグメントごとにACKを送る必要があるため
非常に効率が悪くなってしまうという問題が発生する。
これが次の遅延ACKに関連している。
遅延ACK
データを受信した側が即座にACKを返すと、フロー制御が働いて小さいウィンドウサイズが設定される場合がある。
送信側はそれに合わせてデータを送ってしまうと非常に効率が悪くなる。(これはSWS: Silliy WIndow Syndromeと呼ばれている)
そこで、このACKを意図的に送らせて、ネットワークを効率よく利用する方法が挙げられた。
それが遅延ACKとなる。
遅延させる場合は
- 2 * 最大セグメントサイズのデータを受信するまで確認応答しない。
- 上記以外の場合は最大200~500ms、ACKを遅延させる。
大体2セグメントごとにACKを送信するように遅延させる場合がおおい。(らしい)
Nagleアルゴリズムと遅延ACK
上記で紹介した、遅延ACKとNagleアルゴリズムが両方働くと遅延が余分に働く場合がある
特にサーバが遅延ACKのタイムアウトになるまでリクエストがストップしてしまうという場合。*4
これは以下のサイトにわかりやすい例が記載されている。
postd.cc
特にわかりやすい例を以下にあげる
アプリケーション: やあ、パケット1だよ。 HAProxy:<無反応で、2つ目のパケットを待っている> HAProxy:<そのうちACKするけど、まあいいか> アプリケーション:<無反応> アプリケーション:<ACKを待っているんだけど、ネットワークが混雑しているのかな> HAProxy:もう待ち疲れた。はい、ACKだよ。 アプリケーション:やった! じゃあ2つ目のパケットを送るよ。 HAProxy:よかった。これで終了だ。
送受信側が意図的に待機している時間があるが、そこで余分な200msが発生している。
これが、リアルタイム性を要求されている場合わりと致命的な遅延となってしまうのでNagleアルゴリズムを無効化するかTCP_CORKで遅延時間を制御する場合がある
おわりに
楽しかった。
*1:輻輳 | 用語集 | KDDI株式会社 http://www.kddi.com/yogo/%E9%80%9A%E4%BF%A1%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9/%E8%BC%BB%E8%BC%B3.html
*2:RFC 896 - Congestion Control in IP/TCP Internetworks https://tools.ietf.org/html/rfc896
*3:Nagleアルゴリズム - Wikipedia https://ja.wikipedia.org/wiki/Nagle%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0
*4:TCPを(少しは)理解しておくべきその理由 | POSTD https://postd.cc/why-you-should-understand-a-little-about-tcp/
Unicode9.0 以降の複雑な符号化における letter-spacing トラップ
はじめに
どうも、最近レンタルしたレディプレイヤー1が面白すぎて購入を検討しているけんつです。
最近、Webにおいて圧倒的に面倒な絵文字対応について色々と考えているところなのですが
もろもろ対応していると不可解な現象とその原因がわかったので少しだけまとめます。
問題
Unicode9.0以降で絵文字で肌色が選択できるわけなのですが、あれをWeb上で表示させるときに何故か
以下の様に絵文字が 本体+スキントーンの形式に分離してしまう問題が発生していた。
しかもスキントーンは色が四角くなって出てきてしまい、初見ではこれが一体何なのかわからなかった。
実際はスキントーン以外でも、国旗や複数人がひとつの絵文字に存在するような場合には同様に分離してしまう箇所がいくつかあった。
そして、なぜか場所によっては正常に表示されている部分もあった。
原因
肌色や複数のコードポイントにまたがって表現される絵文字は以下のような形式を取っている
U+1F466 U+1F3FE
このように結合文字列としてひとつの絵文字を表現しているのだ。
そのため、 letter-spacing が使用されていると本来ひとつの絵文字として表現されているものが
U+1F466 と U+1F3FE に分離してしまうといったものだった。
終わりに
知らんがな。
MVCに基づいて設計する時に思う自分なりのベストプラクティス
はじめに
最近、ヴァイオレット・エヴァーガーデン:スペシャルをみてダメになったけんつです。
割とよく「MVCってなんだ」とか「MVCを採用している開発でどう切り分けたらいいかわからん」と聞かれる事が多いので
自分なりに考え、「こうなっていると最高に嬉しかった、良かった」というベストプラクティスをまとめてみたくなったのでまとめてみます。
MVCは様々なケースで使われていますが、今回は特にWebサーバサイドに限定して紹介していきます。
MVC とは
特にWebサーバサイドの開発に携わってきた人にとっては古くからお馴染みな Model View Controller という
UIをもつアプリケーションソフトウェアを実装するためのデザインパターン。
特徴として、内部で扱うデータをユーザが直接参照・編集する情報から分離する構成を取ることが多い。
そして、これはwikipediaにも書いている。
Model View Controller - Wikipedia
次に、一般的に言われているMVCそれぞれの役割についてまとめる。
Controller の役割
一般的にControllerの役割は、ユーザの入力情報に応じて
ModelとViewを制御する役割を持っている。
View の役割
一般的にViewは表示や、ユーザの入出力が行われるものを制御する。
特に表示、レンダリングに関する事を行う場合はModelを受け取って行うことが多い。
MVCのメリットとデメリット
メリット
MVCを使うことで、ロジック部分とレンダリング部分、それらを制御する部分という
役割がはっきりとしている状態に分離することができるので開発も効率的に行うことができる。
また、MVCはそれぞれModel View Controllerという分類もされるがModelならModelの中でもわけられていることが多く、それぞれに独立性が高くなるため
仕様が変更されても比較的柔軟に対応できる。
デメリット
プロダクトの規模が大きくなると抱える問題も増えてくる。
例えば、規模が大きくなるとビジネスロジックも多くなってしまうため
モデルがFat Modelになってしまいがちで、管理が大変。
これはControllerにおいても言えることで、必要な機能を増やしていくと
Controllerで行うべき処理も増えてしまうため、Fat Controller になってしまう。
この状況を作ると、ControllerとViewの依存性が始めからある程度強いMVCでは変更が難しくなってしまう。
また、ControllerとView、Modelの依存性が強くなってしまいがちで
修正が困難になってしまう場合がある。
MVCを使うためのベストプラクティス
上記に上げた、概要やメリットとデメリットを考慮した上でいくつかポイントを絞って
自分の思うWebサーバサイドに適用できる、MVCのベストプラクティスについてまとめていく。
Controller の使い方
まず、大事なのがModel View Controller で、特に Controller をどう切り出すか。
つまり、どのような処理を Controller にまとめるかということだと思っている。
これを意識することで、Model と Controller の境界をハッキリさせ必要以上に Model にロジックを集約させてしまうことを防ぐ。
では、どのように分けるのがよいかというと以下の一点を考えるとうまく行く。
- HTTP に関する処理を Controller でのみ扱い、 Model に含めない。
これに尽きると思っている。
確かにビジネスロジックを Model にまとめるのは重要な話だが、Webサーバサイドにおいて
それらと HTTP では、DBなどから取ってくるという処理や、その結果として現れるデータと
Webサーバサイドの根幹にある通信技術というように、そもそもデータとしての属性が大きく異なる。
そのため、Controller では Cookieやセッションに関する処理、Query String や Json POST で渡ってきたデータのバリデーション、それに伴うエラーハンドリングを行う。
このようにすると、ModelにHTTP関連の処理を負わせないため比較的すっきりとまとまる上に
Controller と Model を確実に分離することができるため必要以上に Controller と Model を依存させないという利点をもたらしてくれる。
イメージは下記の画像の感じで
URL設計を見なおしてみる。
これは、「MVCと関係無いのでは?」と思うかもしれないが実は上の Controller にHTTP関連の処理をまとめることにも関連している重要な点だと思っている。
上の記述で、HTTP関連のバリデーションを追加すると辛くなってくるのが
Query String を多用する場合で、そのような場合に Controller で実装するメソッドは規模が大きくなってしまいがちになる。
また、それらを多用するとそもそもにそのクエリがリクエストに存在するかどうかから確かめないと行けない場合もあって冗長になりやすい。
それを未然にルーティングで吸収してしまうことで、必要のないHTTPリクエストの時点でエラーをはいてしまうようなリクエストを Controller で扱うことがなくなる。
特に以下のような場合で有効。
ユーザプロフィールを表示するためのルーティングを前提とした場合 /profile?id=xxx&x=a&y=b <= 一意に情報が定まるものもQuery Stringにしている /profile/xxx?x=a&y=b <= 一意に定まるものはURLのパスに含めてしまう
この様に、必要となる一意で定まるものをパスに含めてしまうことで
それが確実に存在するように保証する、そしてそれの形式が異なる場合や存在しない場合は
その先、Controllerなどにリクエストを飛ばさないようにする。
これで必要のないバリデーションを行わないようにする。
Controller と Model でやりとりを増やし過ぎない
Controller はその名前の通り、View と Model の流れを管理しているわけだが
Model が増えてくると Controller で呼び出すべき Model が増えて依存性が高くなってしまう。
こうなってくると、後々の修正や変更が大変になってくる上にMVCのメリットである独立性を維持できていない状況が生まれてしまう。
そのような状況を増やさないためにも、ControllerからViewに渡すべき Model が多いと感じた場合はそもそも Model の設計が適切かどうかを見なおすか
Facade パターンを採用することが解決策として挙げられる。
Facade パターンを採用する
複数のModelを参照する必要がある場合、それらをひとつの処理と見なして Facede パターンを適用するのが得策の場合がある。
Facede パターンとは、Modelの動作順を守ったうえでControllerで行っていた処理をひとまとめにしているだけだが
Model を多く呼び出す必要がある場合に Fat Controller になるのを防ぐことができる。
【PHPのお話】PSRって知ってますか?
はじめに
どうも、最近ヴァイオレット・エヴァーガーデンの5話にメンタルブレイクされたけんつです。
最近、さらなるスキルアップを目指してサーバサイドで別の経験を積もうとしている。
それでGoとか調べていると、PHPが盛大にディスられていて悲しい気持ちになっている。
確かにPHPはバグとかを生み出しやすい感じは自分で書いていても思うが、そういう記事ほどPSRについて触れていない。
なので、こういう便利なものが標準であるんだよということを今回は書いていきたい。
PSRとは
PSRとは、PHP Standards Recommendations の事で PHP-FIG(PHP Framework Interop Group = PHPフレームワーク相互運用性グループ)が策定しているPHPコーディング規約のことを指す。
The PHP League とか、PHPで使えるOSSをリリースしているところではPSRを厳守する様に書かれていて、これにはいくつかの種類がある。
現状で承認されているPSRは以下の通り
- PSR-1 Basic Coding Standard(基本的なコーディング標準)
- PSR-2 Coding Style Guide(コーディングスタイルガイド)
- PSR-3 Logger Interface(ロガーインタフェース)
- PSR-4 Autoloader(オートローダー)
- PSR-6 Caching Interface(キャッシングインターフェイス)
- PSR-7 HTTP Message Interface(HTTPメッセージインターフェイス)
- PSR-11 Container Interface(コンテナインタフェース)
- PSR-13 Hypermedia Links(ハイパーメディアリンク)
- PSR-15 HTTP Handlers(HTTPハンドラ)
- PSR-16 Simple Cache(シンプル・キャッシュ)
- PSR-17 HTTP Factories(HTTPファクトリー)
これらをざっくりと、ところどころ重要なものはある程度詳細に書いていく。
PSR-1 Basic Coding Standard
自分はあまり意識することが少ないが PSR-1 は最低限準拠することが求められるコーディング規約を示している。
といっても、本当に基本的なことなので次にくる PSR-2 を満たす過程で自動的に満たしている事が多い。
詳しいことは以下のドキュメントを参照して欲しい。
PSR-2 Coding Style Guide
これはPHPで何かOSSを書くときや、その開発に参入するときや業務で使うときなど
ありとあらゆる場合で絶対といってもいいぐらいに使う最高に重要なコーディングおよびコーディングスタイル規約。
公式DocにあるOverviewを一部、紹介すると
- 一行は80文字以下が望ましい
- namespace のあとには空行をいれる
- クラスを宣言している中括弧はclass キーワードを含む行におく必要がある
などなど、結構色々ある。
公式DocにあるPSR-2を満たしたコーディングを以下に引用する。
<?php namespace Vendor\Package; use FooInterface; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; class Foo extends Bar implements FooInterface { public function sampleMethod($a, $b = null) { if ($a === $b) { bar(); } elseif ($a > $b) { $foo->bar($arg1); } else { BazClass::bar($arg2, $arg3); } } final public static function bar() { // method body } }
ただ、こんなこと毎回意識していられないので自動でfixしてくれるツールをいつも使っている。
基本的にPHPを用いた開発ではcomposerを使うので、requireしたあとはコミットする前に
$ ./vendor/bin/php-cs-fixer fix . --verbose
を走らせる。
以下が公式Doc
www.php-fig.org
PSR-3 Logger Interface
これも普段あまり意識することがない。
なぜなら、大抵error_log関数にまかせてしまうからだ。
PSR-3 はロギングに関する規約となっている。
これを満たすのはOSSではあまり見たことないので詳しいことは割愛。
以下が公式Doc
www.php-fig.org
PSR-4 Autoloader
これは、最高に重要な規約の一つでファイルパスからクラスをオートロードするための規約。
PSR-0っていう削除予定の規約もあるが、こっちは現役バリバリで使える。
むしろ、これを満たさないと本当にuse を使うのがしんどかったり
composerを使っている場合なら、そもそもオートロードされないとかっていう最高にヤバい問題が起き始める。
- 完全修飾クラス名は、「ベンダーの名前空間」として知られているトップレベルの名前空間名を持っている必要がある
- 完全修飾クラス名は、1つ以上のサブ名前空間名を持つことがある
- 完全修飾クラス名は終端クラス名を持つ必要がある
- アンダースコアは、完全修飾クラス名のいずれの部分にも特別な意味を持たない。英字は小文字と大文字の任意の組み合わせで構わない。
- すべてのクラス名は大文字と小文字を区別して参照する必要がある。
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
例を以下にしめす。
Abraham\TwitterOAuth\TwitterOAuth;
vendor name\(sub namespace)\class name となっているものをよくみる。
composerの設定を書くときもオートロードの設定はこれがベースになっているから
知らないとオートロードではまりがちなポイントが出来上がる。
公式Docは以下の通り
www.php-fig.org
PSR-6 Caching Interface
割と基本的な機能が完成された場合はまず意識することがなくライブラリで自動的に対処されている場合があるのでここもさくっと書く。
PSR-6はキャッシュに関する規約がまとまっている。
キャッシュを扱う際にこれだけは最低限用意して欲しいというメソッドが固まった interface を指す。
公式Docは以下
PSR-7 HTTP Message Interface
これは、色々なプロダクトを触るのに最高に重要なHTTPのリクエスト、レスポンスに関する規約。
HTTPリクエスト、レスポンスを扱う上で絶対にこれだけは用意してくれっていう関数群がinterfaceとなっている。
これは、Laravelでも標準のリクエスト、レスポンスクラスをPSR-7のInterfaceに置き換えることが出来たりするぐらいWebフレームワークでは対応していることが多い。
あと、HTTPが絡んだOSSをテストする際にこれらのInterfaceをモックする場合もある。
書いていることはそんな大した量じゃないが最高に重要な役割を持つ規約の一つである。
これがベースになっていることが多い
公式Docは以下
www.php-fig.org
PSR-11 Container Interface
これは、あまり注目されていないけど実は重要でOSSではよく使われている。
というのもPSR-11はDIコンテナに関する規約がまとまっているからだ。
これを満たしている、利用しているPHP製のOSSは意外と多い。
うだうだ語るよりも以下の記事を見れば大体わかる。
ルーティングやバッチ系のライブラリで使われているのをよく見る。
公式Docは以下
www.php-fig.org
PSR-13 Hypermedia Links
PSR-13、普段はめったに考えることないけどハイパーメディアリンクに関する規約。
これもハイパーメディアリンクを扱う上で必要な関数群を定義しているがこのあたりのライブラリを見たり実装をすることがないから本当にわからない
なので公式Docだけ晒しておく
www.php-fig.org
PSR-15 HTTP Handlers
これは、PSR-7と関連して満たされる場合が多いためさくっとまとめる。
PSR-15はHTTPハンドラーの規約になっている。
HTTPハンドラーと言っても、リクエストハンドラとミドルウェアに関するコンポーネントが含まれている。
が、これを自前で実装することはなくLaravelなどのFWで最初から組み込まれていることが多い。
それなりに重要なはずなんだけど、よく見えないところに隠されていることが多いため
意識しなくても使っている場合がある
以下が公式Doc
www.php-fig.org
PSR-16 Simple Cache
これはキャッシュに関する規約になっている。
キャッシュ系のライブラリを作ることは少ないと思うが使う際には、この規約にあるインターフェースを利用することになる。
中にはメソッドからキャッシュとして保持する必要のあるデータ型などが書いてある。
以下が公式Doc
www.php-fig.org
PSR-17 HTTP Factories
PSR-17はHTTPファクトリ、つまりPSR-7準拠のHTTPオブジェクトの作成方法についての規約となる。
これができるまでは、HTTPメッセージに関しての規約(PSR-7)しかなく、そのオブジェクトについてはバラバラだったため
それらの規格を統一するためにある。
これも同様にインターフェースなどが定められている。
こういった部分はよくFWの内部に埋もれがちになってしまっている。
以下が公式Doc
www.php-fig.org
おわりに
途中、FWに隠れがちで実例を示すのが難しい部分はかなり説明を端折ってきたが
特に有名なOSSやプロダクトの裏ではこのように制定された規約の上に成り立っていることが多く
これらを満たすことで、快適に開発することができる。
oEmbed を叩いてメディア共有サイトのコンテンツ情報を簡単に取得する
はじめに
どうも、よく訓練された PHPer のけんつです。
突然ですが、みなさん Youtube や Instagram などのメディア共有サイトの情報で特にコンテンツの埋め込みに必要な情報だけ欲しいときに
わざわざデベロッパー登録して、コンシューマーキー等を取得してAPIを叩くのって面倒じゃね?と思うことありませんか?
今日はそんなあれこれを手軽に便利にしてしまう「oEmbed」というものについて書こうと思います。
oEmbed とは
oEembed については以下のサイトがドキュメントを公開している。
じゃあ、oEmbed とは何かというと「コンテンツ共有サイトの情報を埋め込み可能な形式で提供するAPI」のことを指していて。
これは開発者登録等をしなくとも使用することができるためコンテンツ情報だけを使いたい時に非常に便利なもの。
この後、詳しい規格について記述していこうと思いますが「そんなんしらん、使えればok」という人は以下の例を見てください。
Quick Start
今回は Youtube を例に説明していく。
www.youtube.com
上記の動画を題材にする。
まず、oEmbed で必要になるのはそのコンテンツのURL。
今回で言えば
https://www.youtube.com/watch?v=SX_ViT4Ra7k
これ。
youtube の url に /watch というルーティングがあり、 クエリストリングで動画IDが付与されているこれ。
コンテンツのURLがわかれば次にやることは youtube の oEmbed エンドポイントにリクエストを投げるだけ。
Youtube の oEmbed エンドポイントは
https://www.youtube.com/oembed
↑これ。
それのエンドポイントに url 情報と format 形式をクエリストリングで指定してリクエストを送ると json が返ってくる。
※本当はクエリストリングの部分は url encode が必要。
{ "width": 480, "version": "1.0", "author_url": "https://www.youtube.com/user/08yakari", "provider_name": "YouTube", "type": "video", "provider_url": "https://www.youtube.com/", "html": "<iframe width=\"480\" height=\"270\" src=\"https://www.youtube.com/embed/SX_ViT4Ra7k?feature=oembed\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe>", "thumbnail_url": "https://i.ytimg.com/vi/SX_ViT4Ra7k/hqdefault.jpg", "thumbnail_width": 480, "title": "米津玄師 MV「Lemon」", "thumbnail_height": 360, "author_name": "米津玄師", "height": 270 }
あとは json を煮るなり焼くなり好きにするといい感じにできる。
ひとつ言うなら、 html メンバーの値は直接埋め込み可能な iframe 形式となっている。
なんならサムネイルのURLもついてきているのでスゴク便利。
oEmbed の規格
使うだけならあれでいいんだけど、それじゃ気がすまねぇ!!っていう人のためにしっかりと規格についてもまとめていきたい。
Consumer Request
oEmbed のエンドポイントに送るリクエストには必要なパラメータと形式が存在する。
まず大前提としてリクエストはエンドポイントに対してGETリクエストである必要があり、全てのパラメータは RFC 1738 に準拠した形式で URL Encode された状態で無いといけない。
その大前提の元に以下のパラメータをリクエストに含めることができる。
- url (require)
これは情報を取得したいコンテンツのURL。
- maxwidth (optional)
埋め込みリソースの最大幅。一部のリソースタイプ(以下に記載)にのみ適用される
- maxheigth (optional)
埋め込みリソースの最大高さ。一部のリソースタイプ(以下に記載)にのみ適用される。
- format (optional)
レスポンス形式を指定する。
この中で max*** 系 はあまり使わないが、注意が必要なものが format になる。
これは大抵 json or xml を指定するが存在しないものを選択するとエラーが返ってくる。
なので、どちらかを指定してリクエストを投げるのが良い。
Provider Response
ここからはレスポンスに関すること。
Response parameters
json に含まれるパラメータについて。
- type (required)
コンテンツの種別を表す、video, image などがある
- version (required)
oEmbed のバージョンを表す、今は1.0
- title (optional)
コンテンツのタイトル
- author_name (optional)
コンテンツの作者等。
- author_url (optional)
コンテンツの作者のURL
- provider_name (optional)
コンテンツを提供しているヤツの名前、youtube などサービス名がおおい
- provider_url (optional)
これもコンテンツを提供しているサービスのURL
- cache_age (optional)
推奨されるコンテンツのキャッシュ有効期間。
- thumbnail_url (optional)
サムネイルを管理しているURL
- thumbnail_width (optional)
サムネイルのサイズ
- thumbnail_height (optional)
サムネイルのサイズ
この中で特に注意が必要なのが type である。
これによって更に require なパラメータが増えたり、iframe の埋め込みコードがあったりなかったりしてしまう。
Errors
渡したurlパラメータの値となるurlにコンテンツが無い場合に返る
レスポンスを要求された形式で返せない場合に返る。
- 401 Unauthorized
指定されたURLには、プライベート(非公開)リソースが含まれている場合に返る。
Security considerations
oEmbed 対応のAPIを提供する場合に対応するURLについて。
URL スキームを http, https, mailto などに限定しないと、javascript:... などXSSの攻撃に利用されてしまう点に注意
おわりに
oEmbed は、コンテンツ情報をひっぱってくる際にかなり便利なのでぜひ使っていきたい。
ただ、コンテンツの説明などは手に入らない場合があるのでそういうときは黙って開発者登録などしてAPIを使うしかない。
oauth2-mastodon をつくった話
はじめに
いろいろあって、mastodon の Streaming API を叩くことになったが
PHP で使えるいい感じの OAuth2 クライアントがなかったので自作した。
レポジトリは以下のリンクから
github.com
Packagist にも追加してあるのでcomposer を使ってインストールすることができる。
packagist.org
パッケージ概要
対応バージョンは
PHPは現行の安定版(ver 1.0.1)で
- 7.0
- 7.1
- 7.2
に対応している。
基本的な構成は
github.com
みんな大好き、The PHP League にある oauth2-client パッケージの AbstractProvider を継承したマストドン用のクラスを作ることで割とあっさり実現できた。
そのほか Unit Test などは全て The PHP League にある skeleton レポジトリに準拠したものとなっている。
github.com
Mastodon の OAuth2.0 周りは以下のドキュメントを参考にして必要なことを行っているだけ
github.com
最低限実装するべき色々は以下のドキュメントにあるものに基づいている。
Implementing Your Own Provider - OAuth 2 Client
使い方
このライブラリをローカル環境でphpコマンドを叩いてさくっと使うというのは
もとからあるライブラリの構成上難しいので -S オプションをつけて起動して欲しい。
というのもセッション等を使っているのと、認証後は手元の環境にリダイレクトさせる必要があるから。
簡単な使い方を以下に示す。
まずはcomposer を使ってパッケージをインストールする。
$ composer require lrf141/oauth2-mastodon
その後の使い方は非常に簡単で
<?php use Lrf141\OAuth2\Client\Provider\Mastodon; session_start(); $provider = new Mastodon([ 'clientId' => '', 'clientSecret' => '', 'redirectUri' => 'redirect url', 'instance' => 'https://mstdn.jp', 'scope' => 'read write follow', ]); if (!isset($_GET['code'])) { $authUrl = $provider->getAuthorizationUrl(); $_SESSION['oauth2state'] = $provider->getState(); header('Location: '.$authUrl); exit; // Check given state against previously stored one to mitigate CSRF attack } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { unset($_SESSION['oauth2state']); exit('Invalid state'); } else { // Try to get an access token (using the authorization code grant) $token = $provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'] ]); // Optional: Now you have a token you can look up a users profile data try { $user = $provider->getResourceOwner($token); echo $user->getName(); } catch(Exception $e) { exit('Oh dear...'); } echo $token->getToken(); }
とするだけ。
後半にあるユーザ関連の情報は扱わなくてもよいが、諸々の認証をすると
Access Token が取れるようになっているのであとは煮るなり焼くなり好きにして欲しい。
ただし、 oauth2-client やその他 third-party 製の類似ライブラリに見られる使用方法と oauth2-mastodon はいくつか異なる部分がある。
それは特に Provider インスタンスを作成する最初の部分で、分散型であるため対象となるマストドンインスタンスを指定することと
先にマストドン側でアプリケーションを作成し、該当する scope 、つまりアクセス権限について空白スペース区切りで指定する必要がある。
またマストドン側でアプリケーションを作成した場合、リダイレクトURLを複数指定できるがデフォルトのリダイレクトURLを指定すると
どこにもリダイレクトされないためwebページ上にアクセストークンが表示されてしまうだけなので注意が必要。
今回の手元で自身がテストした際には
上記の利用例を test.php に全て書き
$ php -S localhost:8000 -t ./
というコマンドで起動し
リダイレクトURLには
http://localhost:8000/test.php
を指定していた。
さいごに
基本的なクラスを継承して、必要事項などを受け取れるように多少機能を追加しただけで6時間ほどあれば諸々の実装はできるので
PHP で oauth2-client を使って独自の third-party 製ライブラリを作る必要が出てくればぜひともトライしてみて欲しい。