人様のAPIで TypeError: Failed to fetch 出てて、CORSの問題を疑いつつも自分がミスってないか色々確認して、よしメールするぞってメール書いてたら、そもそもそのAPIが公開のものだなんてどこにも書いていなくて、自分でAPIサーバごと用意して使う=no-cors前提らしいことに気づいた… 数時間溶けた…
— KAMEDA Akihiro (@cm3) January 28, 2022
要はマイクロサービス的な設計にしている場合など、APIだからって外部から使われることを前提にしていないことは多々あるので、CORS設定が「適切でない」と疑う前に、まずCORSを許そうとしているかを疑えってことだったのですが。それはともかく、「CORSの問題を疑いつつも自分がミスってないか色々確認して」という部分についてここに詳細を記しておく。また、他人様に「CORS許すつもりがないよ」ってエラーを渡してあげれば親切なので、その方法についても書く。
実際に実装してコンソールとかでチェック
どうせCORS以外の問題もチェックしなきゃいけないので実装はすればいい。すぐだし。一応三種類実装してみたけど、別にこれらによって挙動が異なることはなかったから fetch 使っておけばいいと思う。
オススメな方法
async function postData(url = '', data = {}) { const response = await fetch(url, { method: 'POST', // GET, POST, PUT, DELETE, など headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response.json(); } postData('接続先', データオブジェクト) .then(data => { doSomething(data); //ここで何かする }).catch(error => { console.error('error!!!', error); });
jQueryを使う
function postDataJQ(url = '', data = {}) { $.ajax({ type: 'postかgetか', url: '接続先', data: JSON.stringify(data), contentType: 'application/json', dataType: 'json', success: function(returnedData){doSomething(returnedData);}, //ここで何かする error: function(XMLHttpRequest, textStatus, errorThrown) { alert('error!!!'); console.log("XMLHttpRequest : " + XMLHttpRequest.status); console.log("textStatus : " + textStatus); console.log("errorThrown : " + errorThrown.message); } }); } postDataJQ('接続先', データオブジェクト);
バーニラバニラで高収入♪(vanilla javascriptでold style)
function postDataOld(url = '', data = {}) { var xhr = new XMLHttpRequest(); xhr.open("POST", url); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { console.log(xhr.status); doSomething(xhr.responseText); //ここで何かする }}; xhr.send(JSON.stringify(data)); } postDataOld('接続先', データオブジェクト);
curl で確認する
Firebase で何かを POST しようとしたら CORS の preflight で怒られましたね? - Qiita に紹介されているように、
curl -H "Content-Type:application/json" -H "Origin: localhost:8080…のような送り元" -d '送るデータ' 送り先 -vvv
とか
curl -H "Origin: 127.0.0.1:8080" -H "Access-Control-Request-Method: POST" -X OPTIONS -vvv 送り先
を実行して、< access-control-allow-origin: *
みたいなのが返ってきているか確かめる。ちなみに、頭の<
が通信の方向を指し示していることに今更気づいたw 前者は実際の接続用であり、後者は preflight 用であり、接続がPOSTなら前者にも -X POST
を指定すればよい。ちなみに日本語データとかはパーセントエンコーディング済みのものを渡せばいい
ちゃんとCORS許す気ないとかエラーで教えてあげられると親切
例えば cenobites/flask-jsonrpc: Basic JSON-RPC implementation for your Flask-powered sites とか使ってAPI実装しているなら、Add ability to return custom error numbers in reponse · Issue #38 · cenobites/flask-jsonrpc に書かれているように flask_jsonrpc.exceptions
使って、
from flask_jsonrpc.exceptions import Error @jsonrpc.method('App.error') def error(): error = Error() error.data = {'info': 'Read https://blablabla for more info.'} raise error
とかしておくとよいのかもしれない。ちゃんとorigin見てドメイン外からの接続であることを確認してエラー吐いてとかやろうとすると面倒なので、一括でウェブサイトへのリンクだけ貼っておいて、詳細はドキュメンテーションで対処すればいいと思う。
その他
- ちなみにCORSはあくまでブラウザでアクセスできなくなるだけなので、CURLとかではアクセスできてしまう。だからAPI機能のちゃんとした制限をしたければ、トークンを利用した認証を Bearer ヘッダとかつかってちゃんと実装しよう。例えば参考→ Python の Flask Web API に JWT による認証を組み込む - Qiita
- いくつかのオンラインでのAPI検証ツールはCORS設定を無視した結果を返してくるので混乱しないように。例えばOnline API Testing Tool | Test Your API OnlineはCORS設定を無視してる気がする。