Presentation: 데이터 표현 형식을 변환해 서로 다른 시스템의 통신을 가능하게 하는 계층
표현 계층(Presentation Layer)은 애플리케이션이 만든 데이터를 전송 가능한 공통 형식으로 변환하고, 수신 측에서 원래 형태로 복원한다. 문자 인코딩, 직렬화, 압축, 암호화가 대표 기능이다. 실제 인터넷 시스템에서는 독립된 계층보다 응용 계층 프로토콜과 라이브러리에 기능이 분산되어 있다.
By 송민성
1. 개념
표현 계층(Presentation Layer)은 개방형 시스템 상호 연결 모델(OSI Model)의 6계층이다. 서로 다른 시스템이 데이터의 의미를 동일하게 해석할 수 있도록 표현 형식을 변환한다.
주요 기능은 다음과 같다.
- 문자 인코딩(character encoding): 문자열을 바이트로 변환
- 직렬화(serialization): 객체나 구조화된 데이터를 전송 형식으로 변환
- 압축(compression): 데이터 크기를 줄임
- 암호화(encryption): 데이터를 허가되지 않은 대상이 읽지 못하게 변환
- 형식 변환(format conversion): 시스템별 데이터 표현 차이를 조정
예를 들어 다음 객체는 네트워크로 직접 전송할 수 없다.
{
이름: "민수",
나이: 30
}네트워크는 바이트(byte)를 전송하므로 객체를 JSON 문자열로 직렬화하고, 문자열을 UTF-8 바이트로 인코딩해야 한다.
객체
↓ JSON 직렬화
문자열
↓ UTF-8 인코딩
바이트
↓ 압축 또는 암호화
전송 데이터수신 측은 이 과정을 반대로 수행한다.
표현 계층은 개념적 분류다. 현대 인터넷 프로토콜에서는 이 기능이 독립된 계층에만 존재하지 않는다. JSON과 프로토콜 버퍼(Protocol Buffers)는 응용 계층에서 직렬화를 담당하고, 전송 계층 보안(TLS)은 응용 계층과 전송 계층 사이에서 암호화를 제공한다.
2. 왜 사용하는가
컴퓨터마다 내부 데이터 표현이 같다는 보장은 없다.
다음 차이가 통신 문제를 만든다.
- 문자 인코딩: UTF-8, UTF-16
- 정수 바이트 순서(byte order): 빅 엔디언(big-endian), 리틀 엔디언(little-endian)
- 데이터 구조: 언어별 객체와 배열 표현
- 숫자 표현: 정수 크기, 부동소수점 형식
- 미디어 형식: JPEG, PNG, WebP
- 압축 형식: gzip, Brotli
- 암호화 방식과 키
표현 형식을 명시하지 않으면 같은 바이트를 서로 다르게 해석할 수 있다.
예를 들어 문자 가는 UTF-8에서 세 바이트로 표현된다.
EA B0 80수신 측이 다른 문자 인코딩으로 해석하면 원래 문자를 복원하지 못한다.
공통 표현 형식을 사용하면 구현 언어가 달라도 데이터를 교환할 수 있다.
TypeScript 객체
↓ JSON + UTF-8
네트워크 바이트
↓ JSON + UTF-8
Python 딕셔너리3. 동작 원리
송신 과정
송신 측은 일반적으로 다음 변환을 수행한다.
- 애플리케이션 데이터를 직렬화한다.
- 문자열이나 필드를 바이트로 인코딩한다.
- 필요하면 압축한다.
- 필요하면 암호화한다.
- 변환 방식에 관한 메타데이터를 프로토콜 헤더에 기록한다.
HTTP 응답 예시는 다음과 같다.
Content-Type: application/json; charset=utf-8
Content-Encoding: gzipContent-Type은 데이터 형식과 문자 인코딩을 나타낸다. Content-Encoding은 본문에 적용된 압축 방식을 나타낸다.
수신 과정
수신 측은 송신 과정의 역순으로 데이터를 복원한다.
- 암호화된 데이터를 복호화한다.
- 압축된 데이터를 해제한다.
- 바이트를 문자열이나 필드로 디코딩한다.
- 직렬화 형식을 해석해 애플리케이션 데이터로 복원한다.
송신:
객체 → JSON → UTF-8 → gzip → 암호화
수신:
암호문 → 복호화 → gzip 해제 → UTF-8 → JSON 객체변환 순서가 다르면 데이터를 복원할 수 없다. 압축은 반복되는 패턴을 이용하므로 일반적으로 암호화 전에 수행한다. 암호화된 데이터는 반복 패턴이 드러나지 않도록 설계되기 때문에 압축 효과를 기대하기 어렵다.
직렬화 형식
대표적인 직렬화 형식은 다음과 같다.
| 형식 | 표현 방식 | 특징 |
|---|---|---|
| JSON | 텍스트 | 사람이 읽기 쉽고 웹에서 널리 사용 |
| XML | 텍스트 | 문서 구조와 스키마 표현에 사용 |
| 프로토콜 버퍼(Protocol Buffers) | 이진 | 스키마 기반, 작은 크기와 빠른 처리 |
| 메시지팩(MessagePack) | 이진 | JSON과 비슷한 데이터 모델을 이진으로 표현 |
| CBOR | 이진 | 작은 데이터 크기가 필요한 환경에서 사용 |
텍스트 형식은 디버깅하기 쉽지만 숫자와 필드 이름을 문자열로 표현하므로 이진 형식보다 커질 수 있다. 이진 형식은 작고 빠를 수 있지만 스키마 관리와 전용 도구가 필요하다.
네트워크 바이트 순서
여러 바이트로 구성된 정수는 바이트 순서를 합의해야 한다. 인터넷 프로토콜은 빅 엔디언(big-endian)을 네트워크 바이트 순서(network byte order)로 사용한다.
정수 0x12345678의 표현은 다음과 같다.
빅 엔디언: 12 34 56 78
리틀 엔디언: 78 56 34 12송신 측과 수신 측이 다른 순서를 사용하면 전혀 다른 숫자로 해석된다.
4. 코드 예제
다음 코드는 객체를 JSON으로 직렬화하고, UTF-8로 인코딩한 뒤 gzip으로 압축한다. 수신 측에서는 역순으로 원본 객체를 복원한다.
import { gzipSync, gunzipSync } from "node:zlib";
type UserMessage = {
id: number;
name: string;
roles: string[];
};
function encodeMessage(message: UserMessage): Buffer {
const json = JSON.stringify(message);
const utf8 = Buffer.from(json, "utf8");
return gzipSync(utf8);
}
function decodeMessage(data: Buffer): UserMessage {
const utf8 = gunzipSync(data);
const json = utf8.toString("utf8");
const parsed: unknown = JSON.parse(json);
if (
typeof parsed !== "object" ||
parsed === null ||
typeof (parsed as UserMessage).id !== "number" ||
typeof (parsed as UserMessage).name !== "string" ||
!Array.isArray((parsed as UserMessage).roles)
) {
throw new Error("올바르지 않은 메시지 형식");
}
return parsed as UserMessage;
}
const original: UserMessage = {
id: 42,
name: "민수",
roles: ["개발자", "관리자"],
};
const encoded = encodeMessage(original);
const decoded = decodeMessage(encoded);
console.log("원본 바이트 크기:", Buffer.byteLength(JSON.stringify(original), "utf8"));
console.log("압축 바이트 크기:", encoded.length);
console.log("복원 결과:", decoded);
if (JSON.stringify(original) !== JSON.stringify(decoded)) {
throw new Error("원본과 복원 결과가 다름");
}파일을 presentation.ts로 저장한 뒤 실행한다.
npx tsx presentation.ts압축 결과가 항상 더 작지는 않다. 데이터가 짧거나 반복 패턴이 적으면 gzip 헤더와 압축 메타데이터 때문에 크기가 증가할 수 있다.
다음 코드는 32비트 정수를 네트워크 바이트 순서로 변환한다.
const buffer = Buffer.alloc(4);
buffer.writeUInt32BE(0x12345678, 0);
console.log(buffer);
// <Buffer 12 34 56 78>
const value = buffer.readUInt32BE(0);
console.log(value.toString(16));
// 12345678writeUInt32BE와 readUInt32BE의 BE는 빅 엔디언(big-endian)을 뜻한다.
5. 시간 복잡도 또는 성능 특성
입력 데이터 크기를 n바이트라고 할 때 일반적인 특성은 다음과 같다.
- UTF-8 인코딩과 디코딩:
O(n) - Base64 인코딩과 디코딩:
O(n) - JSON 직렬화와 역직렬화: 처리하는 전체 값의 크기에 비례해
O(n) - 압축과 해제: 알고리즘과 구현에 따라 다르지만 전체 입력을 읽어야 하므로 최소
Ω(n) - 대칭키 암호화와 복호화: 처리하는 데이터 크기에 비례해
O(n)
Base64는 원본 3바이트를 4개의 문자로 표현한다. 패딩을 포함한 결과 크기는 다음과 같다.
4 × ceil(n / 3) 바이트따라서 Base64는 데이터 크기를 줄이지 않는다. 이진 데이터를 텍스트 전용 경로로 전달하기 위한 인코딩 방식이며 암호화 방식도 아니다.
직렬화 형식 선택은 다음 비용에 영향을 준다.
- 네트워크 전송량
- 중앙 처리 장치(CPU) 사용량
- 메모리 할당량
- 디코딩 지연 시간
- 스키마 변경 비용
압축은 전송량을 줄이지만 압축과 해제에 CPU 시간이 필요하다. 작은 응답은 압축 비용이 절감되는 전송 비용보다 클 수 있다.
6. 실무 사용 사례
HTTP API
서버와 클라이언트는 콘텐츠 유형(content type)을 통해 표현 형식을 합의한다.
Content-Type: application/json; charset=utf-8
Accept: application/json서버는 Content-Encoding으로 압축 방식을 알린다.
Content-Encoding: br클라이언트는 Accept-Encoding으로 처리 가능한 압축 방식을 전달한다.
Accept-Encoding: gzip, br원격 프로시저 호출
원격 프로시저 호출(RPC) 시스템은 구조화된 요청을 이진 메시지로 변환한다. gRPC는 주로 프로토콜 버퍼를 사용하며, 송신자와 수신자가 공유하는 스키마로 필드의 의미와 자료형을 정의한다.
데이터베이스 연결
데이터베이스 드라이버는 애플리케이션 자료형과 데이터베이스 자료형을 변환한다.
TypeScript Date
↓ 드라이버 인코딩
PostgreSQL timestamp
↓ 드라이버 디코딩
TypeScript Date 또는 문자열시간대(time zone), 숫자 정밀도, 널(null) 처리 규칙이 일치하지 않으면 데이터가 다르게 해석될 수 있다.
웹 보안 통신
전송 계층 보안(TLS)은 데이터를 암호화하고 무결성을 검증한다. OSI 모델의 표현 계층 기능과 일부 겹치지만, 실제 프로토콜 스택에서 독립된 표현 계층 프로토콜로 구현되는 것은 아니다.
이미지와 영상 전송
브라우저와 서버는 콘텐츠 협상(content negotiation)을 통해 지원하는 미디어 형식을 선택할 수 있다.
Accept: image/avif,image/webp,image/png서버는 지원 범위와 캐시 정책에 따라 AVIF, WebP, PNG 중 하나를 반환한다.
7. 주의할 점
인코딩 정보를 명시한다
문자열을 전송하면서 문자 인코딩을 명시하지 않으면 환경별 기본값 때문에 문자가 깨질 수 있다. 웹에서는 UTF-8을 명시적으로 사용한다.
Content-Type: application/json; charset=utf-8역직렬화 결과를 신뢰하지 않는다
JSON 파싱에 성공했다는 사실은 데이터가 올바른 구조라는 뜻이 아니다. 네트워크 입력은 자료형, 필수 필드, 길이와 허용 범위를 검증해야 한다.
Base64를 암호화로 오해하지 않는다
Base64 데이터는 누구나 원본으로 되돌릴 수 있다. 기밀성이 필요하면 검증된 암호화 프로토콜을 사용한다.
압축과 비밀값을 함께 다룰 때 주의한다
공격자가 제어하는 입력과 비밀값이 같은 압축 단위에 포함되면 압축 결과 크기를 이용한 정보 노출 공격이 가능하다. 인증 토큰 같은 비밀값을 공격자 입력과 함께 압축하지 않는다.
숫자 정밀도를 확인한다
JSON은 숫자의 구체적인 저장 크기를 정의하지 않는다. JavaScript의 number는 정수 -(2^53 - 1)부터 2^53 - 1까지만 정확하게 표현한다. 이 범위를 넘는 정수 식별자는 문자열로 전송하는 편이 안전하다.
버전 호환성을 설계한다
메시지 형식에 필드를 추가하거나 제거하면 구버전 클라이언트가 처리하지 못할 수 있다. 선택 필드, 기본값, 스키마 버전, 알 수 없는 필드 처리 규칙을 정해야 한다.
8. 핵심 정리
- 표현 계층(Presentation Layer)은 데이터의 의미를 유지하면서 전송 가능한 표현 형식으로 변환한다.
- 문자 인코딩, 직렬화, 압축, 암호화, 바이트 순서 변환이 주요 기능이다.
- 송신 측의 변환 순서와 수신 측의 복원 순서는 서로 반대다.
- 현대 네트워크에서는 표현 계층 기능이 HTTP, TLS, JSON, 프로토콜 버퍼 같은 프로토콜과 라이브러리에 분산되어 있다.
- 형식 선택 시 전송량뿐 아니라 처리 시간, 정밀도, 보안, 버전 호환성을 함께 고려해야 한다.