ブラウザの同一オリジンポリシー
同一オリジンとは、プロトコル、ホスト名、ポート番号が全て一致するURLを指します。ブラウザはセキュリティ上の理由から、Cookie、Web Storage、iframe、XMLHttpRequestなどのリソースに対するクロスオリジンアクセスを制限しています。
JSONPによるクロスオリジン通信
script要素のクロスオリジン読み込みを利用した手法です。サーバーからJavaScriptコードを返却し、クライアント側でコールバック関数を実行します。
function fetchCrossOriginData(config) {
return new Promise((resolve) => {
const scriptTag = document.createElement('script');
const queryParams = new URLSearchParams(config.parameters);
queryParams.append('callback', config.callbackName);
scriptTag.src = `${config.endpoint}?${queryParams.toString()}`;
document.head.appendChild(scriptTag);
window[config.callbackName] = (response) => {
document.head.removeChild(scriptTag);
delete window[config.callbackName];
resolve(response);
};
});
}
// 使用例
fetchCrossOriginData({
endpoint: 'https://api.example.com/data',
parameters: { user: 'tech_wizard' },
callbackName: 'dataHandler'
}).then(result => {
console.log('受信データ:', result);
});
CORSの設定と実装
サーバー側で適切なHTTPヘッダを設定することで、クロスオリジンリクエストを許可します。
const express = require('express');
const server = express();
server.use((request, response, next) => {
response.header('Access-Control-Allow-Origin', request.headers.origin);
response.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
response.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
response.header('Access-Control-Max-Age', '86400');
response.header('Access-Control-Allow-Credentials', 'true');
response.header('Access-Control-Expose-Headers', 'X-Custom-Header');
next();
});
server.get('/api/users', (request, response) => {
response.json({ users: ['alice', 'bob'] });
});
server.listen(8080);
postMessage APIの活用
異なるウィンドウやiframe間でのメッセージ通信を実現します。
// 送信側
const targetFrame = document.getElementById('externalFrame');
targetFrame.contentWindow.postMessage(
{ type: 'DATA_UPDATE', payload: { value: 42 } },
'https://target-domain.com'
);
// 受信側
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted-domain.com') return;
console.log('受信メッセージ:', event.data);
});
window.nameを利用したデータ共有
iframeのwindow.nameプロパティを経由してデータを転送します。
let loadCount = 0;
const proxyFrame = document.createElement('iframe');
proxyFrame.onload = () => {
if (loadCount === 0) {
proxyFrame.src = '/proxy.html';
loadCount++;
} else {
const sharedData = proxyFrame.contentWindow.name;
console.log('共有データ:', JSON.parse(sharedData));
}
};
proxyFrame.src = 'https://external-domain.com/data-source.html';
document.body.appendChild(proxyFrame);
location.hashによる状態伝達
URLフラグメントを介した親子iframe間の通信手法です。
// 子iframeでの処理
const parentHash = window.parent.location.hash;
window.parent.location.hash = '#response:' + encodeURIComponent(data);
// 親ウィンドウでのハッシュ変更監視
window.addEventListener('hashchange', () => {
const responseData = decodeURIComponent(location.hash.slice(1));
console.log('ハッシュ経由のデータ:', responseData);
});
document.domainの設定
サブドメイン間での制限緩和に使用されます。
// メインページ (www.example.com)
document.domain = 'example.com';
const frame = document.getElementById('subdomainFrame');
frame.onload = () => {
console.log('サブドメインデータ:', frame.contentWindow.sharedData);
};
// サブドメインページ (api.example.com)
document.domain = 'example.com';
window.sharedData = { apiKey: 'secret123' };
WebSocketを用いた双方向通信
const webSocket = new WebSocket('wss://echo.example.com');
webSocket.onopen = () => {
webSocket.send(JSON.stringify({ action: 'subscribe' }));
};
webSocket.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('WebSocketメッセージ:', message);
};
Nginxリバースプロキシの設定
server {
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://backend-server:3000/;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'Authorization, Content-Type';
}
location /static/ {
root /var/www/html;
add_header Access-Control-Allow-Origin *;
}
}