ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Github 리드미에 달력 표시하기 - 2탄
    Back-End/Python 2023. 5. 30. 01:59

    Github 리드미에 달력 표시하기 1탄에 이어 2탄을 작성해보려고 합니다. 지난번 1탄에서는 프로젝트 전반적인 소개와 설계 및 구현 등에 대해 살펴보았다면 이번에는 라이브러리 사용 및 운영 환경에서 발생했던 이슈들과 고민 해결 등에 대해서 회고해보려고 합니다.

    Github 리드미에 달력 표시하기

     


     

    🦄 Notion API 사용하기

     처음 이 프로젝트를 계획한 것은 작년 초쯤이었는데 그 당시에는 notion-py라는 모듈을 사용했습니다. 그러나 이 모듈의 단점은 비공식 Notion API를 지원한다는 점입니다. 비공식 Notion API란 21년 5월쯤 공식 Notion API가 세상에 공개되기 전에 사람들이 브라우저의 개발자도구 네트워크 탭을 이용해서 API 정보를 해킹해서 만들어 놓은 API입니다.

     

     비공식 API들로 구성되서 그런지 notion-py 모듈은 생각보다 불안정하였습니다. 이슈가 많은데도 불구하고 최종 업데이트가 2년 전이어서 더 이상 유지보수를 지원하지 않는 모듈인 것 같아 어쩔 수 없이 사용하는 것을 포기하였습니다.(아마도 공식 API가 나오면서 사용 권장을 안하는것 같습니다.) 저 같은 경우에는 데이터베이스를 불러오는 과정에서 버그가 발생하였습니다.

     

      현재는 Notion에서 제공하는 공식 API를 사용하는 것으로 코드를 변경하였습니다. 노션 캘린더의 오늘 날짜에 해당하는 월에 대한 페이지 데이터들을 가져올 예정이었으므로 Query a database라는 API를 사용해서 간단하게 데이터들을 불러왔습니다.

     

    url = f"https://api.notion.com/v1/databases/{os.getenv('db')}/query"
    headers = {
        "Authorization": f"Bearer secret_{os.getenv('auth')}",
        "Accept": "application/json",
        "Notion-Version": "2022-02-22",
    }
    
    today = date.today()
    params = {
        "filter": {
            "property": "Date",
            "date": {
                "after": f"{today.year}-{today.month:02}-01"
            }
        }
    }
    
    response = requests.post(url, headers=headers, json=params)

     


     

    🗓️ 달력 UI 정하기 : html vs 이미지

     notion API 사용시 달력까지 만들어서 전달해 주면 좋겠지만 아쉽게도 해당 영역은 프런트의 영역입니다. 그렇다면 제가 할 일은 이제 notion에서 불러온 데이터들을 뿌려줄 UI에 대해서 고민해 보아야 합니다.

     

     먼저 컨텐츠의 미디어 타입을 정해야 합니다. html로 뿌려줄 것인가 이미지로 보여줄 것인가? 등등 선택을 해야 하는데 저는 그냥 간단하게 html 또는 마크다운으로 구현하려고 했습니다. github action을 활용해 cron을 사용하여 스케줄을 만들어 놓으면 자동으로 설정한 시간에 README.md 마크다운 파일을 커밋&푸시하여 갱신할 수 있겠다는 생각을 하였습니다.

     

     그러나 <Github 리드미에 달력 표시하기 - 1탄>에서도 설명했듯이 마크다운이나 html사용 시 글자의 길이나 이미지 크기 등으로 인해 grid 디자인이 깨진다는 점은 정말 치명적인 단점이었습니다. 그래서 svg를 사용하여 동적으로 변하는 이미지를 직접 만들어서 응답해 줘야겠다는 생각을 하였습니다.

     


     

    ⌛ 서버 응답 시점 정하기 : 스케줄 vs 실시간

    만들어놓은 svg를 응답해줄 서버는 어떻게 구현할까? 고민한 결과 제가 생각했던 방법은 다음과 같이 두 가지입니다.

     

    1. github action을 사용해서 cron을 통해 UI를 주기적으로 갱신한다.
    2. 새로고침 할 때마다 실시간으로 UI를 갱신한다.

     

     첫 번째 방법은 github action을 사용해서 주기적으로 UI를 갱신하는 방법입니다. README 마크다운 파일 안에 svg 코드를 삽입하면 될 것 같다는 생각을 하였습니다.

     

     하지만 저는 이때 한 가지 착각을 하고 있었습니다. 마크다운 안에 svg코드를 넣으면 이미지가 나올 것이라는 드라마틱한 상상을 했던 것입니다. 기존에 마크다운 안에 html을 삽입했을 때 렌더링이 가능했던 점 때문에 svg도 마찬가지일 거라는 막연한 생각을 했던 것입니다.

     

     결과적으로는 마크다운에서 svg코드 사용이 불가능했습니다. 결국 서버에서 직접 이미지를 만들어서 응답해 주는 방법 밖에 없겠다는 생각이 들어서 저는 두 번째 방법을 채택하였습니다. 다행히 github에서는 마크다운 이미지 또는 html의 <img> 태그를 이용하여 외부 SVG 파일을 이미지로 삽입할 수 있는 기능을 지원하고 있었습니다.

     

     하지만 이렇게 구현하는 경우 주의할 점은 만약에 누군가 악의적으로 제 svg 링크를 퍼가서 지속적인 트래픽을 보낸다면 제 통장이 거덜 날 수도 있습니다. 따라서 이 문제는 특정 시간에 트래픽이 많이 일어난다면 호스팅 사이트에서 차단하고 알람이 오도록 설정하거나 캐싱을 사용해야 합니다.

     


     

    🎯 버그 발견 : 실시간으로 이미지가 변경되지 않는다?

     프로젝트 개발을 완료한 뒤 서버에 띄우고 나서 문제점이 생겼습니다. 분명 localhost에서는 실시간으로 잘 동작했는데 운영 환경(github README.md)에서는 처음에는 잘 나오고 그 뒤로는 아무리 새로고침을 해도 계속 예전 이미지로 나오는 현상이 나타났습니다.

     

    이것저것 자료를 찾아보니 github 이미지에 올려놓으면 자동으로 캐시가 걸린다는 얘기를 발견할 수 있었습니다. 그래서 URL뒤에 아무 수식이나 쿼리를 붙여서 다시 테스트해 보니 정상적으로 새 이미지로 변경되어 잘 나오는 것을 확인했습니다. 그리고 그 상태에서 새로고침을 하면 또다시 변경이 안 되는 것을 발견하고 저는 캐시 문제라는 것을 확신했습니다.

     

    좀 더 찾아보니 깃허브에서는 이미지를 보호하고 캐싱하기 위해 이미지를 업로드하거나 클릭하면 camo.githubusercontent.com 서버를 통해 제공되는데 이곳에서 default 캐시 유효 기간을 1년으로 지정해 놓고 있었습니다. 따라서 캐시 유효기간을 다음과 같이 설정하여 문제를 피할 수 있었습니다.

     

    headers={"Cache-Control": "max-age=0"}
    

     

    다만, 주의할 점은 이전에 쓰던 URL은 이미 1년 캐시가 적용되어 버려서 다른 URL을 사용하던가 브라우저에서 캐시를 제거해줘야 합니다.

     

    참고자료 ▶ https://coding-groot.tistory.com/42

     


     

    🎯 버그 발견 : SVG 안에 이미지 삽입시 안 나온다.

     notion에서는 페이지 아이콘에 이미지를 업로드해서 사용할 수 있는데 github에서 확인해 보면 다음과 같이 이미지가 깨져서 나타납니다. 이 점 역시 localhost에서는 정상적으로 작동하는데 왜 운영환경 (github README.md)에서만 이미지가 깨져서 나오는 건지 이해가 안 됐습니다.

     

    svg안에 이미지 있는 경우 github CSP 위반

     

    개발자도구를 열어서 확인해 보니 Content Security Policy(CSP)를 위반했다는 내용이 나옵니다.

    CSP가 무엇일까요?

     

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

     

    CSP는 웹 브라우저에서 사용하는 콘텐츠 기반의 보안 정책으로써 크로스 사이트 스크립팅 공격(XSS)을 보호하기 위한 정책이라고 나와있습니다.

     

    CSP는 웹사이트에 직접 룰을 적용해서 사용하게 되어있는데 Github에서는 notion2svg 서버를 거쳐서 노션 AWS서버의 이미지를 가져오는 것을 허용하지 않도록 서버에서 막아 놓는 정책을 적용해 놓은 것 같습니다.

     

    cache와 달리 github에서는 CSP(content-security-policy)에 대해서 변경 불가능하게 되어있으므로 아쉽지만 svg안에 이미지를 나타내는 것은 포기하였습니다.

     

    참고자료 1 ▶https://github.blog/2016-04-12-githubs-csp-journey/

    참고자료 2 ▶ https://stackoverflow.com/questions/65941892/github-readme-images-broken-violates-the-following-content-security-policy-di

     


     

    🎯 버그 발견 : 12시가 지났는데 오늘 날짜가 변경되지 않는다?

     이상한 점을 발견했습니다. 분명 이제 페이지는 실시간으로 변경돼서 잘 나타나는데 오늘 날짜를 체크해 주는 디자인이 변하지 않고 어제 날짜에 머물러 있는 현상이 발견되었습니다. 이번에도 역시 localhost에서는 잘 동작했는데 운영환경에서는 동작하지 않았습니다. 😤

     

    오늘 날짜에는 빨간색 동그라미 디자인이 나타난다.

     

    그래도 이 버그는 다른 버그들에 비해 조금 재밌는 발견이었습니다. 2가지 정도 이슈가 있었는데 아래와 같이 처리할 수 있었습니다.

     

    첫 번째 문제 : 현재 날짜가 아닌 인스턴스 생성 날짜를 저장

    Calendar 클래스를 만들어서 오늘 날짜들을 저장해 놓고 가져다 사용하였습니다. 변수들이 계속 메모리를 할당해 가는 것을 방지하기 위해 일부러 클래스 변수로 빼놓았는데 날짜 데이터는 실시간으로 변해야 하는 데이터임을 간과하고 있었습니다.

     

    class Calendar(ABC):
    
        def __init__(self):
            self.now = datetime.now()
            self.date = self.now.strftime("%Y %b %m %X").split(" ")
            self.year, self.str_month, self.month, self.time = self.date
            self.today = self.now.date().isoformat()
            self.weeks = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    
        @abstractmethod
        def get_calendar(self):
            pass

     

    따라서 다음과 같이 weeks처럼 고정 데이터 빼고는 전부 함수에서 날짜 라이브러리를 호출하도록 변경하여 문제를 해결할 수 있었습니다. 콘솔에 값을 찍어보니 정상적으로 오늘 날짜가 나타나는 것을 확인할 수 있었습니다.

     

    class Calendar(ABC):
    
        def __init__(self):
            self.weeks = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    
        @abstractmethod
        def get_calendar(self):
            pass

     

     

    두 번째 문제 : Regions

     이번에는 다른 문제가 있었습니다. 이제는 날짜는 분명 변하는데 왜 운영 환경에서는 또 하루씩 밀리는 걸까요? 🤯 그래서 확인해 보니 문뜩 새로 바꾼 서버 환경 시간이 다를 수도 있겠다고 생각했습니다. 그래서 아니나 다를까 운영 서버의 콘솔 로그를 확인해 보니 시간이 다르게 찍히고 있었습니다.

     

     Koyeb이라는 클라우드를 사용 중인데 가격 측면에서 비교적 이점을 얻을 수 있는 서버를 임대하다 보니까 Region 선택의 폭이 좁았습니다. 독일과 미국은 한국과 시차가 조금 있는 편이죠. 그래서 timezone을 직접 맞춰줘야 합니다.

     

    Koyeb의 nano 서버는 미국, 독일 중 Region을 선택해야 한다.

     

     가장 좋은 방법은 os단에서 timezone을 맞춰주는 것이 맞지만 Koyeb의 SaaS배포용 클라우드는 OS 단 작업을 허용하지 않고 있었습니다. 따라서 다음과 같이 pytz라는 모듈을 설치하고 timezone을 서울로 맞췄습니다.

     

    class Calendar(ABC):
    
        def __init__(self):
            self.weeks = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
            self.tz = pytz.timezone('Asia/Seoul')
    
        @abstractmethod
        def get_calendar(self):
            pass
    
    class NotionCalendar(Calendar):
        def __init__(self):
            super().__init__()
    
        def get_calendar(self):
            now = datetime.now(tz=self.tz)   # 서울의 현재 날짜를 불러옴

     


     

    ⏰ 운영 : 서버 응답 시간문제

    2022년 11월 28일 자로, Heroku는 사용자들에게 무료체험 기간 제공을 중단했습니다. 그래서 저도 플랫폼을 옮길 수밖에 없었는데요. 마침 헤로꾸로 notion2svg를 배포하면서 몇 가지 문제점이 있어서 피차 운영서버를 바꾸려고 했었습니다.

     

     

    Heroku’s Next Chapter

    Back in May, I wrote (https://blog.heroku.com/we-heard-your-feedback) about my enthusiasm to be part of the Heroku story, and I remain just as...

    blog.heroku.com

    heroku 프리티어 지원 중단

     

    헤로꾸를 사용하면 가장 큰 문제점이 서버가 지속적으로 실행되지 않기 때문에 장기간 트래픽이 없을 경우 서버가 중단되어 고작 이미지를 불러오는데도 부팅을 시도한다는 점입니다. 그래서 이미지가 안 나오는 이슈가 여러 발생하였습니다.

     

     그래서 어떤 플랫폼으로 갈아탈지 고민이 많았습니다. 제가 알아본 클라우드 서비스로는 fly.io와 Koyeb입니다. fly.io는 도커 기반으로 컨테이너를 배포할 수 있습니다. 도커를 한번 설치해 놓으면 장점이 도커허브에서 이미지 관리가 가능하므로 추후에 깡통서버로 옮긴다 하더라도 도커만 설치한다면 부담이 없다는 장점이 있었습니다.

     

    하지만 저는 좀 더 빠르고 간단하게 배포하고 싶어서 헤로꾸와 동일하게 Procfile을 사용하여 배포를 하는 Koyeb을 선택하였습니다. Koyeb 역시 헤로꾸처럼 github 레포지토리를 등록할 수 있기 때문에 굉장히 쉽게 배포할 수 있었습니다.

     

     하지만 단점은 서버가 해외에 있기 때문에 응답속도가 많이 느립니다. 대략 평균 응답시간을 측정해 보니 2초 정도 나옵니다. Github 이미지를 불러오는데 2초면 체감상 길게 느껴지긴 했습니다. 그리고 timezone 문제가 있어서 애플리케이션 단에서 강제로 한국 시간 기준으로 적용해 놨기 때문에 여러모로 찝찝한 느낌이 있습니다. 그래서 당분간은 Koyeb을 사용하되 추후에 또다시 서버 이전을 고려하고 있습니다.

     


    🌠 프로젝트 후기

     너무 재밌게 만들었던 프로젝트라서 혼자 개발이 가능했던 것 같고 그만큼 애정이 가는 프로젝트입니다. 아쉬웠던 점은 다른 사람들도 사용할 수 있도록 만들고 싶었지만 notion api키와 database id 등을 이미지 URL 파라미터로 전달받기에는 보안적인 이슈가 너무 강해서 개인정보를 저장할 수 있는 플랫폼이 별도로 있어야 되겠다는 생각이 들었습니다. 사실 이 외에도 여러 가지 생각하고 있는 기능들이 많은데 만약 다음 릴리즈 때 추가적으로 더 보완되는 기능들이 있다면 또다시 글로 찾아뵙겠습니다. 긴 글 읽어주셔서 감사합니다.🫡

    'Back-End > Python' 카테고리의 다른 글

    Github 리드미에 달력 표시하기 - 1탄  (0) 2023.05.21
    Python 프로젝트에 Prettier 적용하기  (0) 2023.05.05

    댓글

Designed by Tistory.