프로젝트/단기 프로젝트

[커리어리 디스코드 봇] RSS XML에서 유효하지 않은 문자 제거 - Invalid bytes in character encoding

Chipmunks 2024. 5. 4. 10:29
728x90

 

안녕하세요.

최근 디스코드 봇 용으로 커리어리 RSS 피드를 조회해 디스코드 웹훅으로 보내주는 스크립트를 만들었습니다.

 

커리어리 디스코드 봇 제작기

안녕하세요, 다람쥐입니다. 최근에 병원에 입원을 했는데요. 예상 외로 회복이 빨라서 바로 손이 근질거리더군요. 새벽에 일찍 일어나서 만들만 만한 게 없을까, 고민하던 차에 자주 이용하는

itchipmunk.tistory.com

 

트러블 슈팅 5번으로 RSS 피드를 불러올 때 XML에 아래처럼 오류가 나는데요.

Invalid bytes in character encoding 오류로 유효하지 않은 문자가 들어가 읽을 수 없다는 오류였습니다.

아주 가끔~ 해당 인코딩이 잘못 들어갔겠거니 싶어서 임시로 replace 를 해줬습니다.

 

이전 트러블슈팅
신규 발생한 Invalid bytes in character encoding 오류

 

그 이후로도 간간히 RSS 피드를 읽을 수 없어 Invalid bytes in character encoding 오류가 발생하더라고요.

보름 정도 하루에 한 번 스크립트를 돌렸는데 3~4번 정도 오류가 발생했습니다.

이 RSS 피드를 읽어 슬랙으로 보내는 공식 봇도 똑같이 오류가 발생할텐데... 어떻게 처리했는지 궁금하네요.

RSS 피드를 발행할 때 XML 규격에 허용되지 않는 문자열은 제외되면 좋을 것 같네요..!

디스코드 봇에서 XML에 유효하지 않는 문자를 지웠습니다.

 

XML에서 유효한 문자 기준

XML에 유효한 문자는 버전별로 상이합니다.

유니코드 기준이며, 해당 범위로 유효한지 검사합니다.

우선 커리어리는 XML 1.0 버전입니다.

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>커리어리 | 요즘 개발자 커뮤니티 </title>

 

 

XML 1.0 에서 유효한 문자 범위

  • U+0009 : 수평 탭
  • U+000A : 줄 바꿈
  • U+000D : 캐리지 리턴
  • U+0020 ~ U+D7FF : 유니코드 문자
  • U+E000 ~ U+FFFD : 유니코드 문자
  • U+10000 ~ U+10FFFF : 유니코드 보조 문자

 

XML 1.1 에서 유효한 문자 범위

  • U+0001 ~ U+D7FF
  • U+E000 ~ U+FFFD
  • U+10000 ~ U+10FFFF

 

XML 1.1 에서 유효하다고 해도, 여전히 XML 1.0 에서 쓰이지 않는 범위는 권장하지 않습니다.

출처 : https://en.wikipedia.org/wiki/Valid_characters_in_XML

 

유효하지 않은 범위 정규식 제거

kotlin String 확장 함수로 sanitizeInvalidUnicode 를 만듭니다.

정규식은 String 의 toRegex() 메소드를 사용해 만들 수 있고, Regex 클래스로 만들 수 있습니다.

아래처럼 정규식이 길므로 코드 가독성 측면에서 Regex 클래스로 생성했습니다.

너무 길면 뒤의 toRegex() 가 눈에 안들어 올 수 있죠.

fun String.sanitizeInvalidXMLUnicode(): String {
    val SANITIZE_REGEX = Regex("[^\\x09\\x0A\\x0D\\x20-\\uD7FF\\uE000-\\uFFFD\\u10000-\\u10FFFF]")
    return this.replace(SANITIZE_REGEX, "");
}

 

정규식에서 '[]' 는 문자 하나에 해당하는 조건이 여러개일 때 한 번에 묶어줍니다.

'^' 은 부정을 나타내며, 뒤 범위가 아닌 문자를 찾는 조건으로 바뀝니다.

 

Regex 객체를 생성하는 데 비용이 비싸 전역으로 만들까 하다

한 번만 실행되고 말 스크립트고, 접근 범위로 관리하기 편하게 해당 메소드안에서 Regex 클래스를 생성했습니다.

 

fun main() = runBlocking {
    val rssParser = RssParser()
    val rss = REQUEST_URL.httpGet().body.sanitizeInvalidXMLUnicode()
    val rssChannel = rssParser.parse(rss)
    
    ...
}

 

위처럼 RSS 텍스트를 읽을 때 유효하지 않은 범위를 제거했습니다.

 

유효하지 않은 문자열을 제거한 후 테스트 결과

여전히 RSS 피드에 들어가면, Invalid bytes in character encoding, 오류가 뜨지만

아래처럼 RSS Reader 가 정상적으로 동작하는 걸 확인했습니다.

 

정상 동작

 

XML 1.0 스펙을 살펴봐서 유효한 범위가 어딘지 꼼꼼히 살펴봤습니다.

이정도는 RSS 라이브러리나 XML 라이브러리에서 자체적으로 해줄 법도 한데...

XML 스펙이 언제 변할지 몰라서 호환성 이슈가 생길 수 있어 안했을 거란 생각도 드네요.

 

다음에 다른 기능이나 이슈가 생기면 찾아뵙도록 하겠습니다!