Статьи

Библиотека J2ME и простая сервисная структура HTTP

Поддержка J2ME для вызова сервера довольно проста и низка. Вам не только приходится иметь дело с HTTP-соединением на низком уровне, но и нет поддержки высокого уровня для файлов cookie, аутентификации или удаленного вызова процедур. Поэтому, если вы хотите передать объект на сервер и получить ответ, вам необходимо выяснить, как это сделать. XML поверх HTTP является одним из решений, но представляет свои собственные проблемы, такие как сериализация и десериализация объектов, не говоря уже о более высоком сетевом трафике из-за метаданных, содержащихся в XML. JAX Binding определенно не поддерживается в J2ME-land, поэтому вам придется использовать SAX-парсер.

В предыдущих проектах я играл с простым способом предоставления услуг через JSP, которые принимают и получают текст с разделителями. Идея состоит в том, чтобы реализовать собственную простую сериализацию и десериализацию простых объектов, позволяющую вам совершать простые вызовы на сервер и получать простые ответы. Я целенаправленно использовал слово «простой» в последнем предложении четыре раза, чтобы убедить вас в том, что серверные вызовы должны быть простыми.

Возьмем, к примеру, приложение J2ME, которое отслеживает местоположение GPS. Чтобы отправить местоположение пользователя, он может просто отправить строку текста, например:

    006.574438 | 045.453345 | 11022344843373

Что это значит?

    долгота | широта | отметка времени

Сериализация и десериализация данных ОЧЕНЬ проста с использованием StringTokenizer (эм, которого нет в J2ME, так что посмотрим позже!). И сервер может ответить просто ОК или он может воспользоваться возможностью обновить клиента с некоторой информацией о трафике:

 M4 | 4 | 6 | 20 | Авария,

что означает:

    дорога | от перекрестка | соединить | сколько минут задержки | причина

Большинство запросов к серверу действительно могут быть такими простыми, особенно когда они задействованы в приложениях J2ME, которые, как правило, сами по себе относительно просты.

Итак, вышеизложенное представляет несколько вопросов … Насколько безопасны данные и как вы узнаете, от кого приходит обновление? Ну, данные должны быть отправлены через SSL, чтобы обеспечить их безопасность, и если данные отправляются через аутентифицированный сеанс, то сервер знает, кто является вошедшим в систему пользователем. Но чтобы войти в систему и запустить сеанс, вам нужны две вещи, а именно: куки (для передачи идентификатора сеанса между клиентом и сервером) и некоторая форма аутентификации. Файлы cookie в J2ME не слишком просты в обращении, поскольку нет встроенного API для обработки их на высоком уровне. Вы можете установить cookie в заголовке запроса, но сложнее всего сохранить cookie из ответов. Я реализовал элементарную обработку файлов cookie, отправив ответ методу, который проверяет наличие любых заголовков файлов cookie и добавляет их в кэш, а также удаляет записи с истекшим сроком действия.Когда запрос отправляется, я вызываю метод, который добавляет все соответствующие файлы cookie в заголовок запроса. Я не реализовалRfC для файлов cookie и не обрабатывать различия между Cookie и Cookie2. На самом деле, я даже не зашёл так далеко, чтобы проверить путь куки, прежде чем отправить его на сервер, потому что в моей среде это даже не актуально. Надлежащая реализация cookie должна была бы делать эти вещи и даже больше, и, возможно, однажды такая библиотека будет существовать. Мне удалось найти это, которое относится к проекту JCookie sourceforge и J2ME, но проверить его сайт я не смог найти что-нибудь, что будет работать с J2ME.

HTTP-аутентификация. Первоначально я обрабатывал ее, добавляя заголовок запроса «авторизация» и применяя к нему строку базовой аутентификации в кодировке Base64. Это вызвало его собственные проблемы, потому что J2ME не имеет встроенного кодера Base64. К счастью, работает класс org.apache.common.codecs.binary.Base64 (который я взял из библиотеки Apache Codecs 1.3. Это зависит от следующих классов, которые есть в распространяемом JAR, а также J2ME-совместимых: BinaryDecoder, BinaryEncoder, Decoder, DecoderException , Encoder, EncoderException, все они находятся в пакете org.apache.commons.codec.

Я столкнулся с другой проблемой, когда хотел, чтобы мое веб-приложение для веб-сайта совпадало с веб-приложением для моих служб. А именно, для сервисов я хотел использовать базовую аутентификацию, а для веб-сайта я хотел использовать форму входа в систему. Спецификация сервлета не позволяет вам определять более одной конфигурации входа в систему для каждого веб-приложения! Поэтому класс J2ME, который я написал для доступа к сервисам (ServerCaller), должен был быть расширен, но это было несложно. Когда веб-приложение, использующее форму входа в систему, требует от вас аутентификации, оно просто возвращает форму входа в систему вместо ожидаемого ответа. Если вы анализируете ответ и проверяете, является ли это вашей формой для входа, а затем просто отправляете эту форму с вашими именем пользователя и паролем, сервер затем регистрирует вас и возвращает код 302, говорящий о том, чтобы вы снова вызывали исходную страницу. Предполагая, что вы ввели правильные учетные данные,все работает Так что мой класс возвращался в себя, если он обнаружил форму входа в систему, и этого было достаточно, чтобы заставить его работать прозрачно.

Следующая проблема заключалась в разборе ответов для их десериализации. Для этого я создал очень простой StringTokenizer, поскольку ни CLDC, ни MIDP не дают его вам ? Реализация приведена ниже.

Кодирование и декодирование URL также важно, поскольку параметры запроса (для GET это строка запроса, для POST тело запроса). К счастью, есть несколько вариантов. У Catalina есть один в своем пакете утилит, который почти работает для J2ME. Поэтому я быстро исправил его, чтобы удалить зависимость от java.util.BitSet, который также не существует в J2ME. .

последняя проблема , я связан с отрывов от просьбы HttpConnection, которые вы можете прочитать больше о здесь .

Итак, наконец, немного кода! Магический класс — это ServerCaller, который вы можете скачать ниже. Это абстрактно, и чтобы использовать его, вам просто нужно расширить его, например:

package uk.co.maxant.cam.c;

import java.util.Vector;

import uk.co.maxant.cam.m.MasterData;
import uk.co.maxant.j2me.core.CredentialsProvider;
import uk.co.maxant.j2me.core.ServerCaller;
import uk.co.maxant.j2me.core.StringTokenizer;

/**
* calls the server to get master data like roles and the version of the latest available download.
*/
public abstract class MasterDataGetter extends ServerCaller implements ServerCaller.Listener {

private ExceptionHandler eh;

public MasterDataGetter(CredentialsProvider credentialsProvider, ExceptionHandler eh) {
super(credentialsProvider, Constants.getBaseUrl(), "ota/getMasterData.jsp");
this.eh = eh;
setListener(this);
}

public void callServer(){
properties.clear();
properties.put("installUid", String.valueOf(InstallHelper.getInstallationUid(eh)));

doCallAsync();
}

protected void handleSuccess(String str) {
StringTokenizer lines = new StringTokenizer(str, "|");
String version = lines.nextToken().trim();
String sessionId = lines.nextToken().trim();
String screenName = lines.nextToken().trim();
String roles = lines.nextToken().trim();
StringTokenizer rs = new StringTokenizer(roles, ",");
Vector rolesv = new Vector();
while(rs.hasMoreTokens()){
rolesv.addElement(rs.nextToken());
}

String s = lines.nextToken().trim();
boolean isSendDebugLog = s.equalsIgnoreCase("true");

MasterData masterData = new MasterData(version, rolesv, sessionId, screenName, isSendDebugLog);
onSuccess(masterData );
}

}

Пример JSP для сервера: 

<%@page import="uk.co.maxant.cam.web.VersionHelper"%>
<%@page import="org.apache.log4j.Logger"%>
<%@page import="java.io.FileInputStream"%>
<%@page import="java.util.Properties"%>
<%@page import="java.io.RandomAccessFile"%>
<%@page import="java.io.File"%>
<%@page import="uk.co.maxant.util.ServiceLocator"%>
<%@page import="uk.co.maxant.cam.services.OrchestrationService"%>
<%@page import="uk.co.maxant.cam.data.Party"%>
<%@page import="java.util.List"%>
<%@page import="uk.co.maxant.cam.services.OrchestrationService.MasterData"%>


<%
Logger log = Logger.getLogger(this.getClass().getCanonicalName());
String user = "";
String roles = "";

//either they are logged in, or we can check their login against the authentication header, since its ALWAYS sent
try{
//we know who they are. if the installUid is known, lets add it to their profile!
String installUid = request.getParameter("installUid");
if(installUid != null && installUid.equals("null")) installUid = null;

String auth = request.getHeader("authorization");

OrchestrationService os = ServiceLocator.getInstance().getOrchestrationService();
MasterData md = os.getMasterData(auth, installUid);
if(md.party != null) user = md.party.getScreenname();
for(String role : md.roles){
roles += role + ",";
}
String version = VersionHelper.getVersionFromJad(request);

boolean sendDebugLog = true; //change to false if we get swamped. but really, we shouldnt ever get this stuff.

%>OK
<%=version%>|
<%=session.getId()%>|
<%=user%>|
<%=roles%>|
<%=sendDebugLog%>|
<%
}catch(Exception e){
log.warn("failed to read master data", e);
%>ERROR
<%=e.getMessage() %>
<%
}
%>

В результате получается библиотека maxant J2ME, которая работает на CLDC 1.1 и MIDP 2.0 и может быть загружена здесь . Он включает в себя классы для всего перечисленного и многое другое и выпускается под лицензией LGPL:

  • URLEncoder — от Apache Catalina
  • Base64 — от кодека Apache Commons
  • CredentialsProvider — интерфейс, который ServerCaller использует для получения учетных данных сервера для входа
  • Formatter — форматирует, например, десятичные дроби
  • GAUploader — загружает событие в Google Analytics
  • Математика — некоторые математические вещи, которых нет в CLDC
  • Точка — двухмерная точка на плоскости
  • ServerCaller — API высокого уровня для вызова сервера, как описано выше
  • StringTokenizer — простой и очень похожий на J2SE
  • URL — инкапсуляция протокола, домена, порта и страницы
  • ImageHelper — код, взятый из Интернета для масштабирования изображений
  • DirectoryChooser — интерфейс для выбора локального каталога на устройстве
  • FileChooser — интерфейс для выбора локального файла на устройстве

Чтобы использовать библиотеку, добавьте ее в папку «lib» вашего проекта J2ME, затем убедитесь, что она находится на пути сборки, и в Eclipse Pulsar вам также необходимо установить флажок в пути сборки на последней вкладке, чтобы убедиться, что классы в JAR экспортируются в ваш JAR-файл JAD.

С http://blog.maxant.co.uk