푸시 서비스중 하나인 FCM HTTP API 방식이 24년 6월에 최종적으로 지원이 종료됨에 따라 회사 앱도 HTTP V1 방식으로 마이그레이션을 해야만 했다. 소식은 지금으로 부터 1년전부터 알고 있었지만 귀찮아서 계속 미루고 있다가 어느덧 6월이 다가와 더이상 미룰 수가 없었다.
하지만 조금이나마 더 미룰 수 있을까하여 살펴보니 기존 HTTP API 방식도 계속 이용은 가능하다는 문구를 발견하여 이걸 회의때 어필하면 더 미룰 수 있겠구나 했는데 생각해보니 신규 앱들은 HTTP V1 방식만 이용 가능하기에 추가되는 앱이 있을 경우 어쩔 수 없이 HTTP V1 방식으로 개발을 해야되서 바로 관련 문서를 검색하기 시작했다.
우선 마이그레이션을 하기위해 공유받은 아래와 같이 FCM 공식 문서를 살펴보았는데 정말 막막했다.
간단하게 살펴보면 OAuth 2.0 엑세스 토큰을 발급받아서 인증하고 기존 FCM의 옵션값만 변경하면 된다고 했는데 문서를 보자마자 시작부터 막혀버렸다. 사실 나같은 경우 회사에 앱 개발자가 따로 있어서 항상 앱 연동되는 부분은 PHP 소스로 편하게 제공받아서 개발만 했기에 firebase 문서를 보고 개발할일은 거의 없었다.
하지만 이번에는 앱 개발자도 간단하게 정리된 PHP 문서를 확인하지 못했다 하여 내가 직접 연동하려고 보니 firebase console 자체가 익숙하지 않아 몇시간 삽질을 했던것같다. 서론은 여기까지 하고 빠르게 진행해보도록 하자.
본 포스팅은 2024.5.17일자 기준으로 작성 되었습니다. |
1. Firebase Console(https://console.firebase.google.com/u/0/?hl=ko)에 접속하여 프로젝트 선택 후 왼쪽 메뉴의 프로젝트 개요 > 톱니바퀴 아이콘 클릭 > 프로젝트 설정 클릭
2. 프로젝트 설정에서 서비스 계정을 선택
3. 새 비공개 키 생성 클릭 (버튼 위의 예시 소스는 무시해도 상관없다. 당연하게도 PHP는 제공안해준다. 나쁜.. ) 후 최종 키 생성 하여 json 파일을 다운로드
OAuth 2.0 엑세스 토큰을 생성하기 위해선 Google Client API를 이용하면 되는데 Composer 로 설치하게 되면 종속된게 워낙 많다보니 설치가 끝나고 용량을 보면 100MB 가 넘는다. (사실 필요없는게 어떤건지 몰라서 그냥 다 설치했으니 참고)
1. 리눅스 접속하여 설치 경로 생성 후 Composer 를 통해 google apiclient 설치 (명령어는 1줄씩)
mkdir google-api-php-client
cd google-api-php-client
composer require google/apiclient:^2.16.0
실행하고 나면 아래와 같이 설치 로그에 google apiclient 모듈 이외 종속된 모듈들도 설치가 된다. 이렇게 설치가 끝나면 모듈을 사용할 수 있는 환경은 구성이 되었고 작업은 50%가 완료된것으로 보면된다.
- Installing ralouphie/getallheaders (3.0.3): Loading from cache
- Installing psr/http-message (2.0): Loading from cache
- Installing psr/http-factory (1.1.0): Loading from cache
- Installing guzzlehttp/psr7 (2.6.2): Loading from cache
- Installing symfony/deprecation-contracts (v2.5.3): Loading from cache
- Installing psr/http-client (1.0.3): Loading from cache
- Installing guzzlehttp/promises (2.0.2): Loading from cache
- Installing guzzlehttp/guzzle (7.8.1): Loading from cache
- Installing paragonie/random_compat (v9.99.100): Loading from cache
- Installing paragonie/constant_time_encoding (v2.7.0): Loading from cache
- Installing phpseclib/phpseclib (3.0.37): Loading from cache
- Installing psr/log (1.1.4): Loading from cache
- Installing monolog/monolog (2.9.3): Loading from cache
- Installing firebase/php-jwt (v6.10.0): Loading from cache
- Installing google/apiclient-services (v0.355.0): Loading from cache
- Installing psr/cache (1.0.1): Loading from cache
- Installing google/auth (v1.37.1): Loading from cache
- Installing google/apiclient (v2.16.0): Loading from cache
처음 firebase 를 통해 다운로드 받은 비공개키를 FTP 접속하여 적당한 경로에 업로드 하자. 이 포스팅에서는 예시를 위해 같은 경로에 업로드 하였으며 실 운영시에는 위험하니 웹에서 접근 불가한 디렉토리에 업로드 하도록 하자.
푸시를 발송하기 위한 설정 및 모듈 로드를 아래와 같이 config.php 파일로 작성해 보자
<?php
// 메시징 엔드포인트 URL ( {프로젝트ID} 는 firebase console 에서 > 프로젝트개요 > 톱니바퀴 아이콘 클릭 > 프로젝트 설정 > 일반탭에서 확인 가능)
define('FCM_MESSAGE_ENDPOINT_URL','https://fcm.googleapis.com/v1/projects/{프로젝트ID}/messages:send');
// 구글 인증시 사용범위들 지정 (FCM 메시징 인증만 사용)
define('FCM_MESSAGE_SCOPES',array('https://www.googleapis.com/auth/firebase.messaging'));
// 비공개키값 경로 지정
define('FCM_MESSAGE_ADMIN_KEY_FILE',__DIR__.'/fcm-admin-key.json')
// autoload.php 불러온다.
require __DIR__.'/vendor/autoload.php';
// 네임스페이 적용
use Google\Client as Google_Client;
// 구글 비공개키값을 이용하여 OAuth 2.0 엑세스토큰 발급받는 함수 추가
function getAccessToken($serviceAccountFile = '') {
try {
// 파일 체크
if( empty($serviceAccountFile) || !is_file($serviceAccountFile)){
throw new Exception('비공개 키파일 비었음');
}
// Google 클라이언트 초기화
$client = new Google_Client();
$client->setAuthConfig($serviceAccountFile); // 파일로 보내도 되고 비공개 파일 json 데이터를 배열화 해서 보내도 된다.
$client->setScopes(FCM_MESSAGE_SCOPES);
$client->fetchAccessTokenWithAssertion();
$result = $client->getAccessToken();
if( empty($result['access_token'])){ throw new Exception("엑세스토큰 생성 실패"); }
return array('rst'=>'success','access_token'=>$result['access_token']);
} catch (Exception $e) {
return array('rst'=>'fail','msg'=>$e->getMessage());
}
}
푸시 메시지 단건을 보내는 예제이며 자세한 내용은 주석을 참고하도록 하자.
<?php // msgSend.php
// 설정파일로드
include_once __DIR__.'/config.php';
// 액세스 토큰 출력 예시 => 1시간동안 유효하니 상황에 따라 크론에서 5분단위로 체크하여 10분전에 초기화해서 사용해도됨
$getAccessToken = getAccessToken(FCM_MESSAGE_ADMIN_KEY_FILE);
// 실패처리
if( empty($getAccessToken['rst']) || $getAccessToken['rst'] != 'success' ){
$errMsg = empty($getAccessToken['msg']) ? '엑세스토큰 생성 실패':$getAccessToken['msg'];
die($errMsg);
}
// 최종 access_token 을 가져온다.
$accessToken = $getAccessToken['access_token'];
// 헤더정의
$headers = array(
'Content-Type:application/json',
'Authorization: Bearer '.$accessToken
);
// 전송 데이터
$requestData = array(
'message'=>array(
'token'=>'사용자 푸시 고유토큰', // APP 고유토큰, 앱에서 발급받을 수 있다. (기존 FCM으로 본다면 to 에 해당)
'notification'=>array(
'title'=>'푸시 타이틀', // 푸시 타이틀
'body'=>'푸시 내용' // 푸시내용
),
)
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, FCM_MESSAGE_ENDPOINT_URL);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS,json_encode($requestData));
$res = curl_exec($ch);
curl_close($ch);
// 최종 JSON 결과 데이터를 배열로 받는다.
$responseData = json_decode($res,true);
// 전송 성공 및 실패 처리 성공 시 name 값으로 결과 값이 온다.
if( !empty($responseData['name'])){
// 전송 성공처리
/*
==> 결과예시
Array
(
[name] => projects/redinfo-c05f9/messages/0:1715959401356544%529b9228529b9228
)
*/
}
else{
// 전송 실패처리
}
기존 HTTP API 방식에서는 멀티 발송 시 registration_ids 정보에 배열형태로 넣어주었을 것이다. 하지만 HTTP V1 API 방식에서는 아무리 찾아봐도 멀티로 발송하는 옵션 자체를 제공하지 않는것 같다. 따라서 반복문을 통해 기능을 만들어야 한다. 해당 소스는 아래와 같다.
<?php // msgSend.php
// 설정파일로드
include_once __DIR__.'/config.php';
// 액세스 토큰 출력 예시 => 1시간동안 유효하니 상황에 따라 크론에서 5분단위로 체크하여 10분전에 초기화해서 사용해도됨
$getAccessToken = getAccessToken(FCM_MESSAGE_ADMIN_KEY_FILE);
// 실패처리
if( empty($getAccessToken['rst']) || $getAccessToken['rst'] != 'success' ){
$errMsg = empty($getAccessToken['msg']) ? '엑세스토큰 생성 실패':$getAccessToken['msg'];
die($errMsg);
}
// 최종 access_token 을 가져온다.
$accessToken = $getAccessToken['access_token'];
// 헤더정의
$headers = array(
'Content-Type:application/json',
'Authorization: Bearer '.$accessToken
);
// 전송 데이터
$requestData = array(
'message'=>array(
'token'=>'', // 멀티발송시 토큰은 하단에서 재설정
'notification'=>array(
'title'=>'푸시 타이틀', // 푸시 타이틀
'body'=>'푸시 내용' // 푸시내용
),
)
);
// 푸시 발송토큰
$tokens = array('사용자 푸시 고유토큰1','사용자 푸시 고유토큰2','사용자 푸시 고유토큰3');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, FCM_MESSAGE_ENDPOINT_URL);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 생성한 사용자 고유토큰 만큼 전송
$responseDatas = array();
foreach($tokens as $token){
$requestData['message']['token'] = $token;
curl_setopt($ch, CURLOPT_POSTFIELDS,json_encode($requestData));
$res = curl_exec($ch);
// 최종 JSON 결과 데이터를 배열로 받는다.
$responseDatas[$token] = json_decode($res,true);
}
curl_close($ch);
// 최종 결과를 저장 -> 어떤 토큰을 가진 사용자가 실패했는지
foreach($responseDatas as $token=>$responseData){
if( !empty($responseData['name'])){
// 전송 성공처리
/*
==> 결과예시
Array
(
[name] => projects/redinfo-c05f9/messages/0:1715959401356544%529b9228529b9228
)
*/
}
else{
// 전송 실패처리
}
}
위와 같이 PHP를 이용하여 FCM HTTP V1 방식으로 푸시를 전송하는 방법에 대해 알아보았다. 사실 PHP는 빠르게 웹 개발을 할 수 있는 고마운 프로그래밍 언어이긴 하나 여러 종속된 라이브러리를 사용하기가 영 불편하다. 또한 OAuth 2.0 토큰 인증이 필요한 API 들은 문서상 PHP로 공식 제공되는 리소스가 없거나 빈약하다보니 처음 접근하는 사람들은 어려울 수 밖에 없다.
물론 git에서 Composer 이용하여 종속된 라이브러리를 쉽게 사용할 수 있도록 제공해 주는 문서가 많이 있긴 하나 실제 원하는 결과를 얻기 위해서는 검색을 다시 해야만 하고 오류 발생시 해결 방법이 없어 포기하는 경우도 많이 발생한다. 다행히도 요즘은 chat GPT 가 있어서 독고다이 개발자들에겐 많은 도움이 되지만 GPT도 어설프게 실수를 많이 해서 곤란할때도 있는건 사실이다.
아무튼 이 포스팅을 통해 PHP 환경에서 HTTP V1 으로 마이그레이션 하는데 조금이나마 도움이 되었으면 한다. 참고로 포스팅에 사용된 예제 소스는 실제 테스트는 해보지는 못하였고 마이그레이션 작업한 소스를 참고하여 작성하였으니 오류가 있을 경우 댓글로 남겨주기 바란다.
혹시나 댓글 보실까 싶지만 지푸라기 심정으로 질문 좀 ..
기존 방식으로 보낸푸시 선택하면 url넘긴 값으로 웹뷰 실행되던 앱인데 v1으로 보내니까 앱이 실행중일 때만 기존처럼 작동하고 앱이 실행중이 아닐 때 푸시를 선택하면 앱만 실행 되네요
앱개발자가 아니라 당최 뭔차이 인지 모르겠습니다 ㅠㅜ 혹시..?