Приложения Java: взаимодействие аплетов с Web-сервером
А.В. Фролов, Г.В. Фролов
В этой статье мы расскажем об интересной возможности взаимодействия между
аплетами Java и расширениями сервера Web, такими, как программы CGI или
приложения ISAPI. Вы научитесь создавать аплеты Java, способные получать
произвольные файлы, расположенные на сервере Web, передавать расширениям сервера
Web любые данные и принимать от них результаты обработки.
Расширения CGI и ISAPI часто применяются в активных серверах Web для
организации диалогового режима работы. Например, заполнив форму, размещенную в
документе HTML, пользователь может отправить запрос в базу данных, реализованную
с применением указанных выше расширений. Обработав запрос, расширение CGI или
ISAPI динамически формирует документ HTML с результатами обработки запроса и
передает его пользователю.
Этот нехитрый сценарий предполагает, что вся основная работа по выполнению
запроса и представлению его результатов в удобном для просмотра виде выполняется
на Web-сервере. И если результаты запроса необходимо представить в графическом
виде, на сервере Web создается соответствующий файл GIF или JPEG, а в посылаемом
пользователю документе HTML размещается ссылка на него.
Однако на передачу графического изображения может уйти немало времени. Было
бы лучше, если бы пользователю посылались только данные результатов обработки в
виде массива цифр, а построение графических диаграмм выполнялось локальными
средствами, например, с помощью аплетов Java или элементов управления ActiveX.
Распределив обработку данных между сервером Web и удаленной рабочей станцией, вы
можете заметно улучшить время реакции приложения. В результате пользователям
будет намного приятнее работать с вашим сервером.
Классы Java для работы в сети
Язык Java изначально ориентирован для работы с протоколом TCP/IP, поэтому в
составе его библиотек есть мощные и удобные классы, предназначенные для создания
сетевых приложений. Классы InetAddress, URL и URLConnection пригодятся вам для
организации взаимодействия между аплетами Java и расширениями сервера Web.
Класс InetAddress
Для работы с адресами IP предназначен класс InetAddress. Объект этого класса
может быть создан для локального или удаленного узлов, причем в качестве
исходной информации допустимо указание адреса IP как в виде четырех чисел
(например, 155.100.100.5), так и в виде доменного имени (например,
www.microsoft.com).
В следующем примере мы создаем объект класса InetAddress для локального
узла: InetAddress iaddrLocal;
try
{
iaddrLocal = InetAddress.getLocalHost();
}
catch (Exception ioe)
{
. . .
}
Обратите внимание на то, что метод getLocalHost - статический и вызывается
без создания объекта класса InetAddress.
Вызов метода getLocalHost нужно выполнять с обработкой исключений, для чего
служит конструкция try-catch. Если для данного узла не найден адрес IP, при
вызове метода может возникать исключение UnknownHostException.
Создание объекта класса InetAddress для удаленного узла выполняется методами
getByName и getAllByName. Эти методы так же, как и метод getLocalHost, создают
исключение UnknownHostException.
С помощью метода getByName можно образовать объект класса InetAddress для
узла, заданного в виде текстовой строки доменного имени или числового адреса IP.
Например: InetAddress iaddrMicrosoft;
try
{
iaddrMicrosoft = InetAddress.getByName("www.microsoft.com");
}
catch (UnknownHostException uhe)
{
. . .
}
Метод getAllByName позволяет создать массив объектов класса InetAddress для
всех адресов IP, относящихся к указанному доменному имени: InetAddress iaddrAllMicrosoft[];
try
{
iaddrAllMicrosoft = InetAddress.getAllByName("www.microsoft.com");
}
catch (UnknownHostException uhe)
{
. . .
}
В классе InetAddress есть еще несколько методов, которые могут быть полезны.
Метод getHostName возвращает строку доменного имени, соответствующего объекту
класса InetAddress. Метод equals предназначен для сравнения двух объектов этого
класса. Если передаваемый ему аргумент не равен значению null и сравниваемые
объекты соответствуют одному и тому же адресу IP, метод equals возвращает
значение true. В противном случае возвращается false. И наконец, с помощью
метода toString вы можете получить текстовую строку, соответствующую адресу IP
объекта класса InetAddress.
Класс URL
Этот класс позволяет организовать работу с ресурсами, расположенными на
сервере Web, такими, как документы HTML, произвольные файлы, расширения CGI и
ISAPI. Как известно, адресация ресурсов в сети Internet выполняется с помощью
универсального указателя ресурсов URL (Universal Resource Locator). В общем виде
в обозначении этого адреса присутствуют четыре основных компонента - название
протокола, доменный адрес узла, номер порта и путь к файлу ресурса: [protocol://]host[:port][path]
Конструкторы класса URL. Для того чтобы работать с ресурсом Internet
средствами Java, следует создать для него объект класса URL. Такую задачу можно
решить с помощью одного из четырех конструкторов, предусмотренных в этом
классе:
• public URL(String szURLSpec) - создает объект класса URL из текстовой
строки;
• public URL(String szProtocol, String szHost, int nPort, String szFile) -
создает объект класса URL при раздельном указании компонентов;
• public URL(String szProtocol, String szHost, String szFile) - то же;
• public URL(URL szContext, String szURLSpec) - то же, но при задании
контекста адреса szContext и строки адреса URL szURLSpec. В строке контекста
обычно задают компоненты адреса URL, отсутствующие в строке szURLSpec, такие,
как протокол, доменное имя узла, путь к файлу или номер порта.
Заметим, что исходная информация для конструкторов класса URL может быть
получена с помощью методов описанного выше класса InetAddress. Так, с помощью
метода getHostName можно получить для узла строку доменного имени по его адресу
IP.
Все конструкторы класса URL в случае возникновения ошибок создают исключение
MalformedURLException. Поэтому вызов конструкторов необходимо выполнять с учетом
этого обстоятельства, например: URL urlMicrosoft;
try
{
urlMicrosoft = URL("www.microsoft.com");
}
catch (MalformedURLException mue)
{
. . .
}
Данное исключение возникает, если в параметрах конструктора указан
неизвестный протокол или протокол не указан вообще (только для четвертого
конструктора).
Основные методы класса URL:
• public final InputStream openStream() - создает входной поток для чтения
файла ресурса, связанного с объектом класса URL. Когда поток создан, для чтения
данных из него должен использоваться метод read, определенный в классе
InputStream. Этот способ пригоден для чтения как двоичных, так и текстовых
файлов, расположенных в каталогах сервера Web;
• public final Object getContent() - отвечает за получение содержимого
сетевого ресурса, для которого создан объект URL. Этот метод очень удобен, если
нужно прочитать текстовый файл, но, к сожалению, непригоден для получения
документов HTML. Причина заключается в том, что для данного ресурса не определен
обработчик содержимого, предназначенный для создания объекта. В результате метод
getContent способен создать объект только из текстового файла. Но эту проблему
можно решить, применив вместо метода getContent комбинацию методов openStream
(из класса URL) и read (из класса InputStream);
• public String getHost() - определяет доменное имя узла, соответствующего
объекту URL;
• public String getFile() - отвечает за получение информации о файле,
связанном с данным объектом URL;
• public int getPort() - определяет номер порта, посредством которого
выполняется связь с объектом URL;
• public String getProtocol() - определяет протокол, примененный для
установки соединения с ресурсом, заданным объектом URL;
• public String getRef() - возвращает текстовую строку ссылки на ресурс,
соответствующий объекту URL;
• public int hashCode() - возвращает хэш-код объекта URL;
• public boolean sameFile(URL other) - определяет, ссылаются ли два объекта
класса URL на один и тот же ресурс: если да, возвращает значение true, если нет
- false;
• public boolean equals(Object obj) - устанавливает идентичность адресов URL,
заданных двумя объектами класса URL: если да, возвращает значение true, если нет
- false;
• public String toExternalForm() - возвращает текстовую строку внешнего
представления адреса URL, определенного данным объектом класса URL;
• public String toString() - возвращает текстовую строку, представляющую
данный объект класса URL;
• public URLConnection openConnection() - создает канал между приложением и
сетевым ресурсом, представленным объектом класса URL.
Как мы уже говорили, для того чтобы получить содержимое файла, расположенного
в каталоге сервера Web, вы можете создать поток методом openStream, или, если
речь идет о текстовых файлах, воспользоваться методом getContent. Метод
openConnection предоставляет еще одну возможность. При его использовании аплет
может создать канал как объект класса URLConnection, а затем образовать для
этого канала входной поток. Для этого следует воспользоваться методом
getInputStream, определенным в классе URLConnection. Такая методика позволяет
перед созданием потока определить или установить некоторые характеристики
канала, например задать кэширование.
Однако самое главное то, что с помощью метода openConnection можно создать
канал, предназначенный для обмена данными между аплетом и программой CGI.
Загрузка файлов из каталогов сервера Web
Прежде чем научиться принимать и обрабатывать данные от программы CGI, решим
более простую задачу - загрузим файлы из каталогов сервера Web. Пользуясь
классом URL, любой аплет может легко это сделать. Заметим, однако, что для
успешной загрузки необходимо выполнение двух условий:
• файл должен находиться на том же сервере, откуда был запущен аплет;
• администратор сервера Web должен предоставить доступ на чтение к
загружаемому файлу.
Первое из этих ограничений накладывается на аплет из соображений
безопасности, второе касается любых способов получения файлов с сервера Web.
Ниже мы привели исходные тексты аплета TextEdit. В окне, которое он выводит,
есть однострочное поле для ввода адреса URL, кнопка с надписью "Получить файл" и
многострочное поле для редактирования.
В нашем примере в аплет загружен исходный текст HTML-документа с аплетом
TextEdit.
Для правильной работы аплета важно, чтобы HTML-документ с аплетом был
загружен с сервера Web с применением протокола HTTP, а не с локального диска
компьютера. В противном случае при попытке чтения файла возникнет исключение
SecurityException. Это же исключение появится и при попытке получить файл не с
того сервера, с которого был загружен аплет. Исходный текст аплета TextEdit
приведен в листинге 1.
Листинг 1. Загрузка файлов с сервера Web // =======================================
// (C) Фролов А.В., 1998
// =======================================
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class TextEdit extends Applet
{
// tl - ссылка на объект класса Label
Label tl;
// txtURLAddress - ссылка на однострочное поле редактирования текста
// используется для ввода адреса ресурса URL
TextField txtURLAddress;
// txt - ссылка на многострочное поле редактирования класса
// TextArea, где после
// загрузки будет отображено содержимое файла
// TextArea txt;
// btnGetText - ссылка на кнопку класса Button
Button btnGetText;
String str;
byte buf[] = new byte[100];
public void init()
// добавляем однострочное поле для ввода адреса URL,
// кнопку, инициирующую процесс загрузки файла,
// и многострочное поле редактирования
{
tl = new Label("Введите адрес URL:");
add(tl);
txtURLAddress = new TextField("http://", 50);
add(txtURLAddress);
txt = new TextArea("", 20, 70);
btnGetText = new Button("Получить файл");
// добавляем поля редактирования
add(btnGetText);
add(txt);
// устанавливаем желтый цвет фона для окна аплета
setBackground(Color.yellow);
}
public boolean action(Event evt, Object obj)
// загрузка файла ресурса, заданного своим адресом URL,
// и отображение его в многострочном поле редактирования
// txt.
{
Button btn;
// проверяем, вызвано ли это событие нашей кнопкой
if(evt.target instanceof Button)
{
btn = (Button)evt.target;
if(evt.target.equals(btnGetText))
{
URL u;
try
{
// создаем объект класса URL
u = new URL(txtURLAddress.getText());
// открываем входной поток для записи ссылки в переменную u
InputStream is = u.openStream();
// читаем данные из потока
while(true)
{
// метод read возвращает количество прочитанных байт данных
// или -1, если был достигнут конец потока
int nReaded = is.read(buf);
if(nReaded == -1)
break;
str = new String(buf, 0);
txt.appendText(str.substring(0, nReaded));
}
}
// текст описания исключения записывается в строку состояния
// браузера
catch(Exception ioe)
{
showStatus(ioe.toString());
}
}
else
return false;
return true;
}
return false;
}
public void paint(Graphics g)
{
// определяем размеры окна
Dimension dimAppWndDimension = size();
g.setColor(Color.black);
// рисуем прямоугольную рамку по периметру
g.drawRect(0, 0,
dimAppWndDimension.width - 1,
dimAppWndDimension.height - 1);
}
}
Рассмотрим более подробно процесс получения файла. Прочитанный блок данных
записывается в рабочий массив buf. Преобразуем его в строку класса String,
вызвав соответствующий конструктор. Во втором параметре передаем этому
конструктору нулевое значение, которое записывается в старшие байты формируемой
строки UNICODE. Этот способ, к сожалению, подходит только для работы с
латинскими символами. Что же касается кириллицы, то для нее содержимое старшего
байта должно быть равно 4. Заметим также, что кодировка младшего байта
кириллических символов UNICODE не соответствует кодировке символов ANSI,
привычной для программистов, создающих приложения Windows.
Получение данных от программы CGI и их обработка
После TextEdit напишем второй аплет с названием CGICall, который будет решать
более сложную задачу: вызвать расширение сервера Web, сделанное нами в виде
программы CGI, получить от нее десять случайных чисел и отобразить эти числа в
виде цветной столбчатой диаграммы.
Сразу после запуска аплета в его окно выводится сообщение "Click me!". Если
сделать щелчок левой клавишей мыши внутри окна, аплет запустит на сервере Web
программу CGI, получит от нее случайные числа и нарисует диаграмму. Каждый раз,
когда вы будете щелкать мышью на окне аплета, он будет обращаться к программе
CGI за новыми случайными числами. Заметим, что цвета для отображения столбцов
диаграммы выбираются аплетом также случайно. Исходный текст аплета CGICall
представлен в листинге 2. Текст документа HTML, в который встроен аплет,
приведен в листинге 3.
Листинг 2. Получение данных от сервера Web// =======================================
// (C) Фролов А.В., 1998
// =======================================
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class CGICall extends Applet
{
// u - ссылка на адрес URL программы CGI
URL u;
// c - ссылка на объект класса URLConnection,
// используемый как канал передачи данных при
// взаимодействии аплета и программы CGI
URLConnection c;
// is - ссылка на входной поток класса DataInputStream,
// по которому аплет получает данные от программы CGI
DataInputStream is;
// str - рабочее поле для хранения текстовой строки
// со случайными числами, принятой от программы CGI
String str = "";
String val[] = new String[11];
public void init()
{
for(int i=0; i < 10; i++)
val[i] = "0";
// вызываем метод repaint для перерисовки окна аплета
// до обмена данными с программой CGI; при перерисовке
// метод paint выводит в окне сообщение "Click me!"
repaint();
}
public void paint(Graphics g)
{
Integer iInteger;
int maxWidth = 0;
int curWidth;
// устанавливаем желтый цвет фона окна, определяем
// размеры окна и обводим его черной рамкой
setBackground(Color.yellow);
Dimension dimAppWndDimension = size();
g.setColor(Color.black);
g.drawRect(0, 0,
dimAppWndDimension.width - 1,
dimAppWndDimension.height - 1);
// для преобразования строк к типу int
// создаем объект класса Integer
iInteger = new Integer(val[0]);
maxWidth = iInteger.intValue();
if(maxWidth == 0)
g.drawString("Click me!", 10, 100);
else
{
// выводим строку, полученную от программы CGI, в исходном
// виде
g.drawString(str, 10, 20);
for(int i=1; i < 11; i++)
{
// выбираем случайным образом компоненты цвета
// для изображения очередного столбца диаграммы
int rColor = (int)(255 * Math.random());
int gColor = (int)(255 * Math.random());
int bColor = (int)(255 * Math.random());
g.setColor(new Color(rColor, gColor, bColor));
// извлекаем случайное значение, определяющее ширину столбца
// диаграммы
iInteger = new Integer(val[i]);
curWidth = iInteger.intValue() + 1;
g.fillRect(1, 10 + 20 * i,
(300 * curWidth) / maxWidth, 20);
// справа от столбцов диаграммы отображаем их ширину
// в численном виде
g.drawString(val[i],
10 + (300 * curWidth) / maxWidth, 24 + 20 * i);
}
}
}
public boolean mouseDown(Event evt, int x, int y)
{
try
{
// создаем объект u класса URL для загрузочного файла
// программы CGI
u = new URL("http://frolov/scripts/random.exe"");
c = u.openConnection();
// после соединения создаем поток для форматированного
// ввода-вывода
// данных от программы CGI
is = new DataInputStream(c.getInputStream());
// читаем строку из входного форматированного потока
str = is.readLine();
is.close();
// создаем объект класса StringTokenizer, передавая
// конструктору через первый
// параметр ссылку на разбираемую строку, а через второй -
// строку разделителей
StringTokenizer st = new StringTokenizer(str, ",rn");
int i = 0;
while(st.hasMoreElements())
{
// разбираем строку и записываем токены в массив val
val[i] = (String)st.nextElement();
i++;
}
repaint();
}
catch (Exception ioe)
{
showStatus(ioe.toString());
}
return true;
}
}
Листинг 3. Текст документа HTML, в который встроен аплет<html>
<head>
<title>CGICall</title>
</head>
<body>
<hr>
<applet
code=CGICall.class
name=CGICall
width=360
height=240 >
</applet>
<hr>
<a href="1821/CGICall.java">The source.</a>
</body>
</html>
Щелчком левой кнопки мыши в окне аплета управление передается методу
mouseDown. Именно этот метод запускает программу CGI и получает от нее данные.
Так как в процессе сеанса связи с Web-сервером возможно возникновение
исключений, мы предусмотрели внутри метода mouseDown конструкцию try-catch.
В нашем случае программа CGI возвращает аплету одну текстовую строку, в
которой имеется десять цифр, разделенных запятой. Первая цифра - это
максимальное значение для следующих десяти случайных цифр. Метод mouseDown
приступает к разбору этой строки. Проще всего решить такую задачу с применением
специально предназначенного для этого класса StringTokenizer.
Когда все токены будут записаны в массив, цикл завершит свою работу. Теперь
останется только перерисовать окно аплета, отобразив в нем значения из массива
val в виде столбчатой диаграммы. Эта задача решается с помощью метода repaint.
Перерисовкой окна аплета занимается метод paint.
Затем метод paint проверяет нулевой элемент массива val. Сразу после
инициализации в этот элемент метод init записывает нулевое значение. После
получения данных от программы CGI вместо него будет находиться максимально
возможное значение для случайных чисел, хранящихся в элементах с номерами от 1
до 10.
В листинге 4 приведен исходный текст программы CGI, предназначенной для
совместной работы с аплетом CGICall.
Листинг 4. Программа CGI, предназначенная для совместной работы с аплетом
CGICall#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void main(int argc, char *argv[])
{
int I;
// инициализируем генератор случайных чисел
srand((unsigned)time(NULL));
// записываем в стандартный поток заголовок HTTP:
// этот заголовок описывает документ как простой текст
printf("Content-type: text/plainrnrn");
// посылаем браузеру максимальное значение RAND_MAX
printf("%d", RAND_MAX);
// генерируем десять случайных чисел и записываем их
// в выходной поток
// через запятую
for(i = 0; i < 10; i++)
printf(",%d", rand());
}
В предыдущем примере (листинг 2) обмен данными с программой CGI выполнялся в
методе mouseDown, который получал управление после щелчка мышью в окне аплета.
Заметим, что, пока этот метод не вернет управление, работа с аплетом будет
заблокирована.
В случае аплета CGICall такое обстоятельство не играет существенной роли,
однако в других приложениях блокировка на время обмена данными с расширением
сервера Web может оказаться нежелательной. Поскольку передача данных по сети
Internet обычно происходит с невысокой скоростью, пользователь вынужден долго
ждать завершения этого процесса.
Между тем есть простой способ избежать блокировки на время обмена данными -
достаточно предусмотреть выполнение этой процедуры в отдельном потоке. Следующий
аплет - CGICallMulti - внешне похож на аплет CGICall и решает те же задачи, но в
нем предусмотрен многопоточный режим работы (листинг 5).
Листинг 5. Многопоточный режим обмена данными с сервером Web// =======================================
// (C) Фролов А.В., 1998
// =======================================
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class CGICallMulti extends Applet implements Runnable
{
Thread thTransactionThread = null;
URL u;
URLConnection c;
DataInputStream is;
String str = "";
String val[] = new String[11];
public void init()
{
for(int i=0; i < 10; i++)
val[i] = "0";
repaint();
}
public void stop()
{
if(thTransactionThread != null)
{
thTransactionThread.stop();
thTransactionThread = null;
}
}
void startTransaction()
// создает новый поток и запускает его на выполнение
{
thTransactionThread = new Thread(this);
thTransactionThread.start();
}
public synchronized void run()
{
try
{
u = new URL("http://frolov/scripts/random.exe");
c = u.openConnection();
is = new DataInputStream(c.getInputStream());
str = is.readLine();
is.close();
StringTokenizer st = new StringTokenizer(str,
",rn");
int i = 0;
while(st.hasMoreElements())
{
val[i] = (String)st.nextElement();
i++;
}
repaint();
}
catch (Exception ioe)
{
showStatus(ioe.toString());
stop();
}
}
public void paint(Graphics g)
{
// исходный текст этого метода
// такой же, как представленный в листинге 2
. . .
}
public boolean mouseDown(Event evt, int x, int y)
{
startTransaction();
return true;
}
}
Щелчок мышью в окне аплета заставляет метод mouseDown вызывать определенный в
нем метод startTransaction.
Главный класс аплета CGICallMulti реализует интерфейс Runnable, поэтому мы
возложили на метод run те функции по обмену данными с программой CGI, которые в
предыдущем примере (листинг 4) выполнял метод mouseDown. Именно run будет
взаимодействовать с расширением сервера Web одновременно с выполнением основного
кода аплета. Поэтому во время передачи данных по сети Internet аплет станет
функционировать нормально.
Мы рассмотрели способы получения данных от CGI-программы, однако на практике
чаще необходим обмен информацией между аплетом и сервером.
Обмен текстовыми строками с расширением сервера Web
Попробуем решить несложную на первый взгляд задачу: создадим аплет Java,
который будет передавать произвольные текстовые строки программе CGI,
возвращающей эти строки обратно аплету. В окне нашего аплета coding (листинг 6)
два поля для ввода текста и одна кнопка с надписью "Передать строку".
Если ввести строку в верхнем поле окна и нажать кнопку, аплет запустит поток.
Этот поток обращается к программе CGI, передает ей введенную строку, получает
ответ, а затем отображает его в нижнем поле окна аплета. Его исходный текст
представлен в листинге 6.
Листинг 6. Передача строки программе CGI и прием от нее ответной строки// =======================================
// (C) Фролов А.В., 1998
// =======================================
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class coding extends Applet implements Runnable
{
private Thread m_coding = null;
TextField txtOriginal;
TextField txtResult;
Button btnGetText;
public void init()
{
txtOriginal = new TextField("<Исходная строка>", 40);
add(txtOriginal);
txtResult = new TextField("<Ответ>", 40);
add(txtResult);
btnGetText = new Button("Передать строку");
add(btnGetText);
setBackground(Color.yellow);
}
public void paint(Graphics g)
{
setBackground(Color.yellow);
Dimension dimAppWndDimension = size();
g.setColor(Color.black);
g.drawRect(0, 0,
dimAppWndDimension.width - 1,
dimAppWndDimension.height - 1);
}
public boolean action(Event evt, Object obj)
{
Button btn;
if(evt.target instanceof Button)
{
btn = (Button)evt.target;
if(evt.target.equals(btnGetText))
{
startTransaction();
}
else
return false;
return true;
}
return false;
}
void startTransaction()
{
m_coding = new Thread(this);
m_coding.start();
}
public void stop()
{
if (m_coding != null)
{
m_coding.stop();
m_coding = null;
}
}
public String Str2Hex(String szSrc)
{
char chSrc[] = szSrc.toCharArray();
char chDst[] = new char[chSrc.length * 4];
int i, j;
char chXlat[] = "0123456789ABCDEF".toCharArray();
for(i=0, j=0; i < chSrc.length; i++, j++)
{
chDst[j] = chXlat[(int)((chSrc[i] & 0xf000) >> 12)];
j++;
chDst[j] = chXlat[(int)((chSrc[i] & 0x0f00) >> 8)];
j++;
chDst[j] = chXlat[(int)((chSrc[i] & 0x00f0) >> 4)];
j++;
chDst[j] = chXlat[(int)(chSrc[i] & 0x000f)];
}
return new String(chDst);
}
public String Heх2Str(String szSrc)
{
byte hi, lo;
int i, j;
char chSrc[] = szSrc.toCharArray();
char chDst[] = new char[chSrc.length / 4];
for(i=0, j=0; i < chSrc.length; j++, i += 4)
{
hi = bCombine(chSrc[i], chSrc[i+1]);
lo = bCombine(chSrc[i+2], chSrc[i+3]);
chDst[j] = (char)(((int)hi << 8) | (int)lo);
}
return new String(chDst);
}
public byte bCombine(char chHi, char chLo)
{
byte hi = (byte)(chHi & 0xff);
byte lo = (byte)(chLo & 0xff);
if(hi >= 0x41)
hi = (byte)(hi - 0x41 + 10);
else
hi -= 0x30;
if(lo >= 0x41)
lo = (byte)(lo - 0x41 + 10);
else
lo -= 0x30;
return (byte)((hi << 4) | lo);
}
public void run()
{
URL u;
URLConnection c;
PrintStream ps;
DataInputStream is;
try
{
// исходная строка, введенная в верхнем поле, извлекается
// методом getText и
// записывается в переменную szSourceStr:
String szSourceStr = txtOriginal.getText();
String szReceived;
// создаем объект класса URL для программы CGI и открываем
// канал
// для обмена данными, вызывая метод openConnection:
u = new URL("http://frolov/scripts/coder.exe");
c = u.openConnection();
// открываем для него выходной поток данных класса
// OutputStream,
// а на его базе - поток для форматированного вывода
// PrintStream, который
// можно использовать для передачи текстовых строк программе
// CGI,
// получающей их из стандартного потока ввода
ps = new PrintStream(c.getOutputStream());
ps.println(Str2Hex(szSourceStr));
ps.close();
is = new DataInputStream(c.getInputStream());
// введенная в верхнем поле окна аплета строка преобразуется
// из гексадецимального формата в тип String
szReceived = Heх2Str(is.readLine());
// закрываем входной поток (программа CGI на Web-сервере
// завершается),
// и записываем перекодированную строку в нижнее поле окна
// аплета
is.close();
txtResult.setText(szReceived);
repaint();
}
catch (Exception ioe)
{
showStatus(ioe.toString());
stop();
}
}
}
Исходный текст документа HTML, в который встроен аплет coding (листинг 6), вы
найдете в листинге 7.
Листинг 7. Исходный текст документа HTML, в который встроен аплет
coding<html>
<head>
<title>coding</title>
</head>
<body>
<hr>
<applet
code=coding.class
name=coding
width=320
height=85 >
</applet>
<hr>
<a href="1821/coding.java">The source.</a>
</body>
</html>
Вся работа по обмену данными с программой CGI выполняется аплетом в методе
run, который действует одновременно с другим кодом аплета.
Исходный текст программы CGI, работающей вместе с нашим аплетом (листинг 6),
представлен в листинге 8.
Листинг 8. Исходный текст программы CGI, работающей вместе с аплетом
coding#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char *argv[])
{
int nInDatasize;
char *szMethod;
char szBuf[2000];
szMethod = getenv("REQUEST_METHOD");
if(!strcmp(szMethod, "POST"));
{
// определяем, сколько байт данных необходимо
// считать из входного потока
nInDatasize = atoi(getenv("CONTENT_LENGTH"));
fread(szBuf, nInDatasize, 1, stdin);
szBuf[nInDatasize] = " ";
// текстовая строка отправляется обратно аплету, для чего
// записываем ее в выходной поток, предваряя соответствующим
// заголовком
printf("Content-type: text/plainrnrn");
printf("%s", szBuf);
}
}
При необходимости можно преобразовать принятые данные из гексадецимального
формата в формат UNICODE и обработать их.
* * *
Мы рассмотрели минимальный набор приемов, при помощи которых можно
реализовать сценарий взаимодействия пользователя и Web-сервера, описанный в
начале статьи. Остается лишь добавить творческую мысль дизайнера и программиста,
чтобы сделать посещение сервера не только полезным, но и приятным и
запоминающимся.
ОБ АВТОРАХ: Александр Вячеславович Фролов, Григорий Вячеславович Фролов
- авторы серий книг "Библиотека системного программиста" и "Персональный
компьютер. Шаг за шагом", e-mail: frolov@glas.apc.org, Web: http://www.glasnet.ru/~frolov
Использование гексадецимального формата
Как известно, строки класса String приложений Java хранятся в формате
UNICODE. Этот формат предполагает, что каждый символ занимает в памяти два
байта. Если нам нужно передать строку UNICODE программе CGI через поток,
созданный аплетом, мы вынуждены выполнять преобразование исходной строки в поток
символов ANSI. Это связано с тем, что программа CGI принимает через стандартный
входной поток только символьную информацию. Более того, программа CGI,
запущенная в среде Microsoft Internet Information Server версии 3.0,
воспринимает только символы из первой половины кодовой таблицы ANSI. Последнее
обстоятельство затрудняет передачу строк, содержащих символы кириллицы.
Применение гексадецимального формата как промежуточного для передачи
информации между аплетом и программой CGI позволяет преодолеть указанные
трудности. Напомним, что
в гексадецимальном формате каждый двоичный байт кодируется двумя символами,
соответствующими старшей и младшей тетраде байта. Например, число 0xA8 в
гексадецимальном формате представляется в виде символов A и 8. В результате
любая двоичная информация может быть представлена как поток символов из первой
половины таблицы ANSI.
Cведения о формате UNICODE и функциях Win32, предназначенных для работы со
строками и символами UNICODE, вы найдете в документации к Win32 SDK или в
справочной системе Microsoft Visual C++.
|