WIA-LANG-001 통신 프로토콜은 분산된 언어 보존 시스템 간의 안전하고 효율적인 데이터 교환을 가능하게 합니다. 전 세계 수천 개의 언어 보존 프로젝트가 서로 다른 환경에서 운영되므로, 표준화된 통신 프로토콜은 상호운용성을 보장하는 핵심 요소입니다.
WIA-LANG 프로토콜 스택은 OSI 모델을 기반으로 다음과 같은 계층으로 구성됩니다:
| 계층 | 프로토콜 | 역할 | 예시 |
|---|---|---|---|
| 응용 계층 | WIA-LANG Protocol | 언어 데이터 교환 | 어휘 동기화, 메타데이터 전송 |
| 표현 계층 | JSON/MessagePack | 데이터 직렬화 | JSON 인코딩/디코딩 |
| 세션 계층 | WebSocket/gRPC | 연결 관리 | 지속적 연결, 양방향 통신 |
| 전송 계층 | TLS 1.3 | 보안 전송 | 암호화, 인증 |
| 네트워크 계층 | TCP/IP, HTTP/3 | 패킷 라우팅 | 데이터 전송 |
WIA-LANG의 기본 통신 프로토콜은 HTTPS입니다. 모든 데이터 전송은 TLS 1.3 이상으로 암호화되어야 하며, 弘益人間 철학에 따라 개방성과 보안성을 동시에 추구합니다.
// 표준 HTTPS 요청 헤더
GET /v1/languages/lang-jje-001 HTTP/2
Host: api.wia.org
User-Agent: WIA-LANG-Client/1.0 (Philosophy: 弘益人間)
Accept: application/json
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
X-WIA-Request-ID: req-abc123
X-WIA-Client-Version: 1.0.0
X-WIA-Philosophy: 弘益人間
// 표준 HTTPS 응답 헤더
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Content-Length: 2048
Cache-Control: public, max-age=3600
ETag: "686897696a7c876b7e"
X-WIA-Request-ID: req-abc123
X-WIA-Server-Version: 1.0.0
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1735486800
X-WIA-Philosophy: 弘益人間
{
"status": "success",
"data": { ... },
"meta": {
"philosophy": "弘益人間",
"version": "1.0"
}
}
| 헤더명 | 설명 | 필수여부 | 예시 |
|---|---|---|---|
X-WIA-Request-ID |
요청 추적 ID | 권장 | req-abc123 |
X-WIA-Client-Version |
클라이언트 버전 | 권장 | 1.0.0 |
X-WIA-Philosophy |
弘益人間 철학 표명 | 선택 | 弘益人間 |
X-RateLimit-Limit |
시간당 요청 한도 | 응답 시 포함 | 1000 |
X-RateLimit-Remaining |
남은 요청 수 | 응답 시 포함 | 998 |
실시간 언어 데이터 동기화와 협업을 위해 WIA-LANG은 WebSocket 프로토콜을 지원합니다. 이를 통해 여러 연구자가 동시에 같은 언어 데이터를 편집하고, 변경사항을 즉시 공유할 수 있습니다.
// WebSocket 클라이언트 연결
import { WIALangWebSocket } from '@wia/lang-client';
const ws = new WIALangWebSocket({
url: 'wss://realtime.wia.org/v1/stream',
apiKey: process.env.WIA_API_KEY,
philosophy: '弘益人間'
});
// 연결 이벤트
ws.on('connect', () => {
console.log('WIA-LANG WebSocket 연결됨');
// 특정 언어 채널 구독
ws.subscribe('language:jje', {
events: ['word.created', 'word.updated', 'audio.uploaded']
});
});
// 실시간 데이터 수신
ws.on('word.created', (data) => {
console.log('새 단어 추가됨:', data);
/*
{
"languageId": "lang-jje-001",
"wordId": "word-jje-2501",
"lemma": "고망",
"contributor": "김제주",
"timestamp": "2025-12-29T14:30:00Z"
}
*/
});
// 연결 끊김 처리
ws.on('disconnect', (reason) => {
console.log('연결 끊김:', reason);
// 자동 재연결 시도
setTimeout(() => ws.reconnect(), 5000);
});
// 핑/퐁으로 연결 유지
ws.on('ping', () => {
ws.pong();
});
// 클라이언트 -> 서버 (구독 요청)
{
"type": "subscribe",
"channel": "language:jje",
"events": ["word.created", "word.updated"],
"filters": {
"partOfSpeech": "noun"
},
"philosophy": "弘益人間"
}
// 서버 -> 클라이언트 (구독 확인)
{
"type": "subscribed",
"channel": "language:jje",
"subscriptionId": "sub-xyz789",
"timestamp": "2025-12-29T14:30:00Z"
}
// 서버 -> 클라이언트 (실시간 이벤트)
{
"type": "event",
"event": "word.created",
"channel": "language:jje",
"data": {
"wordId": "word-jje-2501",
"lemma": "고망",
"translation": "고맙다",
"contributor": "김제주"
},
"timestamp": "2025-12-29T14:30:15Z",
"philosophy": "弘益人間"
}
대용량 언어 데이터 전송과 마이크로서비스 간 통신을 위해 WIA-LANG은 gRPC를 지원합니다. gRPC는 HTTP/2 기반의 고성능 RPC 프레임워크로, Protocol Buffers를 사용하여 효율적인 직렬화를 제공합니다.
// wia_lang.proto
syntax = "proto3";
package wia.lang.v1;
// 언어 서비스 정의
service LanguageService {
// 언어 목록 조회
rpc ListLanguages(ListLanguagesRequest) returns (ListLanguagesResponse);
// 특정 언어 조회
rpc GetLanguage(GetLanguageRequest) returns (Language);
// 어휘 목록 조회 (스트리밍)
rpc StreamLexicon(StreamLexiconRequest) returns (stream Word);
// 오디오 업로드 (클라이언트 스트리밍)
rpc UploadAudio(stream AudioChunk) returns (AudioUploadResponse);
// 실시간 동기화 (양방향 스트리밍)
rpc SyncLanguageData(stream SyncRequest) returns (stream SyncResponse);
}
// 메시지 정의
message Language {
string id = 1;
string code = 2;
string name = 3;
string english_name = 4;
EndangermentLevel endangerment_level = 5;
int32 speaker_count = 6;
Region region = 7;
string philosophy = 8; // "弘益人間"
}
enum EndangermentLevel {
SAFE = 0;
VULNERABLE = 1;
ENDANGERED = 2;
CRITICALLY_ENDANGERED = 3;
EXTINCT = 4;
}
message Region {
string country = 1;
string province = 2;
Coordinates coordinates = 3;
}
message Coordinates {
double latitude = 1;
double longitude = 2;
}
message Word {
string id = 1;
string lemma = 2;
string translation = 3;
string part_of_speech = 4;
Pronunciation pronunciation = 5;
}
message Pronunciation {
string ipa = 1;
string audio_url = 2;
}
message ListLanguagesRequest {
int32 page = 1;
int32 limit = 2;
EndangermentLevel endangerment_level = 3;
string region = 4;
}
message ListLanguagesResponse {
repeated Language languages = 1;
Pagination pagination = 2;
string philosophy = 3; // "弘益人間"
}
message Pagination {
int32 current_page = 1;
int32 total_pages = 2;
int32 total_items = 3;
}
// Node.js gRPC 클라이언트
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Proto 파일 로드
const packageDefinition = protoLoader.loadSync('wia_lang.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const wiaLangProto = grpc.loadPackageDefinition(packageDefinition).wia.lang.v1;
// gRPC 클라이언트 생성
const client = new wiaLangProto.LanguageService(
'grpc.wia.org:443',
grpc.credentials.createSsl()
);
// 언어 목록 조회
client.listLanguages({
page: 1,
limit: 20,
endangerment_level: 'CRITICALLY_ENDANGERED',
region: 'Asia'
}, (error, response) => {
if (error) {
console.error('오류:', error);
return;
}
console.log(`총 ${response.pagination.total_items}개 언어 발견`);
console.log(`철학: ${response.philosophy}`); // "弘益人間"
for (const lang of response.languages) {
console.log(`${lang.name} (${lang.code}): ${lang.speaker_count}명`);
}
});
// 어휘 스트리밍 조회
const stream = client.streamLexicon({
language_id: 'lang-jje-001',
limit: 1000
});
stream.on('data', (word) => {
console.log(`${word.lemma} (${word.translation})`);
});
stream.on('end', () => {
console.log('스트리밍 완료');
});
stream.on('error', (error) => {
console.error('스트리밍 오류:', error);
});
언어 데이터는 오디오, 비디오, 이미지 등 대용량 파일을 포함하므로, 효율적인 데이터 압축과 전송 최적화가 필수적입니다.
| 압축 방식 | 압축률 | 속도 | 사용 사례 |
|---|---|---|---|
| Gzip | 중간 (70-80%) | 빠름 | JSON, XML 텍스트 데이터 |
| Brotli | 높음 (75-85%) | 중간 | 웹 API 응답 |
| Zstandard | 높음 (80-90%) | 매우 빠름 | 대용량 데이터베이스 백업 |
| FLAC | 높음 (50-70%) | 중간 | 오디오 파일 (무손실) |
| MessagePack | 중간 (60-70%) | 매우 빠름 | 실시간 바이너리 데이터 |
// 클라이언트 압축 지원 명시
GET /v1/languages HTTP/2
Host: api.wia.org
Accept-Encoding: br, gzip, deflate
// 서버 응답 (Brotli 압축 사용)
HTTP/2 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding
[Brotli 압축된 데이터]
언어 데이터의 무결성과 기밀성을 보장하기 위해 WIA-LANG은 다층 보안 프로토콜을 적용합니다.
// Node.js TLS 설정 예제
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
ca: fs.readFileSync('ca-cert.pem'),
// TLS 1.3만 허용
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
// 안전한 암호화 스위트
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256'
].join(':'),
// Forward Secrecy 활성화
honorCipherOrder: true,
// 클라이언트 인증 요구 (선택적)
requestCert: false,
rejectUnauthorized: true
};
const server = https.createServer(options, (req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
'X-WIA-Philosophy': '弘益人間'
});
res.end(JSON.stringify({ status: 'secure' }));
});
server.listen(443, () => {
console.log('WIA-LANG 보안 서버 실행 중 (TLS 1.3)');
});
모든 전송 데이터는 체크섬을 통해 무결성을 검증합니다.
// SHA-256 체크섬 계산 및 검증
const crypto = require('crypto');
function calculateChecksum(data) {
return crypto
.createHash('sha256')
.update(JSON.stringify(data))
.digest('hex');
}
// 데이터 전송 시 체크섬 포함
const languageData = {
id: 'lang-jje-001',
name: '제주어',
philosophy: '弘익人間'
};
const checksum = calculateChecksum(languageData);
const payload = {
data: languageData,
checksum: checksum,
algorithm: 'sha256'
};
// 수신 측 검증
function verifyChecksum(payload) {
const receivedChecksum = payload.checksum;
const calculatedChecksum = calculateChecksum(payload.data);
if (receivedChecksum !== calculatedChecksum) {
throw new Error('데이터 무결성 검증 실패!');
}
console.log('✓ 체크섬 검증 성공');
return payload.data;
}
try {
const verifiedData = verifyChecksum(payload);
console.log('검증된 데이터:', verifiedData);
} catch (error) {
console.error('오류:', error.message);
}
네트워크 불안정성을 고려하여 WIA-LANG은 지능적인 재시도 메커니즘을 제공합니다.
// 지수 백오프 재시도 구현
async function fetchWithRetry(url, options = {}, maxRetries = 5) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
console.log(`시도 ${attempt + 1}/${maxRetries}`);
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'X-WIA-Retry-Attempt': attempt.toString(),
'X-WIA-Philosophy': '弘益人間'
}
});
if (response.ok) {
return await response.json();
}
// 429 (Too Many Requests) 처리
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : calculateBackoff(attempt);
console.log(`Rate limit 도달. ${waitTime}ms 대기 중...`);
await sleep(waitTime);
continue;
}
// 5xx 서버 오류는 재시도
if (response.status >= 500) {
throw new Error(`서버 오류: ${response.status}`);
}
// 4xx 클라이언트 오류는 재시도 안 함
throw new Error(`클라이언트 오류: ${response.status}`);
} catch (error) {
lastError = error;
console.error(`오류 발생:`, error.message);
if (attempt < maxRetries - 1) {
const backoffTime = calculateBackoff(attempt);
console.log(`${backoffTime}ms 후 재시도...`);
await sleep(backoffTime);
}
}
}
throw new Error(`최대 재시도 횟수 초과: ${lastError.message}`);
}
function calculateBackoff(attempt) {
// 지수 백오프: 2^attempt * 1000ms + 랜덤 지터
const exponentialTime = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * 1000;
return Math.min(exponentialTime + jitter, 60000); // 최대 60초
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 사용 예제
try {
const data = await fetchWithRetry('https://api.wia.org/v1/languages', {
method: 'GET',
headers: {
'Authorization': 'Bearer token123'
}
});
console.log('데이터 조회 성공:', data);
} catch (error) {
console.error('최종 실패:', error.message);
}
// Circuit Breaker 구현
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker OPEN 상태 - 서비스 일시 중단');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
console.log('✓ Circuit breaker: CLOSED');
}
onFailure() {
this.failureCount++;
console.log(`✗ Circuit breaker: 실패 ${this.failureCount}/${this.threshold}`);
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
console.log(`⚠ Circuit breaker: OPEN (${this.timeout}ms 동안)`);
}
}
getState() {
return this.state;
}
}
// 사용 예제
const breaker = new CircuitBreaker(3, 30000);
async function callAPI() {
return breaker.execute(async () => {
const response = await fetch('https://api.wia.org/v1/languages');
if (!response.ok) throw new Error('API 오류');
return response.json();
});
}
// 여러 번 호출 시도
for (let i = 0; i < 10; i++) {
try {
const data = await callAPI();
console.log('성공:', data.meta?.philosophy); // "弘益人間"
} catch (error) {
console.error('실패:', error.message);
}
await sleep(1000);
}
중앙 서버 의존도를 낮추고 弘益人間 정신에 따라 분산된 언어 보존을 실현하기 위해, WIA-LANG은 선택적으로 P2P (Peer-to-Peer) 통신을 지원합니다.
// WebRTC P2P 연결 설정
import { WIALangP2P } from '@wia/lang-p2p';
const p2p = new WIALangP2P({
peerId: 'peer-researcher-042',
signalServer: 'wss://signal.wia.org',
philosophy: '弘益人間'
});
// 시그널 서버 연결
await p2p.connect();
// 다른 연구자와 P2P 연결
const peer = await p2p.connectToPeer('peer-linguist-123');
// 언어 데이터 직접 전송
peer.send({
type: 'language_data',
languageId: 'lang-jje-001',
data: {
wordId: 'word-jje-2501',
lemma: '고망',
audio: [오디오 바이너리 데이터]
}
});
// 데이터 수신
peer.on('data', (message) => {
console.log('P2P 데이터 수신:', message);
if (message.type === 'language_data') {
// 로컬 데이터베이스에 저장
saveLanguageData(message.data);
}
});
console.log('弘益人間 - P2P로 언어 데이터 공유 중');
X-RateLimit-Limit과 X-RateLimit-Remaining 헤더가 중요한 이유는 무엇이며, 클라이언트는 이 정보를 어떻게 활용해야 하나요?