[iOS] 아이폰 푸시알림 오지 않는 현상 수정 (HTTP/2 APNs Provider API 적용)

[iOS] 아이폰 푸시알림 오지 않는 현상 수정 (HTTP/2 APNs Provider API 적용)

기존에 잘 동작하던 아이폰 푸시알림이 오지 않는 문제가 있었다.

기존에 푸시알림을 gateway.push.apple.com:2195 (개발은 gateway.sandbox.push.apple.com:2195) 로 보내고 있었는데 보내는 것은 문제가 없었다. 로그상 아무런 오류도 없었고 gateway.push.apple.com:2195 까지 잘 도달하는 것 같았다.

문제는 아이폰에 푸시알림이 오지 않는 것이었다. 나의 역할은 gateway.push.apple.com:2195 까지 정보를 보내는 것까지이고, gateway.push.apple.com:2195 에서 아이폰에 푸시알림을 왜 보내주지 않는지는 알 수도 없고 통제할 수도 없었다.

앱 4개(서로 다른 번들 아이디를 가진 앱) 중 사이트 3개가 푸시알림이 오지 않았다. 앱 1개는 오고 있다는 점 때문에 상당히 헷갈렸는데, 테스트를 거듭한 결과 푸시알림이 오는 앱은 adhoc 프로비저닝 프로파일을 입힌 앱이었고, 나머지 3개 사이트는 enterprise 프로비저닝 프로파일을 입힌 앱이었다.

결론은 2021년 3월 31일 이후로 기존 애플 푸시알림 방식이 레거시로 분류되어 더 이상 지원하지 않게 되었다.

이제 gateway.push.apple.com:2195 가 아닌 api.push.apple.com:443 으로 푸시알림을 보내야 한다.


APNs 제공업체 API 기한 업데이트 : https://developer.apple.com/kr/news/?id=c88acm2b

구분

Binary Provider API (APNs Legacy API)

APNs Provider API

Protocol

TCP

HTTP/2, TLS1.2 이상

URL/PORT

gateway.push.apple.com:2195,2196

api.push.apple.com:443

payload

2KB

4KB

인증방식

인증서(.p12 파일)

인증서(.p12 파일)

토큰방식도 사용 가능(.p8)

관련 오픈소스 라이브러리

apns-1.0.0.Beta6.jar

https://github.com/notnoop/java-apns

apns-http2-1.0.5.jar

https://github.com/CleverTap/apns-http2

참고사이트 : http://photogrammer.pe.kr/?p=265

푸시알림을 적용하는 방법은 크게 2가지가 있다고 한다.

(1) HTTP/2 사용하는 방법 (직접 APNs로 푸시알림 전달)

(2) Google Firebase를 사용하는 방법 (Firebase에 보내면 Firebase가 APNs로 푸시알림 전달)

개인적으로 iOS 앱을 Firebase 에 맞게 변경하는 것을 원하지 않았기 때문에 1번 방법을 선택했다.

이에 따라 기존 푸시알림 코드를 걷어내고 새로운 푸시알림 코드를 작성했다.

만약 2번 Firebase를 사용하는 방법을 택하려면 다음 주소를 참고하면 된다.

iOS 프로젝트에 Firebase 추가 : https://firebase.google.com/docs/ios/setup?hl=ko#objective-c

[APNs/Firebase] Google Firebase에 iOS App 등록 및 통신하기 : http://photogrammer.pe.kr/?p=288

1번 방법에서는 JDK 1.8 이상을 사용해야 한다.

그런데 openjdk 에서는 protocol 오류(Caused by: javax.net.ssl.SSLException: Received fatal alert: protocol_version)가 발생해서 오라클 JDK를 사용했다.

참고로 오라클 JDK는 1.8.0_202 버전까지 무료이고 이후 1.8.0_211 버전부터 기업에서 유료다.

참고사이트 : 오라클 Java SE(JRE/JDK) 유료버전, 무료버전

https://broccolies.com/2021/03/%EC%98%A4%EB%9D%BC%ED%81%B4-java-sejre-jdk-%EC%9C%A0%EC%83%81%EB%B2%84%EC%A0%84/

아래와 같이 새 프로그램을 작성해서 테스트해봤다.

잘 동작한다.

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;

import com.clevertap.apns.ApnsClient;
import com.clevertap.apns.Notification;
import com.clevertap.apns.Notification.Builder;
import com.clevertap.apns.NotificationResponse;
import com.clevertap.apns.clients.ApnsClientBuilder;

public class MainClass {

    public static void main(String[] args) {
        
        String sendMessage = “Hello World”;
        
        String p12FilePath = “p12파일 경로”;
        String p12FilePassword = “p12파일 패스워드”;
        
        String deviceToken = “아이폰 디바이스 토큰”;
        String iosAppBundleId = “아이폰 앱 번들아이디(패키지명)”;
        
        FileInputStream cert = null;
        
        try {
            cert = new FileInputStream(p12FilePath);
            final ApnsClient client = new ApnsClientBuilder()
             .withProductionGateway()
             .inSynchronousMode()
             .withCertificate(cert)
             .withPassword(p12FilePassword)
             .withDefaultTopic(iosAppBundleId)
             .build();
            
            Builder builder = new Notification.Builder(deviceToken);
            
            int badgeCount = 1;
            builder.badge(badgeCount);
            
            builder = builder.sound(“defualt”);
            builder = builder.alertBody(sendMessage);
            
            builder = builder.customField(“field1”“value1”);
            builder = builder.customField(“field1”“value2”);
            builder = builder.customField(“field1”“value3”);
            
            Notification notification = builder.build();
            
            NotificationResponse result = client.push(notification);
            System.out.println(result);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (cert != null) {
                    cert.close();
                }
            } catch (IOException e) { 

              } catch (Exception e) {
            } finally {
                cert = null;
            }
        }
    }
}

■ JDK 버전

jdk1.8.0_112

■ 사용 라이브러리

apns-http2-1.0.5.jar

commons-codec-1.11.jar

jackson-annotations-2.7.0.jar

jackson-core-2.7.0.jar

jackson-databind-2.1.4.jar

okhttp-3.2.0.jar

okio-1.6.0.jar

기존 라이브러리에 해당하는 apns-1.0.0.Beta6.jar, bcprov-ext-jdk15on-153.jar, javapns.jar 는 걷어냈다. 직접적인 라이브러리는 아니지만 jackson-annotations-2.1.1.jar, jackson-core-2.1.1.jar, jackson-databind-2.1.1.jar 도 걷어냈다.

■ 기타

HTTP/2 통신을 하기 위해 APLN Boot 를 사용해야 한다. APLN Boot 는 jetty 를 기반으로 한 것으로 보인다.

APLN Boot 의 버전은 사용하는 JDK 버전에 맞게 구해야 한다.

참고사이트 1 : https://www.ibm.com/docs/ko/was-liberty/base?topic=40-alpn-support

1.8.0_5=8.1.0.v20141016

1.8.0_11=8.1.0.v20141016

1.8.0_20=8.1.0.v20141016

1.8.0_25=8.1.2.v20141202

1.8.0_31=8.1.3.v20150130

1.8.0_40=8.1.3.v20150130

1.8.0_45=8.1.3.v20150130

1.8.0_51=8.1.4.v20150727

1.8.0_60=8.1.5.v20150921

1.8.0_65=8.1.6.v20151105

1.8.0_66=8.1.6.v20151105

1.8.0_71=8.1.7.v20160121

1.8.0_72=8.1.7.v20160121

1.8.0_73=8.1.7.v20160121

1.8.0_74=8.1.7.v20160121

1.8.0_77=8.1.7.v20160121

1.8.0_91=8.1.7.v20160121

1.8.0_92=8.1.8.v20160420

1.8.0_101=8.1.9.v20160720

1.8.0_102=8.1.9.v20160720

1.8.0_111=8.1.9.v20160720

1.8.0_112=8.1.10.v20161026

1.8.0_121=8.1.11.v20170118

1.8.0_131=8.1.11.v20170118

1.8.0_141=8.1.11.v20170118

1.8.0_144=8.1.11.v20170118

1.8.0_151=8.1.11.v20170118

1.8.0_152=8.1.11.v20170118

1.8.0_161=8.1.12.v20180117

1.8.0_162=8.1.12.v20180117

1.8.0_171=8.1.12.v20180117

1.8.0_172=8.1.12.v20180117

1.8.0_181=8.1.12.v20180117

1.8.0_191=8.1.13.v20181017

1.8.0_192=8.1.13.v20181017

1.8.0_201=8.1.13.v20181017

1.8.0_202=8.1.13.v20181017

1.8.0_211=8.1.13.v20181017

1.8.0_212=8.1.13.v20181017

1.8.0_221=8.1.13.v20181017

1.8.0_222=8.1.13.v20181017

1.8.0_231=8.1.13.v20181017

1.8.0_232=8.1.13.v20181017

1.8.0_241=8.1.13.v20181017

1.8.0_242=8.1.13.v20181017

위 표와는 조금 다르지만 나는 jdk1.8.0_112 버전을 사용하고 alpn-boot-8.1.8.v20160420.jar 를 사용했다. ALPN Boot jar 는 일반적인 jar 처럼 classpath에 추가하는 것이 아니라, bootclasspath (부트스트랩 클래스패스) 에 넣어야 한다.

-Xbootclasspath/p:D:\alpn-boot-8.1.8.v20160420.jar

 

이클립스 톰캣에서도 위와 같이 VM arguments 에 추가하면 된다.

파일시스템에 설치된 톰캣에 적용하려면 catalina.bat 파일을 수정하면 된다.

[톰캣경로]\bin\catalina.bat 149라인 부근 수정

[AS-IS]

set CLASSPATH=

[TO-BE]

set CLASSPATH=

set CATALINA_OPTS=-Xbootclasspath/p:D:\alpn-boot-8.1.8.v20160420.jar

참고사이트 : https://heowc.dev/2019/05/13/ios-push-to-java/