PHP
HHVM
HIPHOP

HHVM(PHP)で file_get_contents 時に context header を指定すると 400 Bad Request

HipHop Virtual Machine 上にて、PHP で書いたスクリプトで file_get_contents() の context を指定して API などにリクエストすると「400 Bad Request」エラーが返ってくる
(@「さくらのVPS」の CentOS7 + Nginx + HHVM + Kusanagi 環境)

「HHVM file_get_contents header」でググっても、Qiita 記事に絞り込んでもドンピシャの情報がなかったので、未来の忘れん房な自分へのググラビリティとして。

TL;DR

file_get_contents() の第2引数が false になってませんか?

一般的なPHPの記法
$result = file_get_contents($url, false, stream_context_create($context));

HHVM では file_get_contents() の第2引数は null にする必要があります。

file_get_contents 関数の、第2引数「use_include_path」のデフォルト値は false だからか、PHP5.6, PHP7.x では false でも動作するのですが、null でも動作するため互換を持たせたい場合は null に統一した方がいいかもしれません。

HHVM互換の記法
$result = file_get_contents($url, null, stream_context_create($context));

検証環境

  • CentOS Linux release 7.5.1804 (Core)
  • HipHop VM 3.19.2-dev (rel)
    • PHP Version => 5.6.99-hhvm
    • Zend Version => 2.4.99
  • PHP 5.6.36 (cli)
  • PHP 7.2.11 (cli)

TS;DR

Qiita の API など、file_get_contents を使ったシンプルに取得するスクリプトは PHP/HHVM どちらでも動きました。

PHP,HHVMどちらも動くシンプルなコード
<?php
$url_api = 'https://qiita.com/api/v2/items/e5f1668444898465ea97';

// リクエスト開始
$http_response_header = null;
$result = file_get_contents($url_api);

if (false === $result) {
    //エラー処理
    print_r($http_response_header);
}

//通常処理
print_r($result);

しかし、認証されていない(アクセストークンをヘッダーに含めていない)リクエストの場合、時間あたりのリクエスト数に制限がかかる API は多いため、ヘッダーに「Authorization: Bearer」を指定してアクセストークンを添えてリクエストすることにしました。

ところが、ローカル環境や、サーバーのコンソールからは PHP で実行すると動作するのに、Nginx(on CentOS7)上の HHVM を通すと「400 Bad Request」エラーが返ってきます。切り分け的に HHVM に問題がありそうです。

問題が起きるコード
<?php
$url_api      = 'https://qiita.com/api/v2/items/e5f1668444898465ea97';
$access_token = 'xxxxxxx...xxxxx'
$header       = "Authorization: Bearer ${access_token}\r\n"; //CRLF

$context = [
    'http' => [
        'method'        => 'GET',
        'ignore_errors' => true,
        'header'        => $header
    ],
];

// リクエスト開始
$http_response_header = null;
$result = file_get_contents($url_api, false, stream_context_create($context));

if (false === $result) {
    //エラー処理
    print_r($http_response_header);
}

//通常処理
print_r($result);

PHP7 で使えている関数の引数の String 型宣言は HHVM 3.6 では使えなかったりするので、PHP7 にシフトしたいのに、ちょいちょい違いがあり困ることがあります。おそらく今回も同様の微妙な仕様の違いである匂いがします。

PHP の場合は CRLF でも LF でも正常にヘッダーが送信されるが、HHVM の場合では LF だとヘッダーのフォーマットがおかしくリクエスト先サーバーに Bad Request を返されてしまう。
PHPのシステムをHHVMに載せた時に動かなくて直した所一覧 @ suzuki0keiichiの日記)

ちょっと古い情報なのですが、「お!これじゃないか!」と思える上記の情報はあったのですが、残念ながらすでに CRLF で改行していました。(´・ω・`)=з

HHVM の既知の PHP 互換問題一覧や、リポジトリの Issue を探してもドンピシャのものはなかったのですが、「ん?」と思う箇所がありました。

以下の2つの Issue を見ると、file_get_contents() の第2引数が null になっているのです。

「あれ?第2引数は false じゃないの?もしかして、型々言わない系?」と思い、falsenull に変えたところ、、、動きました!

なんたルチア。

HHVM は KUSANAGI が Wordpress を最適化してるので速いから使っているのですが、PHP 7 も同じくらい速いそうですし、HHVM は PHP サポートを打ち切るので、PHP 7 にガッツり移行するためにも、今後 HHVM を使い続けるか悩むところです。

参考文献