Вступление
Цель этой статьи — дать обзор, а также некоторые рекомендации по использованию инфраструктуры JBOSS RestEasy в качестве вспомогательного уровня обслуживания для приложения на основе Apple IOS, размещенного в облачной среде, такой как Google App Engine.
Технологический стек
клиент
iPhone IOS 4.2
JSON-Framework для IOS
Facebook Connect для IOS
сервер
JBOSS RestEasy
UrbanAirship Служба уведомлений
Обзор архитектуры
На следующей диаграмме показаны основные компоненты, используемые в приложении Pling.
Связь между IOS и Google App Engine обрабатывается:
· IOS Foundation Framework с использованием классов, связанных с NSURL
· RestEasy Framework в Google App Engine для обработки входящих запросов IOS
Связь между Google App Engine и UrbanAirship для обработки push-уведомлений осуществляется:
· Google App Engine URLFetchService
· Джексон для сериализации и десериализации данных JSON
Отметим, что Google App Engine может взаимодействовать только с внешними веб-службами с использованием предоставленной инфраструктуры URLFetchService. Однако фреймворки, использующие HttpClient, могут использовать Google App Engine, предоставляя пользовательские классы для реализации базового сетевого доступа (HttpClientManager, HttpConnection или ClientConnectionManager, ManagedClientConnection в зависимости от версии HttpClient). Плинг не полагается на такую функцию.
Реализация RestEasy
Использовать и настраивать RestEasy в Google App Engine довольно просто.
После добавления библиотек фреймворка RestEasy в Eclipse, пришло время настроить файл web.xml в каталоге war / WEB-INF.
Поскольку экземпляры приложения Google App Engine запускаются по требованию (если вы не платите за зарезервированные экземпляры), время запуска имеет решающее значение, и поэтому автоматическое сканирование поставщиков RestEasy и сканирование ресурсов должны быть отключены. Это увеличивает время ответа на первый запрос REST, так как экземпляр Pling перезапускается Google App Engine.
В результате отключения ресурсов и сканирования поставщиков файл web.xml должен содержать выделенные записи для реализованных классов, связанных с Pling.
Есть два класса ресурсов:
· Служба управления пользователями для обработки регистрации пользователей и сохранения соответствия между маркерами push-уведомлений (специфичными для IOS) и идентификатором пользователя Facebook (предоставляется при успешном входе в систему через Facebook Connect)
· Сервис push-уведомлений для обработки регистрации устройств IOS и запроса push-уведомлений. Для Pling реализовано 2 типа запросов:
o Запрос на передачу информации о местоположении другу Pling на Facebook
o Запрос на запрос информации о местонахождении у друга из Facebook Pling
Существует один класс провайдеров:
· Pling Exception Mapper для обработки ошибок, возникающих на прикладном уровне
Ниже приводится выдержка для конкретных элементов RestEasy, относящихся к файлу web.xml.
<context-param>
<param-name>resteasy.scan.providers</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>resteasy.scan.resources</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>
com.plingmyphone.service.impl.UserManagementServiceImpl,
com.plingmyphone.service.impl.PushNotificationServiceImpl
</param-value>
</context-param>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>
com.plingmyphone.service.PlingExceptionMapper
</param-value>
</context-param>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
Обработка исключений
Все исключения, которые необходимо передать обратно клиенту IOS, обрабатываются функциями JAX-RS ExceptionMapper и ResponseBuilder.
В Pling все исключения направляются через уникальное исключение с использованием статуса HTTP 409.
Ниже приведен код, связанный с нашим пользовательским исключением Pling JSON.
@Provider
public class PlingExceptionMapper implements ExceptionMapper<com.plingmyphone.service.PlingException>
{
public Response toResponse(PlingException exception)
{
ResponseBuilder rb =
Response.status(409)
.entity("{\"error\": { \"type\": \"" +
exception.getClass().getSimpleName() +
"\", \"message\": \"" + exception.getMessage() + "\"}}")
.type("application/json");
return rb.build();
}
}
Сервисы
Управление пользователями и услуги push-уведомлений — это две точки входа в REST.
Методы HTTP, используемые в этих сервисах:
· POST, используя параметр запроса и класс домена, который автоматически десериализуется из JSON в Java (благодаря JAX-RS / RestEasy!)
· GET, используя параметр пути , и возвращающий класс домена , который автоматически получает сериализован в JSON из Java (Спасибо, еще раз, к JAX-RS / Resteasy!)
· PUT, используя путь, параметры запроса, а также класс домена, который автоматически десериализуется из JSON в Java (благодаря … JAX-RS / RestEasy!)
· УДАЛИТЬ, используя параметры пути и запроса.
Любые связанные методы Push-уведомлений также используют JAX-RS DefaultValue для использования по умолчанию экземпляра разработки UrbanAirship. Apple, определяет 2 типа экземпляров:
· Разработка, пуш-уведомления, песочница
· Производство, push-уведомления
На следующей диаграмме показаны 2 экземпляра, настроенных в веб-консоли UrbanAirship.
Ниже приведен пример метода, использующего DefaultValue для управления выбором базового экземпляра UrbanAirship.
@Override
@POST
@Path("/msg")
public void pushMessage(@QueryParam("envType") @DefaultValue("DEV") String envType,
PlingNotification notification)
{
…
}
PlingNotification — это доменный объект, который используется:
· RestEasy для сериализации / десериализации JSON
· JPA для управления хранилищем данных в Google App Engine
Если параметр запроса envType опущен в вызове REST, экземпляром push-уведомления будет DEV, поэтому он указывает на экземпляр pling UrbanAirship (см. Рисунок выше), если для параметра envType установлено значение PROD, экземпляр UrbanAirhsip pling-prod будет используемый.
В следующем разделе освещено определение класса PlingNotification.
@Entity
@Table(name = "PlingNotification")
@XmlRootElement
public class PlingNotification implements Serializable
{
…
}
PlingNotification помечается как объект JPA и получает имя в виде таблицы для основных постоянных возможностей, предлагаемых Google App Engine.
На следующей диаграмме показано, что доступно на специальной панели Google App Engine для Pling.
Здесь используется привязка Java XML RootElement, позволяющая RestEasy сериализовать / десериализовать объект PlingNotification от JSON до Java и от Java до JSON.
IOS Доступ к открытым службам RestEasy
Доступ к службам управления пользователями и push-уведомлений Конечные точки RestEasy на платформе IOS создаются с помощью комбинации встроенных возможностей доступа к сети и URL-адресам, а также внешней среды JSON для сериализации и десериализации сериализации представления JSON в IOS Objective-C.
В приложении Pling используются 2 типа запросов REST:
· Асинхронные вызовы с использованием [NSURLConnection connectionWithRequest…]
· Синхронные вызовы с использованием [NSURLConnection sendSynchronousRequest…]
Пример асинхронного запроса для отправки уведомления о местоположении в конечную точку RestEasy:
NSMutableURLRequest *pushNotificationRequest;
NSString *UAServer = @"http://url_to_gae_instance/service/notification/msg";
// Construct URL to access RestEasy endpoint
pushNotificationRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[pushNotificationRequest setHTTPMethod:@"POST"];
// Send along our device alias as the JSON encoded request body
[pushNotificationRequest addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[pushNotificationRequest setHTTPBody:[jsonNotification
dataUsingEncoding:NSUTF8StringEncoding]];
[[NSURLConnection connectionWithRequest:pushNotificationRequest delegate:self] start];
[pushNotificationRequest release];
Асинхронные вызовы полагаются на сообщение делегата NSURLConnection (аналогично обратному вызову):
- (void)connection:(NSURLConnection *)theConnection didReceiveResponse:(NSURLResponse *)response
Пример синхронного запроса на получение сведений, связанных с сообщением push-уведомления, от конечной точки RestEasy:
NSURLResponse *response = nil;
NSError *error = nil;
PlingNotification *plingNotification = nil;
// Construct the url to access RestEasy endpoint
pushNotificationRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[pushNotificationRequest setHTTPMethod:@"GET"];
[pushNotificationRequest addValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSData *returnData = [NSURLConnection sendSynchronousRequest:pushNotificationRequest returningResponse:&response error:&error];
[pushNotificationRequest release];
// Convert returned JSON data to a PlingNotification object
if(returnData != nil)
{
plingNotification = [[PlingNotification alloc] initWithJSON:[[[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding] autorelease]];
}
Поскольку PlingNotification является сложным объектом, JSON Framework на IOS позволяет расширить объект для предоставления вспомогательных методов для сериализации и десериализации JSON в Objective-C и Objective-C в JSON.
2 метода:
· — (id) initWithJSON: (NSString *) JSONString
· — (id) proxyForJson
Первый метод используется для преобразования JSON в сам объект. Второй преобразует объект в JSON.
Ниже приведен фрагмент каждой реализации метода:
- (id) initWithJSON:(NSString *)JSONString
{
self = [super init];
if (self != nil)
{
SBJsonParser *parser = [SBJsonParser new];
NSDictionary *jsonObject = (NSDictionary *)[parser objectWithString:JSONString];
NSLog(@"NB ENTRIES IN JSON: %d", [jsonObject count]);
NSLog(@"JSON STRING: %@", JSONString);
self.fromFacebookUID = [jsonObject objectForKey:@"fromFacebookUID"];
// Do the rest here (many fields…)
}
return self;
}
- (id)proxyForJson
{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
fromFacebookUID, @"fromFacebookUID",
// more key value pairs here
nil];
for(NSString *key in [dict allKeys])
{
NSLog(@"=====> KEY: %@", key);
}
return dict;
}