なんでPOSTの値がとれないんだよぉぉぉ!!
普通にJavaScriptからPHPにデータ投げて、PHPで処理したかっただけなのに。。
「どうして値がとれないんだよぉぉぁぁぁぁぁ!」
ということで解決までの道のりを記していきます。
今後、私と同じ道を辿る方が出ないことを祈って。
実際のコード
早速ですが実装したコードをご覧いただきましょう。
フロントエンド
フロント側、JavaScriptのコードです。
fetch
メソッドを使用し、PHPファイルにデータを投げているだけです。
const data = {
name: 'john'
}
fetch('http://localhost/request.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(res => {
console.log('🍔', res)
})
.catch(error => {
console.error('🍤', error)
})
バックエンド
フロントエンドからデータを受け取って、そのまま返すようにしました。
リクエストはPOST
のみ受け付けるようにします。CORS対策のため、一旦全てのオリジンを許可するようにしています。
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST");
$name = $_POST["name"];
echo json_encode($name);
初劇「なんでCORSエラーでてんだよぉ!」
早速エラー発生しました。CORS違反です。
Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
どうやらPreflight request
が関係しているようです。
preflight request
について調べてみると以下のように書かれていました。
CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。
これは OPTIONS リクエストであり、 Access-Control-Request-Method,Access-Control-Request-Headers, Origin の 3つのHTTPリクエストヘッダー使用します。
プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが"to be preflighted"と修飾されている場合に現れ、単純リクエストの場合は省略されます。
https://developer.mozilla.org/ja/docs/Glossary/Preflight_request
ということはこのリクエストは単純リクエストではない、ということでしょうか。
単純リクエストについては以下のように書かれています。
単純リクエストは、以下のすべての条件を満たすものです。
許可されているメソッドのうちのいずれかであること。
GET
HEAD
POST
ユーザーエージェントによって自動的に設定されたヘッダー (たとえば Connection、 User-Agent、 または Fetch 仕様書で禁止ヘッダー名として定義されているヘッダー)を除いて、手動で設定できるヘッダーは、 Fetch 仕様書で CORS セーフリストリクエストヘッダーとして定義されている以下のヘッダーだけです。
Accept
Accept-Language
Content-Language
Content-Type (但し、下記の追加の要件に注意してください)
Range (単純範囲ヘッダー値、例えば bytes=256- や bytes=127-255 の場合)
Content-Type ヘッダーで指定できるメディア種別に許されるタイプ/サブタイプの組み合わせは、以下のもののみです。
application/x-www-form-urlencoded
multipart/form-data
text/plain
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#単純リクエスト
つまり、フロント側でapplication/json
に設定したのでPreflight requestが自動発行されたということになり、単純リクエストではなかった、といえます。
Preflight request
が必要とするのは
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Origin
3つのHTTPリクエストヘッダーです。
足りなかったのはAccess-Control-Allow-Headers
です。こちらを指定してあげるとよさそうです。
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header("Content-type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
...
第2劇「なんで値がとれないんだよぉ!!」
CORSエラーは無くなりました。ですが肝心の値が取得できていませんでした。
冷静に考えて、さきほどの単純リクエストの説明の際に引っかかる部分がありました。ここです。
Content-Type ヘッダーで指定できるメディア種別に許されるタイプ/サブタイプの組み合わせは、以下のもののみです。
application/x-www-form-urlencoded
multipart/form-data
text/plain
はい、見事にapplication/json
は入っていませんでした。お疲れ様です。
file_get_contents
file_get_contents
を使用した方法がありました。
...
$raw = file_get_contents('php://input');
$data = json_decode($raw);
// いろいろ操作する
echo json_encode($data);
php://input
ってなんぞや、と思い調べてみると以下のように書かれています。
php://input は読み込み専用のストリームで、 リクエストの body 部から生のデータを読み込むことができます。
https://www.php.net/manual/ja/wrappers.php.php#wrappers.php.input
つまりPOSTで投げられたデータを読み込みを許可する的な意味合いですね。
終劇「でもやっぱり$_POSTで欲しいんだよぉ!!!」
でもやっぱり$_POST
でデータを取得したい、そんな時ありますよね。
大丈夫です。突破口あります。
new URLSearchParams
application/x-www-form-urlencoded
でデータ送信すればよさそうです。
フロント側でnew URLSearchParams
を使用します。
const postData = {
name: 'john'
}
fetch('http://localhost/request.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(postData).toString()
})
.then(response => response.json())
.then(res => {
console.log('🍔', res) // john
})
.catch(error => {
console.error('🍤', error)
})
バックエンド側は以下の通りです。
Access-Control-Allow-Methods, Access-Control-Allow-Headers
はPreflight request
にて利用できるものなので不要になります。
header("Access-Control-Allow-Origin: *");
$name = $_POST["name"];
echo json_encode($name);
new FormData
multipart/form-data
でデータ送信する方法があります。
const formData = new FormData()
formData.append('name', 'john')
fetch('http://localhost/request.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(res => {
console.log('🍔', res) // john
})
.catch(error => {
console.error('🍤', error)
})
header("Access-Control-Allow-Origin: *");
echo json_encode($_POST["name"]);
お気づきでしょうか。ヘッダーにmultipart/form-data
が設定されていません。
この理由ですが、以下の記事が丁寧に解説してくれています。
最後に
こんなの当たり前だろ?って声が聞こえてきそうですが、私はこれで3時間ほど無駄にしました。
どうか皆さんは同じ道をたどらないように気をつけてください。
Discussion