JavaScriptのfetch()を知ってからXMLHttpRequestは使わなくなりましたゆひゃです。
今回fetch関数を使ってローカルのPHPにPOST送信したら結構詰まった話です。
思えば「fetch」は英語で「取ってくる」という意味なのにPOSTを送信するってなんか変ですね。
fetchの仕様
fetchはMDNによると、第一引数にAPIのURL、第二引数に送信情報を指定できるようです。
fetch( url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringfy( data )
})
.then( res => res.json() )
.then( data => {
....
})
こんな感じで送信できるようです。
試したプログラム
今回はJavaScriptからPHPにPOST送信するだけ(だと思っていた)なので、XMLでやっていた通り以下のプログラムを最初に書きました。
普通に$_POSTで受け取る(だめだった)
JavaScript:localhost:3000/
const url = 'http://localhost:8888/file_archives/'
const postData = {
operate: 'fetch',
option: 'none'
}
fetch( url,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
} )
.then( res => res.json() )
.then( files => {
setData( files )
} )
PHP:localhost:8888/file_archives/index.php
<?php
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");
//var_dump($_POST);
$operate = $_POST['operate'];
....
echo json_encode(...);
POST送信したので$_POSTで受け取ろうとしました。phpのヘッダ設定はクロスオリジンの対応です。色々してJSONを出力しています。
ですが、JavaScript側(localhost:3000)では
Uncaught (in promise) SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
のエラーが出て、PHP側ではPOSTを受け取れてない感じのエラーが出て、var_dump($_POST)をしてみるとPOSTは空でした。
ターミナルでcURLしてみる(できた)
まさかPHP側に問題がある?かとも思ってとりあえずXMLで送信しようとしました。
% curl -X POST -d 'operate=fetch' http://localhost:8888/file_archives/
>[{...},{...}]
PHPは同じ。
うまくJSONが返されました。
どうやらPHPは$_POST受取するのは問題なさそうです。
XMLで送信してみる(まあできた)
一旦慣れた方法のXMLHttpRequestで送信してみます。
JavaScript
const xhr = new XMLHttpRequest()
xhr.open('POST', url)
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
xhr.send('operate=fetch')
PHPは同じ。
うまくいきました。$_POSTにちゃんとデータが格納されていて、想定しているJSONが返されました。
ここでリクエストヘッダのXMLの場合は'content-type'を'x-www-form-urlencoded'としていることに気づきました。
うまくいかなかった原因
JavaScript側ではfetch関数で'Content-Type'を'application/json'となっているのに対し、PHP側では$_POSTで受け取ろうとしていたのが問題でした。
どうやら$_POSTで受け取れるのは'application/x-www-form-urlencoded'か'multpart/form-data'のどちらかでしかいけないようです。
解決方法1
JavaScript:localhost:3000/
const url = 'http://localhost:8888/file_archives/'
const postData = {
operate: 'fetch',
option: 'none'
}
fetch( url,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
} )
.then( res => res.json() )
.then( files => {
setData( files )
} )
PHP:localhost:8888/file_archives/index.php
<?php
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");
$input_json = file_get_contents('php://input');
$post = json_decode( $input_json, true );
$operate = $post['operate'];
....
echo json_encode(...);
とする必要があるようです。JavaScriptは最初と同じです。
PHP側でPOSTしたデータを受け取るためには、file_get_contents()を使う必要があるようです。なるほど。
解決策2
JavaScript:localhost:3000/
const url = 'http://localhost:8888/file_archives/'
const postData = {
operate: 'fetch',
option: 'none'
}
fetch( url,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(postData).toString()
} )
.then( res => res.json() )
.then( files => {
setData( files )
} )
PHP:localhost:8888/file_archives/index.php
<?php
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");
$operate = $_POST['operate'];
....
echo json_encode(...);
こちらでもうまくいきました。PHP側の処理は最初と同じです。
JavaScriptの'Content-Type'で 'application/x-www-form-urlencoded'を指定して、bodyパラメータにはURLパラメータ文字列を与えて送信しました。
$_POSTはapplication/jsonが受け取れないってだけなので受け取れる形でJavaScriptで送信するだけです。
割と簡単なミスでした。
まとめ
IEから解放されてfetch関数をゴリゴリ使えるようになってAjaxが楽になったと思った矢先、こんな罠に引っかかるとは、、
今後、WebAPIを作る機会も多くなるでしょうから、この経験が活きることを願いますね。
とりあえず解決してよかった。