技術開発日記

技術やら日々思ったことを綴ってます。

AWS SNS を使ってプッシュ通知してみた【Java】

AWSの管理画面からプッシュする記事だったり、SDKを使っていても断片的なコードしかない記事が多かったので、アプリからサーバーサイドまでの一連の流れをまとめたものを作ってみました。

AWS SNS とは

簡単に言ってしまえばプッシュ通知をまとめて管理してくれるサービス。
詳しくはこちらで

今回の全体アーキテクチャ

f:id:keichanzahorumon0405:20150713201212j:plain

必要な作業

・プッシュ通知のための証明書などの設定

SNSから通知される用のアプリの作成

・トークンを登録する受け口

・プッシュ通知をする送信口

SNSから通知される用のアプリの作成

Simple View Application を作成

AppDelegate.m

#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"Register for push notifications");
    UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:setting];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
    return YES;
}

// 「PUSH通知許可」の場合
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *token = [NSString stringWithFormat:@"Device Token=%@",deviceToken];
    NSLog(@"%@",token);
    
    token = [token stringByReplacingOccurrencesOfString:@"<" withString:@""];
    token = [token stringByReplacingOccurrencesOfString:@">" withString:@""];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    
    NSURL *url = [NSURL URLWithString:@"http://xxx"];
    
    // SNSに登録リクエスト
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url
                                                       cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
                                                   timeoutInterval:3.0];
    
    NSHTTPURLResponse *resp;
    NSError *error;
    
    [NSURLConnection sendSynchronousRequest:req
                          returningResponse:&resp
                                      error:&error];

}

// 「PUSH通知拒否」の場合
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
    NSString *str = [NSString stringWithFormat: @"Error: %@", err];
    NSLog(@"%@",str);
}

// プッシュ通知を受け取ったときの処理
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSLog(@"ReceiveRemoteNotification: %@", userInfo);
}
@end

・トークンを登録するプログラム

 JavaとSpring MVCを使ってトークンを登録する受け口を作成
 細かい設定ファイルなどプロジェクト全体はこちらに

/**
 * Register the token requested from the device to publish later on.
 * 
 * @author keichanzahorumon
 *
 */
@Controller
public class RegisterController {
	
	private static final Logger logger = LoggerFactory.getLogger(RegisterController.class);
	@Autowired
	private String topicArn;
	@Autowired
	private String appArn;
	
	/**
	 * Register the token to the SNS.
	 *  
	 * @param locale
	 * @param model
	 * @param token
	 */
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public void register(Locale locale, Model model, @RequestParam("token") String token) {
		
		if (token == null || token.length() != 64) {
			logger.warn("token must be 64bit. token=" + token);
		}

		AmazonSNSClient snsClient = new AmazonSNSClient(new ClasspathPropertiesFileCredentialsProvider());		                           
		snsClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
		
		// Create endpoint from the requested token
		CreatePlatformEndpointRequest cpeReq = new CreatePlatformEndpointRequest();
		cpeReq.setPlatformApplicationArn(appArn);
		cpeReq.setToken(token);

		CreatePlatformEndpointResult result = snsClient.createPlatformEndpoint(cpeReq);
		logger.info("Succeeded at creating Endpointarn.EndpointArn=" + result.getEndpointArn());

		// Subscribe to Topic
		SubscribeRequest subscribeRequest = new SubscribeRequest();
		subscribeRequest.setTopicArn(topicArn);
		subscribeRequest.setProtocol("application");
		subscribeRequest.setEndpoint(result.getEndpointArn());

		SubscribeResult subscribeResult = snsClient.subscribe(subscribeRequest);
		logger.info("Succeeded at subscribing to a Topic.SubscriptionArn=" + subscribeResult.getSubscriptionArn());
	}
}

流れ
1.事前にAWS管理画面で作成したApplicationに通知された端末を識別するトークンを登録
2.登録すると EndpointArn が返却される
3.事前にAWS管理画面で作成したTopicのarn(TopicArn)と上記EndpointArnをもとにTopicに端末情報を登録

・プッシュ通知をするプログラム

/**
 * Publish the requested message 
 * 
 * @author keichanzahorumon
 *
 */
@Controller
public class PublishController {
	
	private static final Logger logger = LoggerFactory.getLogger(PublishController.class);
	@Autowired
	private String topicArn;
	
	/**
	 * Publish the requested message
	 * 
	 * @param locale
	 * @param model
	 * @param message
	 */
	@RequestMapping(value = "/publish", method = RequestMethod.GET)
	public void publish(Locale locale, Model model, @RequestParam("message") String message) {

		AmazonSNSClient snsClient = new AmazonSNSClient(new ClasspathPropertiesFileCredentialsProvider());		                           
		snsClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
		
		// Publish a message
		PublishRequest publishRequest = new PublishRequest(topicArn, message);
		snsClient.publish(publishRequest);
		
		logger.info("Published a message.Message=" + message);
	}
}

流れ
1.事前にAWS管理画面で作成したTopicにプッシュしたいメッセージを設定、通知
 結果、今までTopicに登録した端末達にメッセージがプッシュされる。

まとめ

Topic,EndPointArn,PlatformApplicationArn,PlatformEndpoint,Subscriptions などなど、SNS固有のものを理解するのに少し戸惑ったけど、こういったきちんとしたアーキテクチャで実装されているサービスはやはり勉強になる。
今回はモバイルにプッシュするのみの構成だが、他にもメールやHTTPリクエスト、Lambdaとか他のAWSサービスにも簡単に連携して通知できるのもなかなか面白い。

あまり関係ないけど、久しぶりにSpring MVCを触ったら設定ファイルやらいろいろハマって面倒かった。。