meta charset
고친 사람 github-actions[bot]
meta charset 은 웹 페이지가 자기 글자를 어떤 규칙으로 적었는지 스스로 밝힙니다. 페이지 맨 앞쪽에 한 줄로 적어 둡니다. 브라우저는 이 줄에 적힌 규칙으로 나머지 바이트를 글자로 되돌립니다. 서버 설정을 못 건드려도 파일만 고쳐서 한글 깨짐을 막을 수 있습니다.
쉽고 빠른 이해
웹 페이지 파일 안에 붙이는 인코딩 이름표입니다. 한글 페이지라면 머리에
<meta charset="utf-8"> 한 줄을 넣습니다. 브라우저는 이 줄을 보고 한글을 제대로 보여 줍니다.
같은 글자도 규칙에 따라 다른 바이트가 됩니다. 이름표가 없으면 브라우저가 규칙을 짐작해야 합니다. 짐작이 빗나가면 한글이 알아볼 수 없는 기호로 깨집니다.
- 브라우저는 먼저 서버가 보낸 헤더에서 인코딩 이름을 찾습니다
- 헤더에 없으면 파일 앞부분을 미리 훑어 이름표를 찾습니다
- 찾으면 그 규칙으로 파일을 처음부터 읽습니다
대가는 meta charset 이 늘 첫째가 아니라는 것입니다. 헤더에 다른 이름이 적혀 있으면 무시됩니다. 파일만 들여다봐서는 깨진 원인을 못 찾게 됩니다.
이름표는 웹 페이지에만 씁니다. 서버가 데이터만 돌려주는 응답에는 넣을 곳이 없습니다.
상세
이 절은 먼저 글자가 바이트가 되는 규칙이 왜 여럿인지 봅니다. 그다음 그 규칙을 페이지 안에 적는 한 줄을 뜯어봅니다. 끝으로 브라우저가 이 줄을 언제 믿고 언제 무시하는지를 봅니다.
글자를 바이트로 적는 규칙
컴퓨터는 글자를 바이트로 바꿔서 담습니다. 어떤 글자를 어떤 바이트로 바꿀지 정한 규칙을 문자 인코딩, 줄여서 인코딩이라고 합니다. 이 규칙은 하나가 아니라 여럿입니다.
같은 「가」 한 글자를 두 규칙으로 바꿔 보면 바이트가 갈립니다. 파이썬에서 문자열을 바이트로 바꾸는 호출입니다.
"가".encode("utf-8") # b'\xea\xb0\x80'
"가".encode("euc-kr") # b'\xb0\xa1'
UTF-8(Unicode Transformation Format 8-bit, 유니코드 변환 형식 8비트)은 「가」를 세 바이트로 적습니다. EUC-KR(Extended Unix Code for Korean, 한국어 확장 유닉스 코드)은 두 바이트로 적습니다. 바이트 길이부터 다릅니다.
그래서 바이트만 받아서는 어느 규칙으로 읽어야 할지 모릅니다. 틀린 규칙으로 읽으면 글자가 엉뚱한 기호로 보입니다. 이 고장을 모지바케라고 부릅니다. 받는 쪽에 규칙의 이름을 건네야 이 고장이 안 생깁니다.
페이지 안에 적는 한 줄
웹 페이지는 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)로 적습니다. HTML 은
<head> 같은 태그로 문서를 나눕니다. <head> 안에는 화면에 안 보이는, 페이지에 관한 정보가
들어갑니다.
그 정보 가운데 하나를 적는 태그가 meta 입니다. meta 에 charset 속성을 붙이면
그 값이 이 페이지의 인코딩 이름이 됩니다. 이 한 줄이 meta charset 입니다. 이 한 줄을
인코딩 선언이라고도 부릅니다.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>주문 내역</title>
</head>
<body>
<p>안녕하세요</p>
</body>
</html>
넷째 줄이 meta charset 입니다. 이 페이지의 바이트를 UTF-8 규칙으로 적었다는 뜻입니다. 브라우저는 이 이름을 보고 아래의 「주문 내역」과 「안녕하세요」를 UTF-8 로 읽습니다.
값은 대소문자를 가리지 않습니다. utf-8 과 UTF-8 은 같은 뜻입니다.
이름과 달리 인코딩을 가리키는 값
charset 은 character set 의 줄임입니다. 우리말로는 문자 집합입니다. 문자 집합은 어떤 글자를 다룰지 모아 번호를 매긴 목록입니다. 그 번호를 어떤 바이트로 적을지는 정하지 않습니다.
유니코드가 이런 목록의 하나입니다. 세계 여러 나라의 글자를 한 목록에 모아 번호를 매겼습니다. UTF-8 은 유니코드 번호를 바이트로 적는 규칙의 하나입니다.
이 속성에 들어가는 것은 목록 이름이 아니라 인코딩 이름입니다. charset="utf-8" 은
「유니코드 글자를 쓴다」가 아니라 「UTF-8 규칙으로 바이트를 적었다」를 알립니다. 브라우저가
글자를 되돌리려면 목록이 아니라 바이트로 적는 규칙을 알아야 하기 때문입니다.
앞 1024바이트 안에 두는 까닭
meta charset 에는 닭과 달걀 문제가 있습니다. 인코딩을 알아야 글자를 읽습니다. 그런데 인코딩 이름이 그 글자 속에 적혀 있습니다. 브라우저는 규칙을 모르는 채로 이름표부터 찾아야 합니다.
이 문제가 풀리는 까닭은 영문자에 있습니다. UTF-8 과 EUC-KR 은 영문자·숫자·기호를 ASCII(American Standard Code for Information Interchange, 미국 정보 교환 표준 부호)와 같은 한 바이트로 적습니다.
<meta charset="utf-8"> 이라는 영문 줄은 UTF-8 로 적어도 EUC-KR 로 적어도 같은 바이트가 됩니다.
그래서 어느 쪽으로 읽든 같은 글자로 읽힙니다. 한글은 규칙마다 달라도 이 줄만은 먼저 읽힙니다.
브라우저는 본격적으로 읽기 전에 파일 앞부분만 미리 훑어 이 줄을 찾습니다. 그래서 meta charset 은 문서의 앞 1024바이트 안에 반드시 통째로 들어가야 합니다(표준의 must — 어기면 표준을 안 지킨 것이 되는 요구).
그보다 뒤에 두면 미리 훑기에서 못 찾습니다. 브라우저는 다른 단서나 짐작으로 읽기 시작합니다.
실무에서는 <head> 를 열자마자 첫 줄에 둡니다. <title> 에 한글이 들어가는 일이 많습니다. 그
한글보다 이름표가 먼저 나와야 합니다.
헤더와 부딪칠 때의 순서
인코딩을 알리는 통로는 meta charset 하나가 아닙니다. 서버는 응답의 헤더에도 인코딩 이름을
적을 수 있습니다. HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 응답의
Content-Type: text/html; charset=utf-8 에서 끝의 charset 이 그것입니다. 이 값을
Content-Type charset 이라고 부릅니다.
통로가 하나 더 있습니다. 파일 맨 앞에 붙는 고정된 몇 바이트입니다. 이것을 바이트 순서 표시라고 부릅니다. 원래는 바이트를 어느 순서로 읽을지 알리는 표시입니다. 그 모양이 인코딩마다 달라서 인코딩 표시로도 쓰입니다.
세 통로가 서로 다른 이름을 말하면 브라우저는 정해진 순서로 하나를 고릅니다.
flowchart TD
A["HTML 파일이 도착한다"] --> B{"맨 앞에 바이트 순서 표시가 있나"}
B -->|있다| C["그 표시대로 읽는다"]
B -->|없다| D{"헤더의 Content-Type 에 charset 이 있나"}
D -->|있다| E["헤더의 charset 대로 읽는다"]
D -->|없다| F{"앞 1024바이트 안에 meta charset 이 있나"}
F -->|있다| G["meta charset 대로 읽는다"]
F -->|없다| H["브라우저가 짐작한다"]
meta charset 은 세 번째입니다. 바이트 순서 표시도 없고 헤더에도 이름이 없을 때만 쓰입니다.
이 순서 때문에 헤더와 파일이 어긋나면 헤더가 이깁니다. 서버가 charset=euc-kr 을 붙여 보내면
파일에 <meta charset="utf-8"> 을 적어도 브라우저는 EUC-KR 로 읽습니다. 깨진 페이지를 고칠 때는
파일보다 서버 응답의 헤더를 먼저 봐야 합니다.
http-equiv 로 적는 옛 표기
오래된 페이지에서는 같은 일을 하는 긴 표기를 자주 만납니다.
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr">
http-equiv 는 「HTTP 헤더와 같은 것」이라는 뜻입니다. 헤더에 적을 줄을 파일 안에 흉내 내어
적는 표기입니다. 브라우저는 이 줄도 meta charset 과 똑같이 다룹니다. 순서도 헤더 다음입니다.
두 표기 가운데 하나만 씁니다. 한 문서에 인코딩을 밝히는 meta 줄은 하나까지만 둘 수 있습니다.
적어야 하는 값과 그 까닭
지금 새로 만드는 HTML 문서는 반드시 UTF-8 로 적어야 합니다(must). 이름을 밝힌다면 값도 utf-8 이어야
합니다. EUC-KR 로 적힌 옛 페이지도 브라우저가 읽어 주기는 합니다. 새로 쓰는 페이지에 고를 값은
아닙니다.
UTF-8 을 고르는 까닭은 둘입니다. 첫째, 유니코드의 모든 글자를 한 규칙으로 담습니다. EUC-KR 은 한국어를 위해 만든 규칙이라 담지 못하는 글자가 많습니다. 둘째, 서버와 주고받는 데이터도 대개 UTF-8 로 적습니다. 페이지도 UTF-8 이면 두 쪽의 규칙이 처음부터 같습니다.
페이지가 영문으로만 이루어져도 이름은 밝힙니다. 사용자가 폼에 입력한 한글이 이 인코딩을 따라 바이트가 되기 때문입니다. EUC-KR 페이지의 폼은 입력값을 EUC-KR 바이트로 서버에 보냅니다.
서버 쪽에서는 이 차이가 곧 문제가 됩니다. 서버는 입력값을 UTF-8 로 읽는다고 해 봅시다. 페이지가 EUC-KR 이면 저장된 이름·주소의 한글이 깨집니다. 페이지의 이름표와 서버가 읽는 규칙을 한 가지로 맞춰야 합니다.
이 줄이 효과를 내지 않는 응답
meta charset 은 HTML 문서 안에서만 뜻이 있습니다. JSON(JavaScript Object Notation, 자바스크립트 객체 표기법)은 데이터만 담는 형식입니다. JSON 으로 답하는 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스) 응답에는 이 줄을 넣을 곳이 없습니다. JSON 은 UTF-8 로 적도록 정해져 있습니다. 그래서 알릴 필요도 없습니다.
XHTML(Extensible HyperText Markup Language, 확장 가능한 하이퍼텍스트 마크업 언어)은 HTML 을
XML(Extensible Markup Language, 확장 가능한 마크업 언어) 문법으로 적은 것입니다. XHTML 문서에서도
이 줄은 아무 효과가 없습니다. XML 문서는 맨 위의 XML 선언으로 인코딩을 밝힙니다.
<?xml version="1.0" encoding="utf-8"?> 가 그 줄입니다.
관련 항목
이 선언이 들어가는 문서와 요소
HTML · head 요소 · meta 요소 · HTML 속성 · doctype · title 요소
같은 인코딩을 다른 통로로 알리는 표시
Content-Type charset · Content-Type · 바이트 순서 표시 · XML 선언 · http-equiv · 인코딩 선언
이 선언에 적히는 인코딩 이름
UTF-8 · EUC-KR · CP949 · UTF-16 · ISO-8859-1 · Shift_JIS · US-ASCII
이 선언이 이름을 대는 글자 규칙의 바탕 개념
문자 인코딩 · 문자 집합 · 유니코드 · 코드 포인트 · 바이트 · ASCII
이 선언이 빠지거나 틀릴 때 터지는 문제
모지바케 · 인코딩 스니핑 · 대체 문자 · 퍼센트 인코딩 · 폼 인코딩
이 선언을 읽어 글자로 되돌리는 프로그램
이 선언이 효과를 내지 않는 문서 형식
이 선언의 이름과 순서를 정하는 표준 문서
HTML Living Standard · WHATWG Encoding Standard · IANA 문자 집합 레지스트리
다른 이름: meta charset 태그 · charset 속성 · meta 요소의 charset