DNS와 HTTP/HTTPS (DNS & HTTP/HTTPS)

핵심 인사이트 (3줄 요약)

DNS는 도메인을 IP로 변환하는 분산 계층형 데이터베이스. HTTP는 비연결·무상태 텍스트 프로토콜이며, HTTPS는 TLS 1.3 암호화를 추가한 보안 버전. HTTP/2·HTTP/3(QUIC)로 성능이 혁신적으로 개선되었다.


Ⅰ. 개요 (필수: 200자 이상)

개념:

  • DNS(Domain Name System): 인간 친화적 도메인 이름(www.example.com)을 기계 친화적 IP 주소(93.184.216.34)로 변환하는 전 세계 분산 계층형 데이터베이스 시스템
  • HTTP/HTTPS: 웹 상에서 하이퍼텍스트를 전송하기 위한 요청-응답 기반 애플리케이션 계층 프로토콜. HTTPS는 TLS/SSL 암호화 계층 추가

💡 비유: DNS는 "인터넷 전화번호부" 같아요. "홍길동"(도메인)을 찾으면 "010-1234-5678"(IP)를 알려주죠. HTTP는 "음식 배달 주문서" 같아요. 주문서(요청)를 내면 음식(응답)이 오고, 배달이 끝나면 연락이 끊기죠!

등장 배경 (필수: 3가지 이상 기술):

  1. 기존 문제점 - IP 주소 암기 불가: 192.168.1.1 같은 숫자는 사람이 기억하기 어려움. hosts 파일 수동 관리의 한계
  2. 기술적 필요성 - 분산 관리: 단일 서버로 전 세계 도메인 관리 불가. 계층적 위임 구조 필요
  3. 시장/산업 요구 - 웹 서비스 확산: 1990년대 웹 폭발로 HTTP가 사실상 인터넷 표준 프로토콜이 됨. 보안(HTTPS)·성능(HTTP/2·3) 요구 증가

핵심 목적: 사용자 친화적 주소 체계, 안전하고 빠른 웹 콘텐츠 전송


Ⅱ. 구성 요소 및 핵심 원리 (필수: 가장 상세하게)

DNS 계층 구조 (필수: ASCII 아트):

┌─────────────────────────────────────────────────────────────────────────┐
│                        DNS 계층 구조                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│                            ┌─────────┐                                  │
│                            │   .     │  ← Root DNS (전 세계 13개 클러스터)│
│                            │  Root   │    (A~M 서버, Anycast 운영)       │
│                            └────┬────┘                                  │
│                                 │                                       │
│         ┌───────────────────────┼───────────────────────┐              │
│         │                       │                       │               │
│    ┌────▼────┐           ┌─────▼─────┐          ┌──────▼──────┐       │
│    │   .com  │           │   .net    │          │    .kr      │       │
│    │  TLD    │           │   TLD     │          │   ccTLD     │       │
│    └────┬────┘           └─────┬─────┘          └──────┬──────┘       │
│         │                      │                       │               │
│    ┌────▼────┐                 │                 ┌─────▼─────┐        │
│    │example  │                 │                 │   .co     │        │
│    │.com SLD │                 │                 │  2nd TLD  │        │
│    └────┬────┘                 │                 └─────┬─────┘        │
│         │                      │                       │               │
│    ┌────▼────┐                 │                 ┌─────▼─────┐        │
│    │  www    │                 │                 │  naver    │        │
│    │ Subdomain                │                 │  SLD      │        │
│    └─────────┘                 │                 └───────────┘        │
│                                                                         │
│   DNS 쿼리 과정 (www.example.com):                                      │
│                                                                         │
│   ① 사용자 → 로컬 DNS (ISP 또는 8.8.8.8)                               │
│                    │                                                    │
│                    │ [캐시 MISS]                                        │
│                    ▼                                                    │
│   ② 로컬 DNS → Root DNS (".") → "com TLD 서버 목록 반환"               │
│                    │                                                    │
│                    ▼                                                    │
│   ③ 로컬 DNS → .com TLD → "example.com 네임서버 목록 반환"             │
│                    │                                                    │
│                    ▼                                                    │
│   ④ 로컬 DNS → example.com NS → "93.184.216.34 반환"                   │
│                    │                                                    │
│                    ▼                                                    │
│   ⑤ 로컬 DNS 캐시 저장 (TTL 동안) → 사용자에게 IP 응답                  │
│                                                                         │
│   총 소요 시간: 캐시 히트 시 1~10ms, MISS 시 50~200ms                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

DNS 레코드 타입 (필수: 표):

레코드이름설명예시
AAddress도메인 → IPv4example.com. IN A 93.184.216.34
AAAAIPv6 Address도메인 → IPv6example.com. IN AAAA 2606:2800::1
CNAMECanonical Name별칭 → 정식 도메인www.example.com. IN CNAME example.com.
MXMail Exchange메일 서버 (우선순위)example.com. IN MX 10 mail.example.com.
NSName Server권한 있는 네임서버example.com. IN NS ns1.example.com.
TXTText텍스트 정보SPF, DKIM, Google 사이트 인증
SOAStart of Authorityzone 정보시리얼, 갱신 주기, TTL
PTRPointerIP → 도메인 (역방향)34.216.184.93.in-addr.arpa.
SRVService서비스 위치_sip._tcp.example.com.

HTTP 프로토콜 구조 (필수: ASCII 아트):

┌─────────────────────────────────────────────────────────────────────────┐
│                    HTTP 요청/응답 구조                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   [HTTP 요청 (Request)]                                                 │
│   ┌─────────────────────────────────────────────────────────────────┐  │
│   │ GET /api/users HTTP/1.1          ← 요청 라인 (Method Path Ver)  │  │
│   │ Host: api.example.com            ← 요청 헤더                    │  │
│   │ User-Agent: Mozilla/5.0                                          │  │
│   │ Accept: application/json                                         │  │
│   │ Authorization: Bearer eyJhbG...                                  │  │
│   │ Content-Type: application/json                                   │  │
│   │                                            ← 빈 줄 (CRLF)        │  │
│   │ {"name": "John", "age": 30}       ← 요청 본문 (Body)             │  │
│   └─────────────────────────────────────────────────────────────────┘  │
│                                                                         │
│   [HTTP 응답 (Response)]                                                │
│   ┌─────────────────────────────────────────────────────────────────┐  │
│   │ HTTP/1.1 200 OK                   ← 상태 라인 (Ver Code Msg)    │  │
│   │ Date: Mon, 01 Jan 2025 00:00:00  ← 응답 헤더                    │  │
│   │ Server: nginx/1.24                                               │  │
│   │ Content-Type: application/json                                   │  │
│   │ Content-Length: 52                                               │  │
│   │ Cache-Control: max-age=3600                                      │  │
│   │                                            ← 빈 줄 (CRLF)        │  │
│   │ {"id":1,"name":"John","age":30}   ← 응답 본문 (Body)             │  │
│   └─────────────────────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                    HTTP 상태 코드 분류                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   1xx Informational (정보)                                              │
│     100 Continue   101 Switching Protocols                             │
│                                                                         │
│   2xx Success (성공)                                                    │
│     200 OK         201 Created         204 No Content                  │
│                                                                         │
│   3xx Redirection (리다이렉션)                                          │
│     301 Permanently  302 Found        304 Not Modified                 │
│     307 Temporary   308 Permanent                                      │
│                                                                         │
│   4xx Client Error (클라이언트 오류)                                    │
│     400 Bad Request  401 Unauthorized  403 Forbidden                   │
│     404 Not Found    405 Method       409 Conflict                     │
│     429 Too Many Requests                                               │
│                                                                         │
│   5xx Server Error (서버 오류)                                          │
│     500 Internal     502 Bad Gateway  503 Unavailable                  │
│     504 Gateway Timeout                                                 │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

TLS 1.3 핸드셰이크 (필수: ASCII 아트):

┌─────────────────────────────────────────────────────────────────────────┐
│                    TLS 1.3 핸드셰이크 (1-RTT)                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Client                                              Server            │
│     │                                                   │               │
│     │─── ClientHello ──────────────────────────────────►│               │
│     │     • Supported versions, cipher suites           │               │
│     │     • Key Share (ECDHE 공개키)                    │               │
│     │     • SNI (Server Name Indication)                │               │
│     │                                                   │               │
│     │◄── ServerHello ───────────────────────────────────│               │
│     │     • Selected version, cipher suite              │               │
│     │     • Key Share (서버 ECDHE 공개키)               │               │
│     │     • Certificate (서버 인증서)                   │               │
│     │     • CertificateVerify (서명)                    │               │
│     │     • Finished                                    │               │
│     │                                                   │               │
│     │ [양측 세션 키 계산 완료]                           │               │
│     │                                                   │               │
│     │─── Finished ─────────────────────────────────────►│               │
│     │                                                   │               │
│     │═══════════════ 암호화 통신 시작 ═══════════════════│               │
│     │                                                   │               │
│                                                                         │
│   TLS 1.3 vs TLS 1.2:                                                   │
│   • TLS 1.2: 2-RTT (ServerKeyExchange, ClientKeyExchange 추가)         │
│   • TLS 1.3: 1-RTT (Key Share로 키 교환 통합)                           │
│   • 0-RTT: 이전 세션 재사용 시 즉시 데이터 전송 가능                     │
│                                                                         │
│   제거된 알고리즘 (TLS 1.3):                                            │
│   • RSA 키 교환 (Forward Secrecy 없음)                                  │
│   • CBC 모드 암호 (BEAST, Lucky13 공격)                                 │
│   • RC4, MD5, SHA-1 (취약)                                              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

HTTP 버전 비교 (필수: 표):

특성HTTP/1.0HTTP/1.1HTTP/2HTTP/3
연결매 요청 재연결Keep-Alive멀티플렉싱QUIC 연결
헤더텍스트텍스트HPACK 압축QPACK 압축
전송순차파이프라이닝멀티플렉싱독립 스트림
서버 푸시
HOL 블로킹OOTCP 레벨해결 (UDP)
프로토콜TCPTCPTCPUDP(QUIC)
0-RTT
연결 마이그레이션

핵심 알고리즘/공식 (해당 시 필수):

[DNS TTL (Time To Live)]

TTL = 레코드가 캐시에 저장되는 시간 (초)
    = 권장값: 서비스 특성에 따라 다름

┌────────────────────────────────────────────┐
│ 레코드 타입   │ 권장 TTL      │ 이유       │
├────────────────────────────────────────────┤
│ A/AAAA       │ 300~3600초    │ 부하 분산  │
│ CNAME        │ 3600초        │ 안정적     │
│ MX           │ 3600~86400초  │ 잘 안 바뀜│
│ TXT (SPF)    │ 3600초        │ 보안 설정 │
│ NS           │ 86400초       │ 매우 안정 │
└────────────────────────────────────────────┘

[HTTP Keep-Alive]

Keep-Alive Timeout = 연결 유지 시간
Max = 연결당 최대 요청 수

예: Keep-Alive: timeout=5, max=100

[TCP 혼잡 윈도우와 HTTP]

혼잡 윈도우 = cwnd (Congestion Window)
전송 속도 = cwnd / RTT

HTTP/1.1: 요청별 TCP 연결 → slow-start 반복
HTTP/2: 단일 연결로 cwnd 효율적 사용
HTTP/3: QUIC의 독립 스트림으로 패킷 손실 영향 최소화

[HPACK 헤더 압축]

정적 테이블: 61개 자주 쓰는 헤더 (인덱스)
동적 테이블: 세션 중 학습한 헤더
허프만 코딩: 문자열 압축

압축률: 85~90% 감소 (vs HTTP/1.1 텍스트 헤더)

[QUIC 연결 ID]

Connection ID = 8~20바이트 식별자
→ IP 변경되어도 연결 유지 (모바일 WiFi↔LTE 전환)

코드 예시 (필수: Python DNS/HTTP 클라이언트):

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
from enum import Enum, auto
import struct
import socket
import time

# ============================================================
# DNS 프로토콜 구현
# ============================================================

class DNSRecordType(Enum):
    """DNS 레코드 타입"""
    A = 1
    NS = 2
    CNAME = 5
    SOA = 6
    PTR = 12
    MX = 15
    TXT = 16
    AAAA = 28


class DNSClass(Enum):
    """DNS 클래스"""
    IN = 1    # Internet
    CS = 2    # CSNET
    CH = 3    # CHAOS
    HS = 4    # Hesiod


@dataclass
class DNSHeader:
    """DNS 헤더 (12바이트)"""
    id: int = 0           # 16비트 식별자
    qr: int = 0           # 0=쿼리, 1=응답
    opcode: int = 0       # 0=표준 쿼리
    aa: int = 0           # 권한 있는 응답
    tc: int = 0           # 잘림
    rd: int = 1           # 재귀 요청
    ra: int = 0           # 재귀 가능
    z: int = 0            # 예약
    rcode: int = 0        # 응답 코드
    qdcount: int = 1      # 질문 수
    ancount: int = 0      # 응답 수
    nscount: int = 0      # 권한 수
    arcount: int = 0      # 추가 수

    def pack(self) -> bytes:
        """헤더를 바이트로 변환"""
        flags = (self.qr << 15) | (self.opcode << 11) | (self.aa << 10) | \
                (self.tc << 9) | (self.rd << 8) | (self.ra << 7) | \
                (self.z << 4) | self.rcode
        return struct.pack('!HHHHHH',
                           self.id, flags, self.qdcount,
                           self.ancount, self.nscount, self.arcount)


@dataclass
class DNSQuestion:
    """DNS 질문 섹션"""
    qname: str
    qtype: int = 1  # A 레코드
    qclass: int = 1  # IN

    def pack(self) -> bytes:
        """질문을 바이트로 변환"""
        # 도메인 이름 인코딩 (라벨 길이 + 라벨)
        labels = self.qname.rstrip('.').split('.')
        qname_bytes = b''
        for label in labels:
            qname_bytes += bytes([len(label)]) + label.encode()
        qname_bytes += b'\x00'  # 종료

        return qname_bytes + struct.pack('!HH', self.qtype, self.qclass)


@dataclass
class DNSResourceRecord:
    """DNS 리소스 레코드"""
    name: str
    rtype: int
    rclass: int
    ttl: int
    rdata: Any

    @property
    def rdata_str(self) -> str:
        if self.rtype == DNSRecordType.A.value:
            return '.'.join(str(b) for b in self.rdata)
        elif self.rtype == DNSRecordType.MX.value:
            pref, exchange = self.rdata
            return f"{pref} {exchange}"
        return str(self.rdata)


class DNSClient:
    """간단한 DNS 클라이언트"""

    DNS_PORT = 53

    def __init__(self, server: str = "8.8.8.8"):
        self.server = server
        self.timeout = 5.0

    def query(self, domain: str, qtype: DNSRecordType = DNSRecordType.A) -> List[DNSResourceRecord]:
        """DNS 쿼리 수행"""
        # 쿼리 패킷 생성
        header = DNSHeader(id=int(time.time() * 1000) % 65536)
        question = DNSQuestion(qname=domain, qtype=qtype.value)

        packet = header.pack() + question.pack()

        # UDP 소켓으로 전송
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(self.timeout)

        try:
            sock.sendto(packet, (self.server, self.DNS_PORT))
            response, _ = sock.recvfrom(4096)
            return self._parse_response(response)
        finally:
            sock.close()

    def _parse_response(self, data: bytes) -> List[DNSResourceRecord]:
        """DNS 응답 파싱"""
        # 헤더 파싱 (12바이트)
        header_data = struct.unpack('!HHHHHH', data[:12])
        ancount = header_data[3]

        # 질문 섹션 건너뛰기
        offset = 12
        while data[offset] != 0:
            offset += data[offset] + 1
        offset += 5  # 종료 바이트 + qtype + qclass

        # 응답 섹션 파싱
        records = []
        for _ in range(ancount):
            # 이름 파싱 (압축 처리)
            name, offset = self._parse_name(data, offset)

            # 타입, 클래스, TTL, RDLENGTH
            rtype, rclass, ttl, rdlength = struct.unpack('!HHIH', data[offset:offset+10])
            offset += 10

            # RDATA 파싱
            rdata = data[offset:offset+rdlength]
            offset += rdlength

            # A 레코드면 IP 주소로 변환
            if rtype == DNSRecordType.A.value:
                rdata = tuple(rdata)

            records.append(DNSResourceRecord(
                name=name,
                rtype=rtype,
                rclass=rclass,
                ttl=ttl,
                rdata=rdata
            ))

        return records

    def _parse_name(self, data: bytes, offset: int) -> Tuple[str, int]:
        """도메인 이름 파싱 (압축 포인터 처리)"""
        labels = []
        original_offset = offset
        jumped = False

        while True:
            length = data[offset]

            if length == 0:
                offset += 1
                break
            elif (length & 0xC0) == 0xC0:
                # 압축 포인터
                if not jumped:
                    original_offset = offset + 2
                pointer = struct.unpack('!H', data[offset:offset+2])[0] & 0x3FFF
                offset = pointer
                jumped = True
            else:
                offset += 1
                labels.append(data[offset:offset+length].decode())
                offset += length

        return '.'.join(labels), original_offset if jumped else offset


# ============================================================
# HTTP/1.1 클라이언트 구현
# ============================================================

class HTTPMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"
    PATCH = "PATCH"
    HEAD = "HEAD"
    OPTIONS = "OPTIONS"


@dataclass
class HTTPRequest:
    """HTTP 요청"""
    method: HTTPMethod
    path: str
    host: str
    headers: Dict[str, str] = field(default_factory=dict)
    body: str = ""

    def __post_init__(self):
        self.headers.setdefault("Host", self.host)
        self.headers.setdefault("User-Agent", "SimpleHTTPClient/1.0")
        self.headers.setdefault("Connection", "close")
        if self.body:
            self.headers.setdefault("Content-Length", str(len(self.body)))

    def pack(self) -> bytes:
        """HTTP 요청을 바이트로 변환"""
        lines = [f"{self.method.value} {self.path} HTTP/1.1"]
        for key, value in self.headers.items():
            lines.append(f"{key}: {value}")
        lines.append("")
        if self.body:
            lines.append(self.body)
        else:
            lines.append("")
        return "\r\n".join(lines).encode()


@dataclass
class HTTPResponse:
    """HTTP 응답"""
    version: str
    status_code: int
    status_text: str
    headers: Dict[str, str] = field(default_factory=dict)
    body: str = ""

    @property
    def ok(self) -> bool:
        return 200 <= self.status_code < 300


class HTTPClient:
    """간단한 HTTP 클라이언트"""

    def __init__(self, timeout: float = 10.0):
        self.timeout = timeout

    def request(self, url: str, method: HTTPMethod = HTTPMethod.GET,
                headers: Dict[str, str] = None, body: str = "") -> HTTPResponse:
        """HTTP 요청 수행"""
        # URL 파싱
        parsed = self._parse_url(url)
        host, port, path, use_https = parsed

        # TCP 연결
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(self.timeout)

        try:
            sock.connect((host, port))

            # HTTPS면 TLS 래핑 (실제로는 ssl.wrap_socket 사용)
            # 여기서는 HTTP만 구현

            # 요청 전송
            request = HTTPRequest(
                method=method,
                path=path,
                host=host,
                headers=headers or {},
                body=body
            )
            sock.sendall(request.pack())

            # 응답 수신
            response_data = b""
            while True:
                chunk = sock.recv(4096)
                if not chunk:
                    break
                response_data += chunk

            return self._parse_response(response_data)

        finally:
            sock.close()

    def _parse_url(self, url: str) -> Tuple[str, int, str, bool]:
        """URL 파싱 → (host, port, path, https)"""
        if url.startswith("https://"):
            use_https = True
            url = url[8:]
        elif url.startswith("http://"):
            use_https = False
            url = url[7:]
        else:
            use_https = False

        if "/" in url:
            host, path = url.split("/", 1)
            path = "/" + path
        else:
            host = url
            path = "/"

        if ":" in host:
            host, port_str = host.split(":")
            port = int(port_str)
        else:
            port = 443 if use_https else 80

        return host, port, path, use_https

    def _parse_response(self, data: bytes) -> HTTPResponse:
        """HTTP 응답 파싱"""
        text = data.decode('utf-8', errors='replace')
        lines = text.split('\r\n')

        # 상태 라인
        status_line = lines[0]
        parts = status_line.split(' ', 2)
        version = parts[0]
        status_code = int(parts[1])
        status_text = parts[2] if len(parts) > 2 else ""

        # 헤더
        headers = {}
        i = 1
        while i < len(lines) and lines[i]:
            key, value = lines[i].split(': ', 1)
            headers[key] = value
            i += 1

        # 본문
        body = '\r\n'.join(lines[i+1:]) if i + 1 < len(lines) else ""

        return HTTPResponse(
            version=version,
            status_code=status_code,
            status_text=status_text,
            headers=headers,
            body=body
        )

    def get(self, url: str, headers: Dict[str, str] = None) -> HTTPResponse:
        return self.request(url, HTTPMethod.GET, headers)

    def post(self, url: str, body: str = "",
             headers: Dict[str, str] = None) -> HTTPResponse:
        return self.request(url, HTTPMethod.POST, headers, body)


# ============================================================
# 쿠키/세션/JWT 관리자
# ============================================================

@dataclass
class Cookie:
    """HTTP 쿠키"""
    name: str
    value: str
    domain: str = ""
    path: str = "/"
    expires: Optional[str] = None
    max_age: Optional[int] = None
    secure: bool = False
    httponly: bool = False
    samesite: str = "Lax"  # Strict, Lax, None


class CookieJar:
    """쿠키 저장소"""

    def __init__(self):
        self.cookies: Dict[str, Cookie] = {}

    def set(self, cookie: Cookie) -> None:
        self.cookies[f"{cookie.domain}:{cookie.name}"] = cookie

    def get(self, domain: str, name: str) -> Optional[Cookie]:
        return self.cookies.get(f"{domain}:{name}")

    def get_for_request(self, domain: str, path: str = "/") -> str:
        """요청에 포함할 쿠키 헤더 생성"""
        valid_cookies = []
        for key, cookie in self.cookies.items():
            if domain in cookie.domain and path.startswith(cookie.path):
                valid_cookies.append(f"{cookie.name}={cookie.value}")
        return "; ".join(valid_cookies)


@dataclass
class JWTToken:
    """JWT 토큰 (시뮬레이션)"""
    header: Dict[str, str]
    payload: Dict[str, Any]
    signature: str = ""

    @property
    def is_expired(self) -> bool:
        if "exp" not in self.payload:
            return False
        return time.time() > self.payload["exp"]


# ============================================================
# DNS 캐시 시뮬레이터
# ============================================================

@dataclass
class DNSCacheEntry:
    """DNS 캐시 엔트리"""
    domain: str
    ip: str
    ttl: int
    cached_at: float

    @property
    def is_expired(self) -> bool:
        return time.time() > self.cached_at + self.ttl


class DNSCache:
    """DNS 캐시"""

    def __init__(self):
        self.cache: Dict[str, DNSCacheEntry] = {}
        self.hits = 0
        self.misses = 0

    def get(self, domain: str) -> Optional[str]:
        """캐시에서 IP 조회"""
        if domain in self.cache:
            entry = self.cache[domain]
            if not entry.is_expired:
                self.hits += 1
                return entry.ip
            else:
                del self.cache[domain]
        self.misses += 1
        return None

    def set(self, domain: str, ip: str, ttl: int = 300) -> None:
        """캐시에 저장"""
        self.cache[domain] = DNSCacheEntry(
            domain=domain,
            ip=ip,
            ttl=ttl,
            cached_at=time.time()
        )

    @property
    def hit_rate(self) -> float:
        total = self.hits + self.misses
        if total == 0:
            return 0.0
        return self.hits / total * 100

    def print_stats(self) -> None:
        print(f"\nDNS 캐시 통계:")
        print(f"  히트: {self.hits}, 미스: {self.misses}")
        print(f"  히트율: {self.hit_rate:.1f}%")
        print(f"  캐시된 항목: {len(self.cache)}개")


# ============================================================
# 사용 예시
# ============================================================

if __name__ == "__main__":
    print("=" * 60)
    print("         DNS & HTTP 프로토콜 데모")
    print("=" * 60)

    # 1. DNS 쿼리 테스트
    print("\n1. DNS 쿼리")
    print("-" * 40)

    dns_client = DNSClient("8.8.8.8")

    try:
        records = dns_client.query("google.com", DNSRecordType.A)
        print(f"google.com A 레코드:")
        for record in records:
            print(f"  → {record.rdata_str} (TTL: {record.ttl}s)")
    except Exception as e:
        print(f"  DNS 쿼리 실패: {e}")

    # 2. DNS 캐시 테스트
    print("\n2. DNS 캐시 시뮬레이션")
    print("-" * 40)

    cache = DNSCache()

    # 첫 번째 조회 (미스)
    ip = cache.get("example.com")
    if ip is None:
        print("  캐시 미스 → DNS 쿼리 수행")
        cache.set("example.com", "93.184.216.34", ttl=300)

    # 두 번째 조회 (히트)
    ip = cache.get("example.com")
    if ip:
        print(f"  캐시 히트 → {ip}")

    cache.print_stats()

    # 3. HTTP 요청 생성 테스트
    print("\n3. HTTP 요청 메시지 생성")
    print("-" * 40)

    request = HTTPRequest(
        method=HTTPMethod.GET,
        path="/api/users",
        host="api.example.com",
        headers={"Accept": "application/json", "Authorization": "Bearer token123"}
    )
    print(request.pack().decode())

    # 4. HTTP 응답 파싱 테스트
    print("\n4. HTTP 응답 파싱")
    print("-" * 40)

    sample_response = b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 27\r\n\r\n{\"status\":\"success\",\"data\":[]}"

    client = HTTPClient()
    response = client._parse_response(sample_response)
    print(f"상태 코드: {response.status_code} {response.status_text}")
    print(f"헤더: {response.headers}")
    print(f"본문: {response.body}")

    # 5. 쿠키 관리
    print("\n5. 쿠키 관리")
    print("-" * 40)

    jar = CookieJar()
    jar.set(Cookie(name="session_id", value="abc123", domain="example.com"))
    jar.set(Cookie(name="user_pref", value="dark_mode", domain="example.com"))

    cookie_header = jar.get_for_request("example.com", "/")
    print(f"요청 쿠키 헤더: {cookie_header}")