結論:WebSocket による AI リアルタイム対話の実装において、クロスドメイン通信と CORS 設定は避けて通れない課題です。本記事では HolySheep AI を事例に、フロントエンド(React/Vue)とバックエンド間のセキュアな接続設定を具体例とともに解説します。HolySheep AI は1ドル=1円の為替レート(公式比85%節約)、WeChat Pay や Alipay と言った決済手段の対応、そして50ミリ秒未満の低レイテンシという特徴は、リアルタイム対話アプリケーションに最適です。
HolySheep AI vs 公式API vs 競合サービスの比較
| 比較項目 | HolySheep AI | OpenAI 公式 | Anthropic 公式 | Google Gemini |
|---|---|---|---|---|
| 為替レート | ¥1 = $1(85%節約) | ¥7.3 = $1(基準) | ¥7.3 = $1(基準) | ¥7.3 = $1(基準) |
| GPT-4.1 出力単価 | $8/MTok | $8/MTok | ー | ー |
| Claude Sonnet 4.5 出力単価 | $15/MTok | ー | $15/MTok | ー |
| Gemini 2.5 Flash 出力単価 | $2.50/MTok | ー | ー | $2.50/MTok |
| DeepSeek V3.2 出力単価 | $0.42/MTok | ー | ー | ー |
| レイテンシ | <50ms | 100-300ms | 150-400ms | 80-200ms |
| 決済手段 | WeChat Pay / Alipay / クレジットカード | クレジットカードのみ | クレジットカードのみ | クレジットカードのみ |
| 無料クレジット | 登録時付与 | $5相当 | $5相当 | $300相当(90日) |
| に向くチーム | コスト重視・中国本土ユーザー | グローバル開発 | エンタープライズ | Google エコシステム |
WebSocket とは:リアルタイム双方向通信の基盤
WebSocket は HTTP プロトコル上の常設接続を確立し、サーバーとクライアントの間で双方向リアルタイム通信を実現する技術です。AI 対話アプリケーションでは、ユーザーの入力に即座に反応するストリーミング応答が求められるため、WebSocket が適しています。
クロスドメイン通信と CORS の必要性
ブラウザ上で動作する JavaScript は、デフォルトで同一生成元ポリシー(Same-Origin Policy)に従います。フロントエンドが https://myapp.com で動作し、WebSocket サーバーが https://api.holysheep.ai に接続しようとする場合、異なるオリジン间的通信となり、明示的な許可が必要です。
私は以前、金融機関の内部システムで AI チャットボットを構築した際に、クロスドメイン制約により WebSocket 接続が拒否される問題に直面しました。開発環境では localhost:3000 から localhost:8080 への接続だったため問題なかったものの、本番環境にデプロイすると CORS エラーが頻発し、根本的な解決策を実装する羽目になりました。
HolySheep AI の WebSocket エンドポイント設定
HolySheep AI の WebSocket エンドポイントに接続するための設定を以下に示します。接続先の base_url は必ず https://api.holysheep.ai/v1 を使用してください。
Node.js バックエンド:WebSocket サーバー設定
// server.js
const { WebSocketServer } = require('ws');
const https = require('https');
const http = require('http');
const fs = require('fs');
// CORS ヘッダーの設定
const CORS_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With',
'Access-Control-Allow-Credentials': 'true'
};
// オプション設定の例
const ALLOWED_ORIGINS = [
'https://your-frontend-domain.com',
'https://www.your-frontend-domain.com',
'http://localhost:3000', // 開発環境
'http://localhost:5173' // Vite 開発サーバー
];
// フロントエンドと HolySheep AI の橋渡し役となる WebSocket サーバー
const server = http.createServer((req, res) => {
// CORS プリフライトリクエストの処理
if (req.method === 'OPTIONS') {
const origin = req.headers.origin;
if (ALLOWED_ORIGINS.includes(origin) || ALLOWED_ORIGINS.includes('*')) {
res.writeHead(200, {
...CORS_HEADERS,
'Access-Control-Allow-Origin': origin || '*'
});
} else {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('Forbidden: Invalid origin');
return;
}
res.end();
return;
}
// 通常の HTTP リクエスト
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', service: 'HolySheep Bridge' }));
}
});
const wss = new WebSocketServer({ server });
wss.on('connection', (ws, req) => {
const clientOrigin = req.headers.origin;
console.log(Client connected from origin: ${clientOrigin});
// HolySheep AI への接続
const holySheepWs = new WebSocket('wss://api.holysheep.ai/v1/ws', {
headers: {
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY},
'X-Client-Origin': clientOrigin
}
});
// クライアント → HolySheep へのメッセージ転送
ws.on('message', (data) => {
if (holySheepWs.readyState === WebSocket.OPEN) {
const message = JSON.parse(data.toString());
// メッセージにタイムスタンプを追加
message.timestamp = Date.now();
holySheepWs.send(JSON.stringify(message));
}
});
// HolySheep → クライアントへのメッセージ転送
holySheepWs.on('message', (data) => {
if (ws.readyState === WebSocket.OPEN) {
const response = JSON.parse(data.toString());
response.serverTimestamp = Date.now();
ws.send(JSON.stringify(response));
}
});
// エラー処理
ws.on('error', (error) => {
console.error('Client WebSocket error:', error.message);
});
holySheepWs.on('error', (error) => {
console.error('HolySheep AI connection error:', error.message);
});
// 切断時の処理
ws.on('close', () => {
console.log('Client disconnected');
if (holySheepWs.readyState === WebSocket.OPEN) {
holySheepWs.close();
}
});
holySheepWs.on('close', () => {
console.log('HolySheep AI connection closed');
});
});
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(WebSocket bridge server running on port ${PORT});
console.log(HolySheep AI endpoint: https://api.holysheep.ai/v1);
});
React フロントエンド:WebSocket クライアント設定
// src/hooks/useHolySheepWebSocket.ts
import { useEffect, useRef, useState, useCallback } from 'react';
interface Message {
role: 'user' | 'assistant' | 'system';
content: string;
timestamp?: number;
}
interface UseHolySheepWebSocketOptions {
apiKey: string;
model?: string;
systemPrompt?: string;
onMessage?: (message: Message) => void;
onError?: (error: Error) => void;
onConnectionChange?: (connected: boolean) => void;
}
export function useHolySheepWebSocket({
apiKey,
model = 'gpt-4.1',
systemPrompt = 'あなたは有帮助なAIアシスタントです。',
onMessage,
onError,
onConnectionChange
}: UseHolySheepWebSocketOptions) {
const [isConnected, setIsConnected] = useState(false);
const [messages, setMessages] = useState([]);
const wsRef = useRef(null);
const reconnectTimeoutRef = useRef>();
const messageIdRef = useRef(0);
const connect = useCallback(() => {
// 開発環境と本番環境でエンドポイントを切り替え
const wsUrl = import.meta.env.PROD
? 'wss://api.holysheep.ai/v1/ws'
: 'ws://localhost:8080/ws';
try {
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
ws.onopen = () => {
console.log('WebSocket connected to HolySheep AI');
setIsConnected(true);
onConnectionChange?.(true);
// 初期化メッセージを送信
ws.send(JSON.stringify({
type: 'init',
model: model,
systemPrompt: systemPrompt,
apiKey: apiKey
}));
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'stream') {
// ストリーミング応答を処理
const assistantMessage: Message = {
role: 'assistant',
content: data.content,
timestamp: data.timestamp || Date.now()
};
setMessages(prev => {
const lastMessage = prev[prev.length - 1];
if (lastMessage?.role === 'assistant') {
// 既存のメッセージに追記
return [
...prev.slice(0, -1),
{ ...lastMessage, content: lastMessage.content + data.content }
];
}
return [...prev, assistantMessage];
});
onMessage?.(assistantMessage);
} else if (data.type === 'complete') {
// 完全な応答が完了
console.log('Response complete, tokens:', data.usage?.total_tokens);
}
} catch (parseError) {
console.error('Failed to parse message:', parseError);
}
};
ws.onerror = (event) => {
console.error('WebSocket error:', event);
const error = new Error('WebSocket connection error');
onError?.(error);
};
ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
setIsConnected(false);
onConnectionChange?.(false);
// 異常終了の場合は再接続を試みる
if (event.code !== 1000 && event.code !== 1001) {
reconnectTimeoutRef.current = setTimeout(() => {
console.log('Attempting to reconnect...');
connect();
}, 3000);
}
};
} catch (error) {
console.error('Failed to create WebSocket:', error);
onError?.(error as Error);
}
}, [apiKey, model, systemPrompt, onMessage, onError, onConnectionChange]);
const sendMessage = useCallback((content: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
const userMessage: Message = {
role: 'user',
content: content,
timestamp: Date.now()
};
setMessages(prev => [...prev, userMessage]);
wsRef.current.send(JSON.stringify({
type: 'message',
id: msg_${++messageIdRef.current},
content: content
}));
} else {
console.warn('WebSocket is not connected');
}
}, []);
const disconnect = useCallback(() => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (wsRef.current) {
wsRef.current.close(1000, 'Client disconnected');
wsRef.current = null;
}
}, []);
useEffect(() => {
connect();
return () => {
disconnect();
};
}, [connect, disconnect]);
return {
isConnected,
messages,
sendMessage,
disconnect,
reconnect: connect
};
}
CORS 設定のベストプラクティス
1. 本番環境での厳格な Origin 検証
開発環境では *(ワイルドカード)を使用して柔軟に対応できますが、本番環境では許可する Origin を明示的に指定することが重要です。以下の環境変数による設定例を示します。
// cors-config.js
require('dotenv').config();
// 許可するOriginsのリスト
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',')
: ['http://localhost:3000', 'http://localhost:5173'];
// 本番環境では必ず許可リストを設定
const PRODUCTION_ALLOWED_ORIGINS = process.env.PRODUCTION_ALLOWED_ORIGINS
? process.env.PRODUCTION_ORIGINS.split(',')
: [];
function getCorsHeaders(origin) {
const isProduction = process.env.NODE_ENV === 'production';
const allowedList = isProduction
? PRODUCTION_ALLOWED_ORIGINS
: ALLOWED_ORIGINS;
// 許可リストに一致するかチェック
const isAllowed = allowedList.some(allowed => {
if (allowed === '*') return true;
if (allowed.endsWith('*')) {
// ワイルドカードサブドメイン対応(例: *.example.com)
const baseDomain = allowed.slice(0, -1);
return origin.startsWith(baseDomain);
}
return origin === allowed;
});
if (!isAllowed && origin) {
console.warn(Blocked CORS request from origin: ${origin});
return null;
}
return {
'Access-Control-Allow-Origin': origin || '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': [
'Content-Type',
'Authorization',
'X-API-Key',
'X-Requested-With',
'X-Client-ID'
].join(', '),
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400', // 24時間キャッシュ
'Access-Control-Expose-Headers': 'X-RateLimit-Remaining, X-RateLimit-Reset'
};
}
module.exports = { getCorsHeaders, ALLOWED_ORIGINS };
2. WebSocket エンドポイントでの Origin 検証
// ws-server.js
const { WebSocketServer } = require('ws');
const { getCorsHeaders } = require('./cors-config');
const wss = new WebSocketServer({
noServer: true // カスタムアップグレード処理
});
function handleUpgrade(request, socket, head) {
const origin = request.headers.origin;
const corsHeaders = getCorsHeaders(origin);
if (!corsHeaders) {
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
socket.destroy();
console.log(Rejected connection from unauthorized origin: ${origin});
return;
}
// WebSocket アップグレードの検証
const validProtocols = ['gpt-4.1', 'claude-sonnet-4.5', 'gemini-2.5-flash', 'deepseek-v3.2'];
const requestedProtocol = request.headers['sec-websocket-protocol'];
if (requestedProtocol && !validProtocols.includes(requestedProtocol)) {
socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
socket.destroy();
console.log(Invalid protocol requested: ${requestedProtocol});
return;
}
wss.handleUpgrade(request, socket, head, (ws) => {
// 認証トークンの検証
const authHeader = request.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
ws.close(4001, 'Authentication required');
return;
}
const token = authHeader.substring(7);
// HolySheep API キーを検証
if (!isValidHolySheepKey(token)) {
ws.close(4002, 'Invalid API key');
return;
}
ws.protocol = requestedProtocol || 'gpt-4.1';
wss.emit('connection', ws, request);
});
}
function isValidHolySheepKey(key) {
// HolySheep AI の API キー形式を検証
// 実際の実装では HolySheep の API を呼び出して検証
return key && key.length >= 32 && key.startsWith('hs-');
}
// 接続イベントの処理
wss.on('connection', (ws, request) => {
console.log('New WebSocket connection from:', request.headers.origin);
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
processMessage(ws, message);
} catch (error) {
ws.send(JSON.stringify({ error: 'Invalid JSON format' }));
}
});
});
async function processMessage(ws, message) {
// HolySheep AI へのリクエストを処理
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY}
},
body: JSON.stringify({
model: message.model || 'gpt-4.1',
messages: message.messages,
stream: true
})
});
// ストリーミング応答を WebSocket 経由で送信
// ...(省略)
}
module.exports = { handleUpgrade };
よくあるエラーと対処法
エラー1:CORS ポリシーによるブロック
Access to fetch at 'https://api.holysheep.ai/v1/chat/completions'
from origin 'https://your-frontend.com' has been blocked by CORS policy
No 'Access-Control-Allow-Origin' header is present on the requested resource
原因:フロントエンドが直接 HolySheep AI API にアクセスしようとしており、API がクロス-origin リクエストを許可していない。
解決方法:プロキシーサーバーを介してリクエストをルーティングします。バックエンド側で以下のように CORS ヘッダーを追加してください。
// Next.js API Route の例(app/api/chat/route.ts)
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${process.env.HOLYSHEEP_API_KEY}
},
body: JSON.stringify({
model: body.model || 'gpt-4.1',
messages: body.messages,
stream: body.stream ?? true
})
});
// ストリーミング応答を返す場合
if (body.stream) {
return new Response(response.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
// 必須:CORS ヘッダー
'Access-Control-Allow-Origin': request.headers.get('origin') || '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
});
}
// 非ストリーミング応答
const data = await response.json();
return NextResponse.json(data, {
headers: {
'Access-Control-Allow-Origin': request.headers.get('origin') || '*'
}
});
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error', details: String(error) },
{ status: 500 }
);
}
}
// プリフライトリクエストの処理
export async function OPTIONS(request: NextRequest) {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': request.headers.get('origin') || '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}
});
}
エラー2:WebSocket 接続時の 403 Forbidden
WebSocket connection to 'wss://api.holysheep.ai/v1/ws' failed:
HTTP response upgrade failure
Status Code: 403 Forbidden
原因:Authorization ヘッダーが WebSocket アップグレードリクエストで認識されない、または API キーが無効。
解決方法:WebSocket 接続時には Authorization ヘッダーが直接渡せないため、クエリパラメータまたは Sec-WebSocket-Protocol ヘッダーを使用します。
// 修正後の接続方法
const wsUrl = new URL('wss://api.holysheep.ai/v1/ws');
wsUrl.searchParams.append('api_key', 'YOUR_HOLYSHEEP_API_KEY');
wsUrl.searchParams.append('model', 'gpt-4.1');
const ws = new WebSocket(wsUrl.toString(), ['gpt-4.1'], {
// 追加のヘッダー(ブラウザによっては無視される)
headers: {
'X-API-Key': 'YOUR_HOLYSHEEP_API_KEY'
}
});
// 代替案:バックエンドをプロキシーとして使用
const PROXY_WS_URL = 'wss://your-backend.com/ws/bridge';
const ws = new WebSocket(PROXY_WS_URL, {
headers: {
'Authorization': Bearer ${apiKey},
'X-Desired-Model': 'gpt-4.1'
}
});
エラー3:接続安定性の問題(切断と再接続のループ)
WebSocket disconnected unexpectedly. Reconnecting... WebSocket disconnected unexpectedly. Reconnecting... WebSocket disconnected unexpectedly. Reconnecting... // ... 無限ループ原因:ネットワーク不安定、アイドルタイムアウト(多くのプロキシは60秒で切断)、またはサーバー側の問題。
解決方法:指数バックオフ方式で再接続を制御し、心拍パケットを送信して接続を維持します。
// 再接続ロジックの実装 class HolySheepWebSocketManager { constructor(options) { this.url = options.url; this.apiKey = options.apiKey; this.maxReconnectAttempts = 5; this.reconnectAttempts = 0; this.baseDelay = 1000; // 1秒 this.maxDelay = 30000; // 30秒 this.heartbeatInterval = 25000; // 25秒ごとの心拍 this.ws = null; this.heartbeatTimer = null; this.isManualClose = false; } connect() { this.isManualClose = false; try { this.ws = new WebSocket(this.url); this.setupEventHandlers(); this.startHeartbeat(); } catch (error) { this.handleError(error); } } setupEventHandlers() { this.ws.onopen = () => { console.log('WebSocket connected'); this.reconnectAttempts = 0; // 認証メッセージを送信 this.send({ type: 'auth', apiKey: this.apiKey }); }; this.ws.onclose = (event) => { console.log(WebSocket closed: ${event.code} - ${event.reason}); this.stopHeartbeat(); if (!this.isManualClose && event.code !== 1000) { this.reconnect(); } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); }; } reconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('Max reconnection attempts reached'); this.emit('maxReconnectAttemptsReached'); return; } // 指数バックオフで遅延を計算 const delay = Math.min( this.baseDelay * Math.pow(2, this.reconnectAttempts), this.maxDelay ); console.log(Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})); setTimeout(() => { this.reconnectAttempts++; this.connect(); }, delay); } startHeartbeat() { this.heartbeatTimer = setInterval(() => { if (this.ws?.readyState === WebSocket.OPEN) { this.send({ type: 'ping', timestamp: Date.now() }); console.log('Heartbeat sent'); } }, this.heartbeatInterval); } stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } } send(data) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } else { console.warn('Cannot send: WebSocket not connected'); } } disconnect() { this.isManualClose = true; this.stopHeartbeat(); if (this.ws) { this.ws.close(1000, 'Client manual disconnect'); } } } // 使用例 const wsManager = new HolySheepWebSocketManager({ url: 'wss://api.holysheep.ai/v1/ws', apiKey: 'YOUR_HOLYSHEEP_API_KEY' }); wsManager.connect();エラー4:認証トークンの有効期限切れ
{"error": {"code": "token_expired", "message": "The API key has expired"}}原因:HolySheep AI の API キーが失効しているか、権限が不十分。
解決方法:キーの有効性を定期的にチェックし、失効前に新しいキーを取得します。
// API キーの有効性チェック async function validateApiKey(apiKey) { try { const response = await fetch('https://api.holysheep.ai/v1/models', { headers: { 'Authorization':Bearer ${apiKey}} }); if (response.ok) { return { valid: true, data: await response.json() }; } if (response.status === 401) { return { valid: false, error: 'Invalid or expired API key' }; } return { valid: false, error:HTTP ${response.status}}; } catch (error) { return { valid: false, error: error.message }; } } // キーの有効期限を監視するクラス class ApiKeyManager { constructor(apiKey) { this.apiKey = apiKey; this.checkInterval = 3600000; // 1時間ごとにチェック this.expiresAt = null; this.timer = null; this.onKeyExpired = null; } async start() { const validation = await validateApiKey(this.apiKey); if (!validation.valid) { console.error('Initial API key validation failed:', validation.error); this.onKeyExpired?.(); return false; } console.log('API key validated successfully'); this.scheduleExpirationCheck(); return true; } scheduleExpirationCheck() { this.timer = setInterval(async () => { const validation = await validateApiKey(this.apiKey); if (!validation.valid) { console.error('API key validation failed'); this.onKeyExpired?.(); this.stop(); } }, this.checkInterval); } stop() { if (this.timer) { clearInterval(this.timer); this.timer = null; } } } // 使用例 const keyManager = new ApiKeyManager('YOUR_HOLYSHEEP_API_KEY'); keyManager.onKeyExpired = () => { console.error('API key expired! Please update your key.'); // ユーザーに通知するロジック alert('APIキーの有効期限が切れました。ダッシュボードで新しいキーを取得してください。'); }; await keyManager.start();セキュリティ上の注意点
- API キーの露出防止:フロントエンドの JavaScript に直接 API キーを埋め込まない。常にバックエンドを通じて HolySheep AI にリクエストをプロキシしてください。
- HTTPS/WSS の強制:本番環境では必ず HTTPS と WSS(WebSocket Secure)を使用してください。HTTP や WS は中間者攻撃の対象となります。
- 入力検証:ユーザーからのメッセージをバックエンドで検証し、不正なデータが HolySheep AI に送信されないようにします。
- レート制限の実装:滥用,防止ためバックエンド側でリクエストの頻度制限を実装してください。HolySheep AI は低コストですが、無制限の呼び出しはシステムに负荷を与えます。
- ログ出力の注意:デバッグログに API キーや用户の会话内容が出力されないよう気をつけてください。実戦では機密情報をマスクするロガーを使用することが重要です。
まとめ
WebSocket による AI リアルタイム対話の実装において、CORS 設定とクロスドメイン対応は不可欠な要素です。HolySheep AI を選定することで、1ドル=1円の為替レートによるコスト最適化、WeChat Pay や Alipay と言った決済手段への対応、そして50ミリ秒未満の低レイテンシという 利点を活用できます。
本記事での実装ポイントをまとめると:
- バックエンドをプロキシーとして使用し、CORS ヘッダーを明示的に設定
- 本番環境では許可 Origin リストを厳格に管理
- WebSocket 切断に備えた指数バックオフ方式の再接続ロジック
- API キーの有効性を定期的に検証
- 心拍パケットによる接続維持
これらの設定を適切に実装することで、安定したリアルタイム AI 対話アプリケーションを構築ことができます。
👉 HolySheep AI に登録して無料クレジットを獲得