それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

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: 


完全にご臨終してしまう

解決策

qiita.com

↑これをやったら想定した動作をした。

実際には全ておこなったわけではなくて


「dockerのホストであるUbuntuでは、デフォルトでdnsmasq が有効になっていて、 /etc/resolv.confが下記のように自分を見る設定になっている。」
この部分を見て /etc/resolv.conf に

nameserver 8.8.8.8
nameserver 8.8.4.4

を追記したて Docker を再起動するだけで、DNSでコケることがなくなった

おわりに

いつからこうなったんだ…

Nagleアルゴリズムと遅延ACK

はじめに

どうも、最近嫌な予感が段々と確信になっていくタイプのサイコホラー映画に弱いという事がわかったけんつです。


諸事情で、Linuxカーネルモジュールとして実装したEchoサーバのコードを見なおしていたらNagleアルゴリズムと遅延ACKというものに出会いました。
気になったので色々と調べているとその仕組みが面白いなと思ったのでまとめます。

本題に入る前に

今回紹介することは、ネットワークで特にTCP/IPにおける輻輳制御についてのお話になる。
がしかし、輻輳制御ってなんやねんと思うのでちょっとまずそこに触れておく。

輻輳輻輳制御とは

輻輳とは、「さまざまな物が1カ所に集中すること」*1 を指している。
ネットワークの分野に置いては「ラピュタ金曜ロードショーで放送されたときTwitterバルスが流れまくる」ときの様に一箇所にアクセスが集中するときが輻輳のいい例となっている。

対して、輻輳制御とは輻輳が発生しそうになった場合にそれを制御することを指している。インフラにおいては、ロードバランサーがいい例として挙げられる。

Nagleアルゴリズムとは

ネーグルアルゴリズムと言うみたい。
RFC 896「IP/TCPインターネットワークにおける輻輳制御」*2で制定されており、その名の通り輻輳制御に関するアルゴリズムである。
そもそも、これが何故必要かということはある問題がキーになっているとRFCには記載されている。

The small-packet problem

この問題は、アプリケーションが1byte 程のサイズでパケットを送ってしまう際に発生する問題を指している。
何が問題かといえば、IPv4TCPのそれぞれでヘッダーサイズが20byteあるため 1byte ほどのメッセージを送信するために 41byte のパケットを送信する点にある。
このヘッダーに対して明らかに大きすぎるパケットを送信してしまう現象は、 Telnetセッションでよく見られるらしく通信速度の遅いネットワークを圧迫する。

The small-packet problem の解決法としてのNagleアルゴリズム

この問題を解決するためにNagleアルゴリズムは存在している。
Nagleアルゴリズムは以下の3つの条件を満たすまで、パケットをバッファにためて条件を満たした段階で送信することで効率的にデータを送信するアルゴリズムである。
つまり、「Nagleアルゴリズムでは最大セグメントサイズ以下の複数の送信メッセージを一つに束ね、まとめて送信する。特に、送信パケットで送信側が ACK を受け取っていないのがある場合、送信するに値するまで送信側はバッファリングを行い、そして、一度にまとめて送信する。」アルゴリズムとなる*3

  • 未送信データが最大セグメントサイズ以上になる
  • 過去の送信パケットで ACK が未受信の物がなくなる
  • タイムアウトになる

このアルゴリズムで注意したい点は送信を遅延させる時間が200~500msであるという点(BDS系は最大200ms)。
これは次にまとめる遅延ACKとの相性が最高に悪く、オンラインゲームやその他のリアルタイム性を要求されるネットワークに於いて
そもそも、Nagleアルゴリズムがレイテンシを犠牲にしている分余計に遅延が発生してしまうという致命的な問題を発生させる。

余談:Nagleアルゴリズムを無効化する

前述の通り、リアルタイム性が要求される場合Nagleアルゴリズムは悪い影響を引き起こす場合がある。
そのため、特にレイヤーの低い分野においてソケットでTCP_NODELAYオプションが提供されている。
これを有効化することで、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で遅延時間を制御する場合がある

おわりに

楽しかった。

Unicode9.0 以降の複雑な符号化における letter-spacing トラップ

はじめに

どうも、最近レンタルしたレディプレイヤー1が面白すぎて購入を検討しているけんつです。

最近、Webにおいて圧倒的に面倒な絵文字対応について色々と考えているところなのですが
もろもろ対応していると不可解な現象とその原因がわかったので少しだけまとめます。

問題

Unicode9.0以降で絵文字で肌色が選択できるわけなのですが、あれをWeb上で表示させるときに何故か
以下の様に絵文字が 本体+スキントーンの形式に分離してしまう問題が発生していた。

しかもスキントーンは色が四角くなって出てきてしまい、初見ではこれが一体何なのかわからなかった。

f:id:RabbitFoot141:20181112203116p:plain

実際はスキントーン以外でも、国旗や複数人がひとつの絵文字に存在するような場合には同様に分離してしまう箇所がいくつかあった。

そして、なぜか場所によっては正常に表示されている部分もあった。

原因

肌色や複数のコードポイントにまたがって表現される絵文字は以下のような形式を取っている

U+1F466 U+1F3FE

このように結合文字列としてひとつの絵文字を表現しているのだ。

そのため、 letter-spacing が使用されていると本来ひとつの絵文字として表現されているものが
U+1F466 と U+1F3FE に分離してしまうといったものだった。

実証

f:id:RabbitFoot141:20181112204047p:plain

このように、letter-spacingを利用しているかどうかで挙動が異なる



以下のサイトで実際にどういう区切りになっているのかなどがよくわかる。
異体字セレクタセレクタ (α v0.3)

終わりに

知らんがな。

MVCに基づいて設計する時に思う自分なりのベストプラクティス

はじめに

最近、ヴァイオレット・エヴァーガーデンスペシャルをみてダメになったけんつです。

割とよく「MVCってなんだ」とか「MVCを採用している開発でどう切り分けたらいいかわからん」と聞かれる事が多いので
自分なりに考え、「こうなっていると最高に嬉しかった、良かった」というベストプラクティスをまとめてみたくなったのでまとめてみます。

MVCは様々なケースで使われていますが、今回は特にWebサーバサイドに限定して紹介していきます。

MVC とは

特にWebサーバサイドの開発に携わってきた人にとっては古くからお馴染みな Model View Controller という
UIをもつアプリケーションソフトウェアを実装するためのデザインパターン

特徴として、内部で扱うデータをユーザが直接参照・編集する情報から分離する構成を取ることが多い。

そして、これはwikipediaにも書いている。
Model View Controller - Wikipedia

次に、一般的に言われているMVCそれぞれの役割についてまとめる。

Controller の役割

一般的にControllerの役割は、ユーザの入力情報に応じて
ModelとViewを制御する役割を持っている。

Model の役割

一般的にModelは、ビジネスロジックに関するものをもたせることになっている。
ビジネスロジック、つまり何らかの値やそれに関連したアルゴリズムを適用した処理などを指す。

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 を依存させないという利点をもたらしてくれる。

イメージは下記の画像の感じで
f:id:RabbitFoot141:20181016193500p:plain

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 パターンを適用するのが得策の場合がある。

f:id:RabbitFoot141:20181016194118p:plain

Facede パターンとは、Modelの動作順を守ったうえでControllerで行っていた処理をひとまとめにしているだけだが
Model を多く呼び出す必要がある場合に Fat Controller になるのを防ぐことができる。

おわりに

MVC を Web サーバサイドに適用する場合の、自分が思うベストプラクティスに関してまとめてみたが
これが必ずしも正しいわけでもなく、あくまでも自分が経験してきた中でこれがよいのではという一つの提案程度に見てもらえると嬉しい

【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 を満たす過程で自動的に満たしている事が多い。

詳しいことは以下のドキュメントを参照して欲しい。

www.php-fig.org

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してくれるツールをいつも使っている。

github.com

基本的にPHPを用いた開発ではcomposerを使うので、requireしたあとはコミットする前に

$ ./vendor/bin/php-cs-fixer fix . --verbose

を走らせる。


これは、多くの PHP 製の OSS で満たされている。

以下が公式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は以下

www.php-fig.org

PSR-7 HTTP Message Interface


これは、色々なプロダクトを触るのに最高に重要なHTTPのリクエスト、レスポンスに関する規約。
HTTPリクエスト、レスポンスを扱う上で絶対にこれだけは用意してくれっていう関数群がinterfaceとなっている。

これは、Laravelでも標準のリクエスト、レスポンスクラスをPSR-7のInterfaceに置き換えることが出来たりするぐらいWebフレームワークでは対応していることが多い。

あと、HTTPが絡んだOSSをテストする際にこれらのInterfaceをモックする場合もある。

書いていることはそんな大した量じゃないが最高に重要な役割を持つ規約の一つである。


これがベースになっていることが多い

github.com


公式Docは以下
www.php-fig.org

PSR-11 Container Interface

これは、あまり注目されていないけど実は重要でOSSではよく使われている。
というのもPSR-11はDIコンテナに関する規約がまとまっているからだ。

これを満たしている、利用しているPHP製のOSSは意外と多い。

うだうだ語るよりも以下の記事を見れば大体わかる。

rabbitfoot141.hatenablog.com

ルーティングやバッチ系のライブラリで使われているのをよく見る。

公式Docは以下
www.php-fig.org



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 のけんつです。
突然ですが、みなさん YoutubeInstagram などのメディア共有サイトの情報で特にコンテンツの埋め込みに必要な情報だけ欲しいときに
わざわざデベロッパー登録して、コンシューマーキー等を取得してAPIを叩くのって面倒じゃね?と思うことありませんか?

今日はそんなあれこれを手軽に便利にしてしまう「oEmbed」というものについて書こうと思います。

oEmbed とは

oEembed については以下のサイトがドキュメントを公開している。

https://oembed.com/

じゃあ、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 が必要。

https://www.youtube.com/oembed?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DSX_ViT4Ra7k&format=json

{
	"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 Format

これは大抵、 jsonxml になっている。
どちらかしかサポートしてないサービスも結構あるが、今回は json についてのみ書いていく。

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 製ライブラリを作る必要が出てくればぜひともトライしてみて欲しい。