Go에서 EUC-KR → UTF-8 변환 시 대용량 데이터 잘림 버그 수정

PowerBase(금융 거래 서버)는 한글 응답을 EUC-KR로 내려줘요. Go는 기본적으로 UTF-8만 다루기 때문에 golang.org/x/text/encoding/korean 패키지로 변환하는 코드가 있었는데, 대용량 응답에서 한글이 잘리는 버그가 있었어요.

문제 코드

1
2
3
4
5
6
7
8
9
10
11
12
func EucKrToUtf8(data []byte) string {
reader := transform.NewReader(
&byteReader{data: data},
korean.EUCKR.NewDecoder(),
)
result := make([]byte, len(data)*3) // UTF-8은 최대 3배 크기
n, err := reader.Read(result)
if err != nil {
return string(data)
}
return string(result[:n])
}

reader.Read(result)는 한 번 호출로 전체 데이터를 읽는다는 보장이 없어요. io.Reader 인터페이스 특성상 내부 버퍼 크기나 디코더 상태에 따라 일부만 읽고 반환할 수 있거든요. 그 결과 대용량 응답에서 뒷부분이 조용히 잘렸어요.

커스텀 byteReader도 만들 필요가 없었어요.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 불필요했던 byteReader 구현
type byteReader struct {
data []byte
pos int
}
func (r *byteReader) Read(p []byte) (n int, err error) {
if r.pos >= len(r.data) {
return 0, fmt.Errorf("EOF")
}
n = copy(p, r.data[r.pos:])
r.pos += n
return n, nil
}

수정 코드

transform.Bytes는 내부적으로 전체 입력을 완전히 변환할 때까지 반복 처리해줘요. 버퍼 크기 걱정도 없고, 커스텀 Reader도 필요 없어요.

1
2
3
4
5
6
7
func EucKrToUtf8(data []byte) string {
result, _, err := transform.Bytes(korean.EUCKR.NewDecoder(), data)
if err != nil {
return string(data)
}
return string(result)
}

30줄짜리 코드가 5줄로 줄었고, 잘림 버그도 사라졌어요.

왜 잘렸나

io.Reader.Read()는 스펙상 len(p) 바이트보다 적게 읽어도 정상이에요. 전체를 읽으려면 io.ReadAll() 같은 루프를 써야 해요. 반면 transform.Bytes()는 전체 변환을 보장하는 헬퍼 함수라 이런 문제가 없어요.

레거시 금융 시스템에서 EUC-KR 전문을 다룰 일이 있다면 transform.Bytes를 쓰는 걸 권장해요.