크라우니브라우저 2.1 — 앱 모드 / 웹 모드 분기 표준
날짜: 2026-05-28
대상: 크라우니 도메인 서버 개발자 (web/asset/edu/play/bank/dex/architect 등)
브라우저 버전: CrownyBrowser/2.1
SSOT: 이 문서 + /Users/ef/CrownyOS/crownyc/libs/앱모드감지.한선
1. 개요
크라우니브라우저는 일반 웹 브라우저와 달리 4상균형3진 벡터형 응답을 받을 수 있다. 서버는 HTTP 헤더 4개로 클라이언트를 식별하고 분기한다.
- 앱 모드 (app): 크라우니브라우저 — HTML +
<crowny-vector>메타데이터 블록 응답 - 웹 모드 (web): 일반 브라우저 (Chrome/Safari/Firefox) — 표준 HTML만 응답
- 미상 (unknown): UA 누락 등 — 반드시 웹 모드로 fallback (보안)
2. 표준 헤더 4개
2.1 요청 헤더 (클라이언트 → 서버)
| 헤더 | 예시 값 | 의미 |
|---|---|---|
User-Agent | CrownyBrowser/2.0 (CrownyOS; 한선씨) | UA. 위조 가능 — 단독 판정 금지 |
X-Crowny-Browser | 2.1 | 크라우니브라우저 버전. 있으면 앱 확정 |
X-Crowny-Layer | T / O / A / U | 4상균형3진 레이어 (데이터/명령/체이닝/미정) |
Accept | text/html,application/xhtml+xml,application/crowny+vector;q=0.9,*/*;q=0.5 | application/crowny+vector 포함 → 벡터 응답 가능 |
2.2 응답 헤더 (서버 → 클라이언트)
| 헤더 | 값 | 의미 |
|---|---|---|
Vary | User-Agent, Accept, X-Crowny-Browser | 캐시 분리 키 |
X-Crowny-Mode | app / web | 서버가 판정한 모드 (디버그/관측용) |
X-Crowny-Mode-Source | header-check / cookie / force | 판정 근거 |
Content-Type | text/html; charset=utf-8 (웹) <br> text/html; charset=utf-8 (앱, 벡터는 body 내 블록) | 응답 타입 |
3. 분기 알고리즘
1. X-Crowny-Browser 헤더가 있으면 → 앱 모드 (1)
2. (위조 차단) X-Crowny-Browser 없는데
User-Agent 에만 "CrownyBrowser" 있으면 → 웹 모드 (0)
3. User-Agent 가 있고 위 둘 다 해당없으면 → 웹 모드 (0)
4. User-Agent 자체가 없으면 → 미상 (-1) → 웹 모드 fallback
왜 두 헤더가 일치해야 신뢰? UA는 누구나 위조 가능하다. X-Crowny-Browser 는
크라우니브라우저 빌드에 하드코딩되어 있고 일반 브라우저는 보내지 않는다.
두 헤더가 일관되게 들어와야 진짜 크라우니브라우저로 판정한다.
4. 한선씨 서버 코드 예시
javascript가져오기 "라우터.한선"
가져오기 "네트워크.한선"
가져오기 "앱모드감지.한선" // 본 표준 모듈
함수 처리(요청) {
변수 요청헤더 = 요청맵_헤더(요청)
변수 모드 = 앱모드_요청검사(요청헤더)
변수 레이어 = 앱모드_레이어추출(요청헤더)
변수 벡터원함 = 앱모드_벡터응답원하는가(요청헤더)
변수 응답헤더 = 맵생성()
앱모드_응답헤더설정(응답헤더, 모드)
변수 본문 = ""
만약 (모드 == 1) {
// 앱 모드 — 벡터 메타데이터 동봉
본문 = "<html><body><h1>크라우니자산</h1>"
만약 (벡터원함 == 1) {
본문 = 본문 + "<crowny-vector layer=\"" + 레이어 + "\">"
본문 = 본문 + "{\"shape\":\"hero\",\"trits\":27,\"layer\":\"" + 레이어 + "\"}"
본문 = 본문 + "</crowny-vector>"
}
본문 = 본문 + "</body></html>"
} 아니면 {
// 웹 모드 — 표준 HTML만
본문 = "<html><body><h1>자산관리</h1><p>일반 페이지</p></body></html>"
}
반환 HTTP응답(200, "text/html; charset=utf-8", 본문, 응답헤더)
}
5. Node.js 서버 코드 예시 (참고 — 한선씨 미적용 서버)
javascriptfunction detectMode(headers) {
const cb = headers['x-crowny-browser'];
const ua = headers['user-agent'] || '';
if (cb) return 'app'; // 앱 확정
if (ua.includes('CrownyBrowser')) return 'web'; // UA 위조 의심 — 웹 fallback
if (!ua) return 'unknown'; // 미상 — 웹 fallback
return 'web';
}
function getLayer(headers) {
const l = headers['x-crowny-layer'];
return (l === 'T' || l === 'O' || l === 'A' || l === 'U') ? l : 'U';
}
function wantsVector(headers) {
const accept = headers['accept'] || '';
return accept.includes('application/crowny+vector');
}
app.use((req, res, next) => {
const mode = detectMode(req.headers);
const layer = getLayer(req.headers);
const wantVec = wantsVector(req.headers);
res.setHeader('Vary', 'User-Agent, Accept, X-Crowny-Browser');
res.setHeader('X-Crowny-Mode', mode === 'unknown' ? 'web' : mode);
res.setHeader('X-Crowny-Mode-Source', 'header-check');
req.crownyMode = mode;
req.crownyLayer = layer;
req.crownyWantsVector = wantVec;
next();
});
app.get('/', (req, res) => {
if (req.crownyMode === 'app' && req.crownyWantsVector) {
const meta = JSON.stringify({ shape: 'hero', trits: 27, layer: req.crownyLayer });
res.send(`<html><body><h1>크라우니</h1>
<crowny-vector layer="${req.crownyLayer}">${meta}</crowny-vector>
</body></html>`);
} else {
res.send(`<html><body><h1>표준 페이지</h1></body></html>`);
}
});
참고: 새 도메인 서버는 반드시 한선씨로 작성한다. Node.js 예시는 기존 레거시 서버
마이그레이션 가이드용이며, 동반 .한선 파일을 필수로 만든다.6. 응답 형식
6.1 웹 모드 응답 (일반)
html<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>자산관리</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<h1>크라우니 자산관리</h1>
<p>일반 콘텐츠</p>
</body>
</html>
6.2 앱 모드 응답 (벡터 메타데이터 포함)
html<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="crowny-design" content="premium-v1">
<title>자산관리</title>
</head>
<body>
<h1>크라우니 자산관리</h1>
<p>일반 콘텐츠</p>
<!-- 크라우니브라우저 전용 메타데이터 블록 (다른 브라우저는 무시) -->
<crowny-vector layer="T">
{
"shape": "hero-card",
"trits": 27,
"layer": "T",
"cube": [1,0,-1,0,1,-1,...],
"actions": [
{"name":"열기", "opcode":347, "args":[0,0,800,600]},
{"name":"채우기", "opcode":349, "args":[1,0,-1]}
]
}
</crowny-vector>
</body>
</html>
<crowny-vector>태그는 표준 HTML 파서가 무시한다 (커스텀 요소).- 크라우니브라우저는 이 블록을 추출해 GPU렌더러로 직접 전달한다.
- 모바일/iframe 안전:
display:none기본.
7. 캐시 가이드
7.1 Vary 헤더 필수
Vary: User-Agent, Accept, X-Crowny-Browser
- 같은 URL에 앱/웹 응답이 분리 캐시되어야 한다.
- CDN/리버스 프록시(crowny-gateway)는 이 키로 캐시 분리.
Accept-Language도 다국어 분기에 필요하면 추가.
7.2 Cache-Control
Cache-Control: public, max-age=300, must-revalidate
- 앱 모드 응답은 벡터 옵코드가 자주 바뀔 수 있으므로 짧게 (5분 권장).
- 정적 자산(이미지, 폰트, CSS)은 분기 없이 공통 — 길게 (1년).
7.3 ETag
벡터 메타데이터의 SHA256 해시를 ETag로 사용 권장:
ETag: "T-a8f3c9d2..." // 레이어 prefix + 해시
8. 보안 가이드
8.1 헤더 위조 시 fallback
X-Crowny-Browser헤더는 누구나 보낼 수 있다 — 단독으로 신뢰 금지.- UA만
CrownyBrowser라고 주장하면 웹 모드로 처리 (위조 의심). - 진짜 크라우니브라우저는 두 헤더를 일관되게 보낸다.
8.2 벡터 메타데이터에 민감 정보 금지
<crowny-vector>블록은 일반 브라우저에서도 view-source로 보인다.- 인증 토큰, PII, 내부 ID 등을 박지 마라.
- 시각/구조 정보만 (도형, 좌표, opcode, 색상).
8.3 X-Crowny-Layer 검증
- 클라이언트가 보내는
X-Crowny-Layer값은 신뢰하지 않는다 (단순 힌트). - 서버 비즈니스 로직(권한, 가격, 인증)은 layer로 분기하지 마라.
- layer는 렌더링 분기에만 사용.
8.4 Content-Security-Policy
앱 모드 응답에도 CSP 적용:
Content-Security-Policy: default-src 'self' crowny-internal:; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
9. 점진 적용 가이드
- 1단계: 응답 헤더만 추가 (
Vary,X-Crowny-Mode). 본문은 동일. - 2단계: 모드 판정 함수 도입 (
앱모드_요청검사). 로깅으로 비율 측정. - 3단계: 앱 모드 응답에
<crowny-vector>블록 점진 추가. - 4단계: 캐시 분리 (
Vary활용) + CDN 설정. - 5단계: 벡터 옵코드 정식 채택 — premium-v1 디자인 시스템과 통합.
10. 관련 파일
- 헤더 전송 (클라이언트):
/Users/ef/CrownyBrowser/src/v2/HTTP스택.한선§2 (기본헤더설정) - 모드 감지 (서버):
/Users/ef/CrownyOS/crownyc/libs/앱모드감지.한선 - 브라우저 메인 빌드:
/Users/ef/CrownyBrowser/src/v2/크라우니브라우저v2.한선 - 게이트웨이 캐시:
/Users/ef/crowny-gateway/cache.yaml(Vary 키 등록)
11. 잔여 이슈
<crowny-vector>파싱 옵코드는 GPU렌더러에 아직 미통합 (v2.2 예정)- crowny-gateway 의
Vary멀티 키 캐시 검증 필요 - 모바일 크라우니브라우저(iOS/Android)는 미구현 — UA 식별만 가능
- application/crowny+vector MIME 등록 (IANA 자체 영역) 검토 중