Edited at

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 を使い続けるか悩むところです。


参考文献