본문 바로가기
Django/Django REST framework

Sendbird를 이용한 DRF 채팅서버 구현

by hyun-am 2022. 6. 24.

채팅 서버를 구현하기위해 고민한 사항들

내부적으로 구현 vs 써드파티를 사용해서 구현

내부적으로 구현

내부적으로 구현할 경우 Django에서 제공하는 Django channels라이브러리 라는것을 이용해 WebSocket프로그래밍을 진행하려고 했습니다.

이러면 장점과 단점이 있는데 장점은 직접 개발 구현을 해서 특정 어플리케이션을 이용하는 것에 대한 비용처리가 없고, 커스텀에도 용이합니다. 하지만 단점은 모든 것을 다 새로 구현하기 때문에 시간(학습에 대한 시간 + 구현에 대한 시간)이 오래 걸리고, 서버 혹은 데이터베이스와 같은 관리도 직접 만들어야 합니다.

써드파티를 통한 구현

채팅을 제공해주는 어플리케이션을 찾던 중 Sendbird라는 메시징 서비스 솔루션을 발견했습니다.

sendbird는 UI Kit, 클라이언트 SDK, 서버 및 인프라 그리고 강력한 관리자 도구를 제공하여 개발 및 운영의 부담을 획기적으로 낮추고 보다 쉽고 빠르게 실시간 커뮤니케이션의 이점을 누릴 수 있게 해줍니다. 또한 개발 API 문서가 아주 잘 정리되어 있어서 쉽고 빠르게 개발할 수 있겠다 라는 생각을 가졌습니다.

하지만 단점으로는 높은 비용과 제공하는 API선에서 사용해야한다는 점이 있습니다.

이렇게 서로 두 케이스에 대해서 많은 고민을 했는데 제일 우선으로 생각하는 신속한 개발과 서버가 안정하게 잘 돌아가는 케이스를 골라서 sendbird를 통해 채팅을 개발하기로 했습니다.

 

Django에 Sendbird 연동하기

서비스에서 필요한 채팅서버 구현 기능들

유저 관련 기능

  1. 회원가입할 경우 자동으로 sendbird에 user로 등록
    1. 이미 회원가입된 유저는 check_update를 통해 sendbird에 user로 등록
  2. 닉네임을 수정할 경우 sendbird닉네임도 변경
  3. 유저 발급 or 등록할 경우 sendbird session_token 발급받는 기능 구현
  4. sendbird session_token 기간 만료시 새로 토큰 받을 수 있게 하는 기능 구현
  5. 회원 탈퇴시 sendbird유저도 같이 삭제하는 기능 구현

그룹 관련 기능

  1. 그룹 생성 시 sendbird group_channel url 생성
  2. exit group할 경우 sendbird group_channel에서도 나갈 수 있게 수정
  3. group finish할 경우(그룹이 종료 될 경우)

기록 관련 기능

  1. (식단,운동,물) 기록 시 group_channel에 채팅 가게 구현

sendbird API사용을 위한 세팅

먼저 sendbird에 회원가입을 완료하고 APP을 등록한 상태라고 생각한 후 진행하겠습니다.

개인정보 → settings → General에 들어간 후 Application ID와 API tokens를 복사한 후 진행하겠습니다.

그 후 env파일에 등록한 후 settings.py에서 불러오게 한 후 진행하겠습니다.

.env

SENDBIRD_APPLICATION_ID=발급받은_sendbird_application_id
SENDBIRD_API_TOKEN=발급받은_sendbird_api_token

settings.py

SENDBIRD_APPLICATION_ID=env("SENDBIRD_APPLICATION_ID")
SENDBIRD_API_TOKEN=env("SENDBIRD_API_TOKEN")

이렇게 하면 1차적으로 sendbird를 사용하기위한 세팅은 마무리입니다.

또한 sendbird api를 사용하는 기능들은 모두 celery를 통해 비동기적으로 처리했습니다.

유저 관련기능 sendbird로 처리하기

유저 등록하기

API Docs : https://sendbird.com/docs/chat/v3/platform-api/user/creating-users/create-a-user

HTTP request

POST https://api-{application_id}.sendbird.com/v3/users

문서에서 보면 requst할때 body에 user_id, nickname, profile_url을 넣어준다고 합니다.

그래서 유저 생성을 위한 코드는 아래와 같이 작성했습니다.

from django.conf import settings

application_id = settings.SENDBIRD_APPLICATION_ID
sendbird_api_token = settings.SENDBIRD_API_TOKEN

@shared_task
def create_sendbird_user(user_id, nickname, profile_url=""):
    url = f"https://api-{application_id}.sendbird.com/v3/users"
    api_headers = {"Api-Token": sendbird_api_token}
    data = {
        "user_id": user_id,
        "nickname": nickname,
        "profile_url": profile_url,
    }
    res = requests.post(url, data=json.dumps(data), headers=api_headers)
    res_data = json.loads(res._content.decode("utf-8"))
    return json.dumps(res_data)
  • API를 사용하기 위해서는 headers에 아까 발급 받은 sendbird_api_token을 사용합니다.

유저 삭제하기

API Docs : https://sendbird.com/docs/chat/v3/platform-api/user/managing-users/delete-a-user

HTTP request

DELETE https://api-{application_id}.sendbird.com/v3/users/{user_id}

유저 삭제하기는 아주 간단합니다. user_id랑 DELETE mothod처리만 있으면 쉽게 처리할 수 있습니다.

@shared_task
def delete_sendbird_user(user_id):
    url = f"https://api-{application_id}.sendbird.com/v3/users/{user_id}"
    api_headers = {"Api-Token": sendbird_api_token}
    requests.delete(url, headers=api_headers)
    return f"{user_id}삭제 완료"

유저 닉네임 변경

API Docs : https://sendbird.com/docs/chat/v3/platform-api/user/managing-users/update-a-user

HTTP request

PUT https://api-{application_id}.sendbird.com/v3/users/{user_id}

@shared_task
def update_sendbird_user_nickname(user_id, nickname):
    url = f"https://api-{application_id}.sendbird.com/v3/users/{user_id}"
    api_headers = {"Api-Token": sendbird_api_token}
    data = {"nickname": nickname}
    requests.put(url, headers=api_headers, data=json.dumps(data))
    return f"{user_id}의 닉네임을 변경했습니다."

유저 put을 통해 다양한 것들을 수정할 수 있는데 저희는 닉네임 변경만 필요해서 data에는 nicknam만 넣어주고 진행했습니다.

유저 세션토큰 발급하기

API Docs : https://sendbird.com/docs/chat/v3/platform-api/user/managing-session-tokens/issue-a-session-token

HTTP request

POST https://api-{application_id}.sendbird.com/v3/users/{user_id}/token

클라이언트 유저가 그룹에 참여하는 api나 나가는 api를 사용하기 위해서는 유저마다 발급된 세션토큰을 통해 인증처리를 진행합니다 따라서 서버에서는 유저에 session토큰을 발급받을 수 있게 코드를 구현했습니다.

@shared_task
def create_sendbird_user_session_token(user_id):
    url = f"https://api-{application_id}.sendbird.com/v3/users"
    api_headers = {"Api-Token": sendbird_api_token}
    get_user_res = requests.get(
        f"{url}/{user_id}", headers=api_headers
    )
    if get_user_res.status_code == 200:
        url = f"https://api-{application_id}.sendbird.com/v3/users/{user_id}/token"
        api_headers = {"Api-Token": sendbird_api_token}
        expire_date = timezone.now() + timedelta(days=365)
        invert_expire_date = f"{invert_timestamp(expire_date)}000"
        data = {
            "expires_at": int(invert_expire_date),
        }
        res = requests.post(url, headers=api_headers, data=json.dumps(data))
        res_data = json.loads(res._content.decode("utf-8"))
        SendbirdSessionToken.objects.create(
            user_id=user_id,
            expiresAt=invert_expire_date,
            sessionToken=res_data["token"],
        )
        return f"{user_id}_{sendbird_excute_env}의 토큰을 발급했습니다."
    return "sendbird에 유저가 없습니다."

여기서 get_user_res.status_code로 if문을 진행한 이유는 해당 유저가 없으면 session_token을 발급받지 못하기 때문에 응답을 잘 받아오면 진행했습니다.

또한 expires_at을 custom할 수 있습니다. 빈값으로 요청할 경우 sendbird에서는 7일로 설정합니다. 근데 세션토큰을 좀 길게 가져가도 될 것 같아서 1년을 request body에 넣었습니다.

그리고 토큰을 효율적으로 관리하기 위해 SendbirdSessionToken이라는 모델을 추가시켜서 user_id, expiresAt, sessionToken형식으로 저장해서 관리했습니다.

그룹 관련기능 sendbird로 처리하기

Sendbird group_channel 생성하기

API Docs : https://sendbird.com/docs/chat/v3/platform-api/channel/creating-a-channel/create-a-group-channel

HTTP Request

POST https://api-{application_id}.sendbird.com/v3/group_channels

@shared_task
def create_sendbird_group_channel(name, channel_url):
    url = f"https://api-{application_id}.sendbird.com/v3/group_channels"
    api_headers = {"Api-Token": sendbird_api_token}
    data = {
        "name": name,
        "channel_url": channel_url,
				"is_public": True,
    }
    requests.post(url, data=json.dumps(data), headers=api_headers)
    return "그룹 채널 생성 완료"

그룹을 생성할때도 엄청 많은 request body가 필요하지만 전부 optional하기 때문에 저는 필요한 이름과 channel_url 그리고 초대장 없이 들어갈 수 있게 is_public=True로 설정했습니다.

Sendbird 채널 나가기

API Docs : https://sendbird.com/docs/chat/v3/platform-api/channel/managing-a-channel/leave-a-channel

HTTP Request

PUT https://api-{application_id}.sendbird.com/v3/group_channels/{channel_url}/leave

@shared_task
def leave_sendbird_group_channel(user_id, group_id):
    group_info = GroupInfo.objects.get(key=group_id)
    url = f"https://api-{application_id}.sendbird.com/v3/group_channels/{group_info.GroupChannelUrl}/leave"
    api_headers = {"Api-Token": sendbird_api_token}
    data = {
        "user_ids": [
            f"{user_id}_{sendbird_excute_env}",
        ]
    }
    requests.put(url, data=json.dumps(data), headers=api_headers)
    return f"유저({user_id})가 그룹 채널에서 나갔습니다."

해당하는 유저를 목록에서 빼는거라 put으로 진행합니다.

채널 삭제하기

API Docs : https://sendbird.com/docs/chat/v3/platform-api/channel/managing-a-channel/delete-a-group-channel

HTTP Request

DELETE https://api-{application_id}.sendbird.com/v3/group_channels/{channel_url}

@shared_task
def delete_sendbird_group_channel(group_id):
    group_info = GroupInfo.objects.get(key=group_id)
    url = f"https://api-{application_id}.sendbird.com/v3/group_channels/{group_info.GroupChannelUrl}"
    api_headers = {"Api-Token": sendbird_api_token}
    requests.delete(url=url, headers=api_headers)
    group_info.groupChannelUrl = ""
    group_info.save()
    return "그룹채널이 삭제되었습니다."

sendbird API를 통해 group_channel을 삭제한 후 DB에 저장된 groupChannelUrl도 삭제했습니다.

구현 후기

처음에는 영어로 된 API Docs를 보고 어떻게 처리해야할지 막막했습니다. 하지만 처음 sendbird api를 사용하는 개발자들도 쉽게 사용할 수 있을만큼 정리가 잘되어있는 문서가 있어서 차근차근 구현할 수 있었습니다. 먼저 제가 구현하고 싶은 기능에 어떤 API가 적합한지 찾고 또 그 API를 처리할때 주의사항이나 어떤 데이터가 필요한지 잘 나와있어서 오류가 나와도 쉽게 대처할 수 있었습니다.. 역시 sendbird.. 최고

'Django > Django REST framework' 카테고리의 다른 글

DRF throttling 사용기  (0) 2023.01.08
15. Throttling  (0) 2022.08.25
Django FCM 개발(DRF)  (0) 2022.03.01
DRF 테스트코드 개발 도전기  (0) 2021.12.17
14-Authentication  (0) 2021.09.26

댓글