서버 환경 및 FCM

Firebase 클라우드 메시징의 서버 측 구성요소는 2가지입니다.

  • Google에서 제공하는 FCM 백엔드
  • 서버 로직이 실행되는 앱 서버 또는 다른 신뢰할 수 있는 서버 환경(예: Google에서 관리하는 Firebase용 Cloud Functions 또는 다른 클라우드 환경)

앱 서버 또는 신뢰할 수 있는 서버 환경에서는 FCM 백엔드에 메시지 요청을 보내고, FCM 백엔드는 사용자 기기에서 실행되는 클라이언트 앱에 메시지를 보냅니다.

신뢰할 수 있는 서버 환경의 요구사항

앱 서버 환경은 다음 기준을 충족해야 합니다.

  • FCM 백엔드에 올바르게 형식이 지정된 메시지 요청을 보낼 수 있습니다.
  • 지수 백오프를 사용하여 요청을 처리하고 다시 보낼 수 있습니다.
  • 서버 승인 사용자 인증 정보와 클라이언트 등록 토큰을 안전하게 저장할 수 있습니다.
  • XMPP 프로토콜을 사용하는 경우 전송하는 각 메시지를 고유하게 구별하기 위해 서버에서 메시지 ID를 생성할 수 있어야 합니다. FCM HTTP 백엔드는 메시지 ID를 생성하여 응답에서 반환합니다. XMPP 메시지 ID는 발신자 ID별로 고유해야 합니다.

서버 옵션 선택

FCM 서버와 상호작용하는 방법을 결정해야 합니다. Firebase Admin SDK 또는 원시 프로토콜을 사용할 수 있습니다. 자주 사용되는 프로그래밍 언어를 지원하며 인증 및 승인 처리가 편리한 Firebase Admin SDK를 사용하는 것이 좋습니다.

FCM 서버와 상호작용할 수 있는 옵션은 다음과 같습니다.

  • Firebase Admin SDK - Node, 자바, Python, C#, Go 지원
  • FCM HTTP v1 API - 가장 최신 프로토콜로서 보다 안전한 승인과 유연한 크로스 플랫폼 메시징 기능 제공(Firebase Admin SDK는 이 프로토콜을 기반으로 하며 모든 고유 이점을 제공함). 새 기능은 일반적으로 HTTP v1 API에만 추가되므로 대부분의 사용 사례에 이 API를 사용하는 것이 좋습니다.
  • 기존 HTTP 프로토콜입니다. 새 프로젝트에서는 기존 프로토콜 대신 FCM v1 HTTP API를 사용하는 것이 좋습니다.
  • 기존 XMPP 서버 프로토콜입니다. 새 프로젝트에서는 기존 프로토콜 대신 FCM v1 HTTP API를 사용하는 것이 좋습니다.

FCM용 Firebase Admin SDK

Admin FCM API는 백엔드 인증을 처리하고 메시지 보내기와 주제 구독 관리를 지원합니다. Firebase Admin SDK를 사용하면 다음을 수행할 수 있습니다.

  • 개별 기기에 메시지 보내기
  • 주제 및 하나 이상의 일치하는 조건문에 메시지 보내기
  • 기기에서 주제 구독 및 구독 취소
  • 다양한 타겟 플랫폼에 맞는 메시지 페이로드 구성

Admin Node.js SDK는 기기 그룹에 메시지를 보내는 메서드를 제공합니다.

Firebase Admin SDK를 설정하려면 서버에 Firebase Admin SDK 추가를 참조하세요. 이미 Firebase 프로젝트가 있다면 SDK 추가부터 시작합니다. 또한 프로젝트의 클라우드 메시징 설정 페이지에서 Cloud Messagin API를 사용 설정해야 합니다. 그런 다음 Firebase Admin SDK가 설치되면 로직 작성을 시작하여 전송 요청을 작성할 수 있습니다.

FCM 서버 프로토콜

현재 FCM은 다음과 같은 원시 서버 프로토콜을 제공합니다.

앱 서버는 이러한 프로토콜을 별도로 사용하거나 동시에 사용할 수 있습니다. 가능하면 여러 플랫폼에 메시지를 보낼 수 있는 유연한 최신 프로토콜인 FCM HTTP v1 API를 사용하는 것이 좋습니다. 기기에서 서버로 업스트림 메시지를 보낼 필요가 있는 경우에는 XMPP 프로토콜을 구현해야 합니다.

XMPP 메시징은 다음 방식에 있어서 HTTP 메시징과 다릅니다.

  • 업스트림/다운스트림 메시지
    • HTTP: 클라우드에서 기기로 전송하는 다운스트림 전용
    • XMPP: 기기에서 클라우드로 전송하는 업스트림 및 클라우드에서 기기로 전송하는 다운스트림
  • 메시징(동기 또는 비동기)
    • HTTP: 동기 메시징입니다. 앱 서버가 HTTP POST 요청으로 메시지를 보내고 응답을 기다립니다. 이 방식은 동기 메시징이며 응답이 수신될 때까지 발신자가 다른 메시지를 보내지 못하도록 차단합니다.
    • XMPP: 비동기 메시징입니다. 앱 서버가 영구 XMPP 연결을 통해 최대 회선 속도로 모든 기기에서 양방향으로 메시지를 주고받습니다. XMPP 연결 서버가 확인 또는 실패 알림을 비동기 방식으로 보냅니다. 확인 형식은 JSON으로 인코딩된 특수한 ACK 및 NACK XMPP 메시지입니다.
  • JSON
    • HTTP: JSON 메시지는 HTTP POST로 전송됩니다.
    • XMPP: JSON 메시지는 XMPP 메시지에 캡슐화됩니다.
  • 일반 텍스트
    • HTTP: 일반 텍스트 메시지는 HTTP POST로 전송됩니다.
    • XMPP: 지원되지 않습니다.
  • 여러 등록 토큰에 전송되는 멀티캐스트 다운스트림
    • HTTP: JSON 메시지 형식으로 지원됩니다.
    • XMPP: 지원되지 않습니다.

HTTP 서버 프로토콜 구현

메시지를 보내려면 앱 서버에서 JSON 키-값 쌍으로 구성된 HTTP 헤더와 HTTP 본문을 포함하는 POST 요청을 만듭니다. 헤더 및 본문의 옵션에 관한 자세한 내용은 앱 서버 보내기 요청 작성을 참조하세요.

XMPP 서버 프로토콜 구현

FCM 메시지의 JSON 페이로드는 HTTP 프로토콜과 비슷하지만 다음과 같은 차이가 있습니다.

  • 여러 수신자가 지원되지 않습니다.
  • FCM은 필수 항목인 message_id 필드를 추가합니다. 이 ID는 XMPP 연결에서 메시지를 고유하게 식별합니다. FCM의 ACK 또는 NACK는 message_id를 사용하여 앱 서버에서 FCM으로 보낸 메시지를 식별합니다. 따라서 이 message_id발신자 ID별로 고유해야 할 뿐만 아니라 항상 존재해야 합니다.
  • XMPP는 서버 키를 사용하여 FCM에 대한 영구 연결을 승인합니다. 자세한 내용은 보내기 요청 승인을 참조하세요.

일반적인 FCM 메시지 외에도 JSON 객체의 message_type 필드로 표시되는 관리 메시지가 전송됩니다. 값은 'ack', 'nack' 또는 'control'일 수 있습니다. 아래 형식을 참조하세요. 알 수 없는 message_type이 있는 모든 FCM 메시지는 서버에서 무시될 수 있습니다.

앱 서버가 FCM에서 수신하는 기기 메시지마다 ACK 메시지를 보내야 합니다. NACK 메시지는 보낼 필요가 없습니다. 메시지에 대한 ACK를 보내지 않으면 이 메시지가 먼저 만료되지 않는 한 다음에 새로운 XMPP 연결이 설정될 때 FCM이 메시지를 다시 보냅니다.

또한 서버에서 기기로 전송한 메시지별로 FCM에서 ACK 또는 NACK를 보냅니다. 둘 다 수신되지 않았다면 작업 중에 TCP 연결이 종료되었으며 서버에서 메시지를 다시 보내야 한다는 의미입니다. 자세한 내용은 흐름 관리를 참조하세요.

모든 메시지 매개변수 목록은 프로토콜 참조를 확인하세요.

요청 형식

페이로드가 포함된 메시지 - 알림 메시지

다음은 알림 메시지의 XMPP 스탠자입니다.

<message id="">
  <gcm xmlns="google:mobile:data">
  {
     "to":"REGISTRATION_ID",  // "to" replaces "registration_ids"
     "notification": {
        "title": "Portugal vs. Denmark”,
        "body”: "5 to 1”
      },
      "time_to_live":"600"
  }
  </gcm>
</message>

페이로드가 포함된 메시지 - 데이터 메시지

다음은 앱 서버에서 FCM으로 보내는 JSON 메시지가 포함된 XMPP 스탠자입니다.

<message id="">
  <gcm xmlns="google:mobile:data">
  {
      "to":"REGISTRATION_ID",  // "to" replaces "registration_ids"
      "message_id":"m-1366082849205" // new required field
      "data":
      {
          "hello":"world",
      }
      "time_to_live":"600",
  }
  </gcm>
</message>

응답 형식

FCM 응답으로 가능한 형식은 세 가지입니다. 첫 번째는 일반적인 'ack' 메시지입니다. 그러나 응답에 오류가 포함된 경우 메시지는 다음과 같은 두 가지 형식을 취할 수 있습니다.

ACK 메시지

다음은 FCM에서 앱 서버로 보내는 ACK 또는 NACK 메시지가 포함된 XMPP 스탠자입니다.

<message id="">
  <gcm xmlns="google:mobile:data">
  {
      "from":"REGID",
      "message_id":"m-1366082849205"
      "message_type":"ack"
  }
  </gcm>
</message>

NACK 메시지

NACK 오류는 message_type 상태 메시지가 'nack'인 일반적인 XMPP 메시지입니다. NACK 메시지에는 다음이 포함됩니다.

  • NACK 오류 코드
  • NACK 오류 설명

아래는 몇 가지 예시입니다.

잘못된 등록:

<message>
  <gcm xmlns="google:mobile:data">
  {
    "message_type":"nack",
    "message_id":"msgId1",
    "from":"SomeInvalidRegistrationToken",
    "error":"BAD_REGISTRATION",
    "error_description":"Invalid token on 'to' field: SomeInvalidRegistrationId"
  }
  </gcm>
</message>

잘못된 JSON:

<message>
 <gcm xmlns="google:mobile:data">
 {
   "message_type":"nack",
   "message_id":"msgId1",
   "from":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
   "error":"INVALID_JSON",
   "error_description":"InvalidJson: JSON_TYPE_ERROR : Field \"time_to_live\" must be a JSON java.lang.Number: abc"
 }
 </gcm>
</message>

기기 메시지 비율 초과:

<message id="...">
  <gcm xmlns="google:mobile:data">
  {
    "message_type":"nack",
    "message_id":"msgId1",
    "from":"REGID",
    "error":"DEVICE_MESSAGE_RATE_EXCEEDED",
    "error_description":"Downstream message rate exceeded for this registration id"
  }
  </gcm>
</message>

NACK 오류 코드의 전체 목록은 서버 참조를 확인하세요. 별도로 표시되어 있지 않으면 NACK 상태인 메시지를 다시 시도해서는 안 됩니다. 예상하지 못한 NACK 오류 코드는 INTERNAL_SERVER_ERROR와 동일하게 처리해야 합니다.

스탠자 오류

특정 상황에서는 스탠자 오류가 발생할 수도 있습니다. 스탠자 오류에는 다음이 포함됩니다.

  • 스탠자 오류 코드
  • 스탠자 오류 설명(자유로운 텍스트 형식)

예를 들면 다음과 같습니다.

<message id="3" type="error" to="123456789@fcm.googleapis.com/ABC">
  <gcm xmlns="google:mobile:data">
     {"random": "text"}
  </gcm>
  <error code="400" type="modify">
    <bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
    <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
      InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
    </text>
  </error>
</message>

관리 메시지

FCM은 부하 분산을 위해 주기적으로 연결을 종료해야 합니다. 연결을 종료하기 전에 FCM이 CONNECTION_DRAINING 메시지를 보내 연결이 드레이닝되는 중이며 곧 종료된다고 알립니다. '드레이닝'이란 연결로 유입되는 메시지 흐름이 종료되지만 이미 파이프라인에 있는 메시지는 계속해서 처리됨을 의미합니다. CONNECTION_DRAINING 메시지가 수신되면 즉시 다른 FCM 연결로 메시지를 보내야 하며 필요한 경우 새 연결을 열어야 합니다. 하지만 원래 연결은 열려 있는 상태로 유지하여 이 연결을 통해 수신될 수 있는 메시지를 계속 수신하고 ACK 처리해야 합니다. 준비가 완료되면 FCM이 연결 종료를 시작하는 작업을 처리합니다.

CONNECTION_DRAINING 메시지는 다음과 같이 표시됩니다.

<message>
  <data:gcm xmlns:data="google:mobile:data">
  {
    "message_type":"control"
    "control_type":"CONNECTION_DRAINING"
  }
  </data:gcm>
</message>

현재 지원되는 control_typeCONNECTION_DRAINING뿐입니다.

흐름 관리

FCM으로 보낸 모든 메시지는 ACK 또는 NACK 응답을 받습니다. 이러한 응답을 받지 못한 메시지는 대기 중인 것으로 간주됩니다. 대기 중인 메시지 수가 100개에 도달하면 앱 서버에서 메시지를 새로 보내는 것을 중지하고 그림 1에 나와 있는 것처럼 FCM이 대기 중인 기존 메시지 중 일부를 확인할 때까지 기다려야 합니다.

FCM과 앱 서버 간 제어 흐름을 자세히 보여주는 다이어그램

그림 1. 메시지 ACK 흐름

반대로, 확인되지 않은 메시지가 너무 많으면 앱 서버에 과부하가 발생하지 않도록 FCM이 전송을 중지합니다. 따라서 FCM을 통해 클라이언트 애플리케이션으로부터 수신한 업스트림 메시지를 앱 서버에서 최대한 빠르게 'ACK' 처리하여 수신 메시지의 흐름을 일정하게 유지해야 합니다. 이러한 ACK에는 앞서 설명한 대기 중인 메시지의 한도가 적용되지 않습니다. 대기 중인 메시지 수가 100개에 도달해도 새로운 업스트림 메시지의 전송이 차단되지 않도록 앱 서버가 FCM에서 수신한 메시지에 대해 계속 ACK를 보내야 합니다.

ACK는 연결 1개 내에서만 유효합니다. 메시지를 ACK 처리하기 전에 연결이 종료되면 다시 ACK 처리하기 전에 FCM에서 업스트림 메시지를 다시 보낼 때까지 앱 서버에서 대기해야 합니다. 마찬가지로 연결이 종료되기 전에 FCM에서 ACK 또는 NACK가 수신되지 않은 대기 중인 모든 메시지도 다시 보내야 합니다.