Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Создание Web-приложений: Практическое руководство

© Александр Фролов, Григорий Фролов
М.: Русская Редакция, 2001, 1040 стр.

12. Создание серверных элементов управления ActiveX

12. Создание серверных элементов управления ActiveX.. 1

Первый проект элемента ActiveX.. 2

Создание проекта. 2

Добавление объекта. 4

Определение нового свойства. 6

Редактирование исходного текста свойства. 8

Подготовка страницы ASP.. 9

Определения методов элемента. 11

Автоматическая обработка кредитных карточек.. 12

Библиотека для имитации интерфейса. 12

Тестовая программа для вызова имитатора интерфейса. 13

Элемент управления CreditCard. 14

Вызов элемента управления CreditCard. 16

Современные почтовые протоколы... 18

Протокол SMTP.. 19

Протокол POP3. 21

Протокол IMAP.. 24

Внутренняя структура электронного сообщения.. 24

Заголовок сообщения. 24

Тело сообщения. 26

Наборы символов и кодировка сообщения. 27

Присоединенный файл. 28

Отправка почтового сообщения из сценария ASP. 29

Элемент управления MTASend. 29

Перекодировка текстовых строк.. 35

Преобразование числа в сумму прописью... 38

 

В этой главе мы кратко рассмотрим методику создания собственных элементов управления ActiveX для расширения объектной модели ASP. Мы будем назвать их серверными элементами управления ActiveX.

В отличие от клиентских элементов управления ActiveX, загружаемых браузером для работы на компьютере посетителя, серверные элементы управления исполняются на сервере Web. Они не вызывают потенциальных проблем с безопасностью данных, возникающих при работе с клиентскими элементами ActiveX, а также проблем с совместимостью браузеров, характерных для технологии ActiveX.

Зачем Вам создавать собственный серверный элемент ActiveX?

Серверные сценарии, размещенные в страницах ASP, ограничены в своих возможностях рамками объектной модели ASP. Хотя, как Вы уже убедились, эта модель предоставляет прекрасные возможности для проектирования интерфейса пользователя и для обращения к базам данных, она имеет ограничения в плане вызова функций программного интерфейса Win32, а также функций, расположенных в библиотеках динамической загрузки DLL.

Другой недостаток, возникающий из-за применения в серверных сценариях ASP интерпретируемых языков программирования JScript и VBScript — невысокая производительность. При формировании элементов пользовательского интерфейса этот недостаток не существенен, однако, если Ваше приложение предназначено для выполнения интенсивной обработки больших объемов данных, вопросы производительности становятся важны.

Еще одна проблема, связанная с использованием серверных сценариев, имеет отношение к защите интеллектуальной собственности разработчика. Дело в том, что исходные тексты серверных и клиентских сценариев могут быть легко проанализированы администратором сервера Web, что не всегда желательно. Реализация бизнес-приложения в виде исполнимых модулей значительно затруднит работу злоумышленника, решившего «вскрыть» алгоритмы работы Вашего приложения.

Конечно, все эти проблемы легко решаются при использовании таких расширений сервера Web, как программы CGI или ISAPI, рассмотренные в предыдущей главе. Однако этот подход тоже имеет свои недостатки. Наиболее существенные из них — это невозможность тесного взаимодействия расширений сервера Web с серверными сценариями ASP (удобными для создания пользовательского интерфейса и обращения к базам данных), а также сложность отладки.

Намного проще создавать собственные расширения объектной модели в виде серверных элементов ActiveX. Эта технология допускает тесную интеграцию с серверными сценариями ASP. В случае реализации серверных элементов ActiveX на языке программирования С++ обеспечивается возможность вызова любых функций Win32 и функций из произвольных библиотек DLL. При этом обеспечивается высокая производительность работы созданных таким образом объектов.

Следует заметить, что серверные элементы ActiveX можно создавать и с применением таких языков программирования, как Visual Basic или Java, однако это не всегда целесообразно. Язык Visual Basic удобен для разработки элементов ActiveX, но его применение вызывает проблемы с производительностью, так как Visual Basic относится к интерпретируемым языкам. Приложения Java также не отличаются высокой скоростью работы и не всегда способны обращаться напрямую к интерфейсам операционной системы.

Поэтому в нашей книге мы расскажем лишь о приемах создания элементов ActiveX, созданных с применением С++. Причем для облегчения работы мы будем использовать библиотеку шаблонов Active Template Library (ATL). Хотя она предоставляет мощные средства для создания приложений COM, к которым относятся элементы ActiveX.

Первый проект элемента ActiveX

В этом разделе мы опишем поэтапную процедуру создания простейшего серверного элемента управления ActiveX с применением Microsoft Visual C++ версии 6.0 и библиотеки шаблонов ATL.

Итак, начнем.

Создание проекта

Запустите Microsoft Visual C++ и выберите из меню File строку New. На экране появится диалоговая панель New, открытая на вкладке Projects (рис. 12-1).

Рис. 12-1. Диалоговая панель New

В списке типов проектов, расположенном в левой части этой панели, выберите строку ATL COM AppWizard. Далее укажите в поле Location путь к каталогу, в котором будет создан новый проект.

В поле Project name задайте имя проекта. Если Вы выбрали какое-нибудь нестандартное имя для проекта, мы советуем добавить к нему строку Mod или Module, чтобы отличать имя модуля от имени файла, созданного для хранения исходного текста класса создаваемого объекта.

Заполнив описанным образом поля панели New, щелкните кнопку OK. Запустится мастер проектов, первая панель которого показана на рис. 12-2.

Рис. 12-2. Первая панель мастера проектов для элементов ActiveX

Оставьте отмеченным переключатель Dynamic Link Library (DLL) и отметьте переключатель Allow merging of proxy/stub code. Далее щелкните кнопку Finish.

Сразу после этого мастер проектов создаст в указанном Вами каталоге файлы исходных текстов, а затем выведет на экран панель со списком основных созданных файлов (рис. 12-3).

Рис. 12-3. Окончание работы мастера приложений

Добавление объекта

В результате работы мастера проектов создается проект библиотеки DLL, в которой пока не определено ни одного объекта.

Выберите в меню Insert системы проектирования Microsoft Visual C++ строку New ATL Object. Откроется первая панели мастера объектов ATL ( рис. 12-4).

Рис. 12-4. Первая панель мастера объектов ATL

Здесь выделите пиктограмму ActiveX Server Component, а затем щелкните кнопку Next. В результате на экране появится вторая панель мастера объектов ATL с тремя вкладками (рис. 12-5).

Рис. 12-5. Вкладка Names в панели мастера объектов ATL

В поле Short Name введите имя объекта как BookStoreLogin. В результате в остальных полях этой вкладки появятся имена, выбранные для различных объектов по умолчанию. Хотя Вы можете оставить все как есть, мы рекомендуем отредактировать поля Type и ProgID.

В поле Type нужно ввести текстовое описание создаваемого объекта, удобное для поиска в списке объектов, отображаемых программой OLE View, входящей в комплект Microsoft Visual C++. Например, можно указать в начале этого поля название Вашей фирмы, а в конце — название объекта.

В поле ProgID следует ввести два идентификатора, разделенных точкой. В качестве первого можете набрать название фирмы (латинскими символами), а в качестве второго — название объекта. Мы вводим в этом поле строку «BookStore.BookStoreLogin».

Теперь откройте вкладку Attributes (рис. 12-6).

 Рис. 12-6. Вкладка Attributes в панели мастера объектов ATL

Здесь оставьте все элементы управления в том состоянии, в котором они показаны на рисунке. Группа переключателей Threading Model позволяет выбрать одну из четырех моделей для работы с потоками. Не вдаваясь сейчас в тонкости отличия моделей, скажем, что в большинстве случаев следует выбирать модель Apartment, как показано на рис. 12-6.

Если Ваш объект не создает событий, то переключатель  Support Connection Points следует оставить без отметки. Однако если в будущем Вы планируете использовать события, этот переключатель необходимо отметить.

Остальные элементы управления оставьте в состоянии, показанном на рисунке.

Теперь откройте вкладку ASP (рис. 12-7).

Рис. 12-7. Вкладка ASP в панели мастера объектов ATL

Если Вы отметить переключатель OnStartPage/OnEndPage, показанный на этом рисунке, создаваемый Вами элемент управления получит способность обращаться к объектам, свойствам и методам ASP. Это могут быть, например, переменные сеанса или приложения, параметры заголовка запроса и другие объекты, доступные серверным сценариям, расположенным на странице ASP.

Заметим, однако, что если все, что Вам нужно для связи серверного сценария и элемента управления ActiveX, это передача параметров, то достаточно определить соответствующие методы и свойства. Именно так мы и поступим в наших примерах.

Итак, мы установили все параметры на вкладках мастера объектов ATL. Теперь щелкните кнопку OK для запуска генерации исходных текстов. Через непродолжительное время исходные тексты шаблона Вашего элемента управления ActiveX будут построены. А нам надо определить собственные методы и свойства.

Определение нового свойства

Сначала мы определим свойство с именем CheckResult, получающее в качестве параметров две входные текстовые строки и возвращающее выходную текстовую строку, созданную слиянием входных строк.

Откройте вкладку ClassView в главном окне Microsoft Visual C++ (рис. 12-8).

Рис. 12-8. Добавление нового свойства

Раскройте папку интерфейса IBookStoreLogin, а затем щелкните правой клавишей мыши строку IbookStoreLogin. Затем выберите из контекстного меню строку Add Property, как это показано на рис. 12-8 (строка Add Method позволяет добавить новый метод).

На экране появится панель Add Property to Interface, показанная на рис. 12-9.

Рис. 12-9. Панель, предназначенная для добавления нового метода

Так как наше свойство предназначено для хранения строк, выберите тип BSTR в списке Property Type, определяющем тип свойства.

В поле Property Name Вы должны ввести имя свойства. В нашем случае это имя CheckResult.

В поле Parameters необходимо указать параметры метода, разделив их запятой. Укажите здесь параметры bsName и bsPassword типа BSTR*. Первый параметр представляет собой указатель на строку типа BSTR с именем пользователя, а второй — указатель на строку BSTR с паролем пользователя.

Как Вы, наверное, знаете, для каждого свойства можно определить две функции, первая из которых предназначена для чтения содержимого свойства, а вторая — для записи в свойство нового значения. Чтобы мастер создания свойства добавил исходный текст и описание соответствующей функции в проект, необходимо отметить переключатели Get Function и Put Function.

В нашем примере мы создаем только одно свойство, предназначенное для чтения, поэтому надо отметить только один переключатель Get Function. Сделав это, щелкните кнопку OK. В проект будет добавлен исходный текст метода CheckResult. Чтобы увидеть его исходный текст, раскройте папку интерфейса IBookStoreLogin, расположенную в папке класса CbookStoreLogin, и дважды щелкните название метода get_CheckResult. Вот что Вы увидите:

STDMETHODIMP CBookStoreLogin::get_CheckResult(
  BSTR *bsName, BSTR *bsPassword, BSTR *pOutVal)
{
  return S_OK;
}

Редактирование исходного текста свойства

Добавьте в определение метода следующие строки:

USES_CONVERSION;
 
if(pOutVal == NULL)
  return E_POINTER;

lpszName = OLE2A(*bsName);
lpszPassword = OLE2A(*bsPassword);

char szBuf[256];

strcpy(szBuf, lpszName);
strcat(szBuf, ":");
strcat(szBuf, lpszPassword);

CComBSTR bstrTemp;
bstrTemp = A2OLE(szBuf);

if(!bstrTemp)
  return E_OUTOFMEMORY;

*pOutVal = bstrTemp.Detach();

Макрокоманда USES_CONVERSION используется для обозначения того факта, что наш метод будет применять перекодировку строк BSTR в формат обычных строк ASCII, закрытых двоичным нулем, причем для перекодировки будут применяться макрокоманды OLE2A и A2OLE. Первая из них предназначена для преобразования строк BSTR в строки ANSI, а вторая выполняет обратное действие.

В начале своей работы добавленный фрагмент кода проверяет указатель pOutVal, передаваемый методу для записи значения свойства. Если он равен NULL, метод завершает свою работу с соответствующей ошибкой.

Далее мы преобразуем входные параметры bsName и bsPassword в обычные текстовые строки ANSI, записывая указатели на результат преобразования соответственно в поля класса lpszName и lpszPassword. Эти поля типа LPSTR Вам надо добавить самостоятельно в класс CBookStoreLogin обычным образом.

После преобразования наш метод копирует входные строки в буфер szBuf, разделяя их двоеточием.

Для преобразования результата копирования в тип BSTR мы создаем указатель bstrTemp типа CComBSTR и записываем в него результат преобразования, выполненного макрокомандой A2OLE. Если оно выполнено с ошибкой, в указатель bstrTemp будет записано нулевое значение. В этом случае метод возвращает код ошибки, означающий отсутствие необходимого объема свободной памяти.

Чтобы вернуть значение свойства, мы вызываем метод Detach, определенный в классе CComBSTR. На этом работа метода закончена.

Добавив описанные выше строки в определение метода CheckResult, запустите проект на трансляцию. Созданный в виде библиотеки DLL элемент управления будет зарегистрирован и доступен для вызова. Если этот файл Вы предполагаете использовать на другом компьютере, его нужно зарегистрировать при помощи программы REGSVR32, передав ей путь к файлу DLL. Запустив программу REGSVR32 без параметров, Вы увидите на экране краткую инструкцию по ее использованию.

Подготовка страницы ASP

Теперь мы подготовим страницу ASP, вызывающую созданный нами серверный элемент управления ActiveX.

На самом деле мы создадим две страницы. Первая из них представляет собой обычный документ HTML с формой, в которой посетитель вводит свой идентификатор и пароль (рис. 12-10).

Рис. 12-10. Форма для ввода идентификатора и пароля

Если после ввода информации щелкнуть кнопку Вход, управление будет передано странице ASP, вызывающей наш элемент управления ActiveX. На этой странице отображается строка, сформированная элементом BookStoreLogin (рис. 12-11).

Рис. 12-11. Результат работы серверного сценария

Исходный текст документа HTML с формой представлен в листинге 12-1.

Листинг 12-1 Вы найдете в файле chap12/BookStoreLoginMod/bslogin.html на прилагаемом к книге компакт-диске.

Определенная в нем форма содержит два поля редактирования с именами USR и PWD:

<FORM ACTION="BookStoreLogin.asp" METHOD="post" TARGET="_top">
<H1>
Добро пожаловать!</H1>
<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=0>
<TR><TD>
Имя</TD><TD>
<INPUT SIZE=10 TYPE="EDIT" NAME="USR"></TD></TR>
<TR><TD>
Пароль</TD><TD>
<nobr>
<INPUT SIZE=10 TYPE="password" NAME="PWD">
<INPUT TYPE="submit" VALUE="
Вход"></nobr>
</TD></TR>
</TABLE>
</FORM>

После щелчка кнопки Вход управление передается странице с именем BookStoreLogin.asp, указанным в параметре ACTION тега <FORM>. Исходный текст этой страницы представлен в листинге 12-2.

Листинг 12-2 Вы найдете в файле chap12\BookStoreLoginMod\BookStoreLoginMod.asp на прилагаемом к книге компакт-диске.

Первая строка в файле BookStoreLogin.asp задает язык серверного сценария JScript:

<%@ LANGUAGE = "JScript" %>

Далее в документе расположен фрагмент серверного сценария, показанный ниже:

<%
var sUser=Request("USR")(1);
var sPassword=Request("PWD")(1);

var culogin = Server.CreateObject("BookStore.BookStoreLogin");
var sResult = culogin.CheckResult(sUser, sPassword);
%>

Здесь мы вначале извлекаем параметры с именами USR и PWD, переданные формой, и сохраняем эти параметры в переменных sUser и sPassword соответственно.

Далее с помощью метода CreateObject встроенного объекта ASP с именем Server мы создаем объект с идентификатором BookStore.BookStoreLogin. Это тот самый идентификатор, который мы определяли при создании проекта в панели, показанной на рис. 12-5.

Объект будет записан в переменную culogin, при помощи которой сценарий обращается к свойствам и методам объекта. Строкой ниже мы читаем свойство CheckResult, передавая ему в качестве параметров идентификатор посетителя и его пароль.

Значение, прочитанное из свойства, сценарий записывает в переменную sResult. Далее эта переменная используется при формировании текста документа HTML, создаваемого сценарием:

<p>Вы ввели (идентификатор:пароль) - <b><%= sResult %></b>

Определения методов элемента

Исходный текст метода CheckResult, а также двух других методов, созданных мастером проекта, находится в файле BookStoreLogin.cpp (листинг 12-3).

Листинг 12-3 Вы найдете в файле chap12BookStoreLoginMod/BookStoreLogin.cpp на прилагаемом к книге компакт-диске.

Рассмотрим его содержимое.

Помимо файла stdafx.h мастер проекта включает в файл BookStoreLogin.cpp файлы BookStoreLoginMod.h и BookStoreLogin.h:

#include "BookStoreLoginMod.h"
#include "BookStoreLogin.h"

Первый из них создается автоматически и содержит определения интерфейсов. Вам не нужно его редактировать, так как это сделает мастер проекта при добавлении новых методов и свойств.

Второй файл содержит определение класса CBookStoreLogin, который представляет собой главный класс нашего объекта. Опять же, при добавлении в определение этого класса новых полей мастер проекта сделает все изменения в файле автоматически.

Методы OnStartPage и OnEndPage генерируются автоматически, если при создании проекта отметить переключатель OnStartPage/OnEndPage на вкладке ASP (рис. 12-7).

Когда посетитель запрашивает страницу ASP, управление получает метод OnStartPage. Ему передается указатель на интерфейс IUnknown, с помощью которого получит указатели на интерфейсы, нужные для связи нашего элемента со встроенными элементами ASP:

STDMETHODIMP CBookStoreLogin::OnStartPage (IUnknown* pUnk)
{
  . . .
}

Вначале метод OnStartPage получает указатель на интерфейс IscriptingContext:

  CComPtr<IScriptingContext> spContext;
hr = pUnk->QueryInterface(IID_IScriptingContext,
  (void **)&spContext);

Далее, пользуясь этим интерфейсом, метод извлекает указатели на объекты Request, Response, Server, Session и  Application:

hr = spContext->get_Request(&m_piRequest);
hr = spContext->get_Response(&m_piResponse);
hr = spContext->get_Server(&m_piServer);
hr = spContext->get_Session(&m_piSession);
hr = spContext->get_Application(&m_piApplication);

Метод OnEndPage освобождает полученные указатели при завершении обработки серверного сценария, расположенного на странице:

  m_piRequest.Release();
  m_piResponse.Release();
  m_piServer.Release();
  m_piSession.Release();
  m_piApplication.Release();

Кроме методов OnStartPage и OnEndPage в файле BookStoreLogin.cpp находится определение созданного нами метода get_CheckResult:

STDMETHODIMP CBookStoreLogin::get_CheckResult(
  BSTR *bsName, BSTR *bsPassword, BSTR *pOutVal)
{
  . . .
  return S_OK;
}

Автоматическая обработка кредитных карточек

Создавая Интернет-магазин, Вы должны продумать способ оплаты товара. Наиболее современный способ оплаты — с помощью кредитных карточек. Мы уже рассказывали о том, как организовать такую оплату с привлечением процессинговых компаний, выполняющих обработку кредиток через Интернет.

Как Вам известно, такие компании предоставляют всем желающим интерфейс в виде библиотеки DLL или автономной программы. Модуль интерфейса необходимо разместить на сервере Web Вашего магазина, причем вызов функций этого модуля должен выполняться из программного обеспечения магазина. Если магазин создан с применением технологии ASP, нужно придумать способ вызова интерфейсного модуля из серверных сценариев, написанных на языках JScript или VBScript.

В этом разделе мы расскажем о том, как создать серверный элемент управления ActiveX, вызывающий функции из библиотеки DLL, имитирующей часть интерфейса обработки кредитных карточек.

Библиотека для имитации интерфейса

Наш демонстрационный интерфейс обработки кредитных карточек состоит всего из одной функции с именем fnSendPayData. Ее задача — отправить информацию о платеже на сервер процессинговой компании. Напомним, что при создании реального магазина Вы получите интерфейсный модуль от процессинговой компании.

Прототип функции fnSendPayData показан ниже:

extern "C" __declspec(dllexport) int fnSendPayData(
  LPSTR szAmount, LPSTR szCurrency, DWORD dwMerchantID,
  LPSTR szSuccessURL, LPSTR szErrorURL, LPSTR szNoyificationURL);

Так как предполагается, что мы вызываем функцию из программы, написанной на С++, для отключения механизма «украшения» имени функции (name mangling) используется определение extern "C".

Чтобы функция fnSendPayData попала в список экспортируемых функций, определим ее как __declspec(dllexport). Это позволит нам не создавать def-файл определения модуля библиотеки DLL.

При вызове функции fnSendPayData необходимо передать шесть параметров.

Через первые два параметра szAmount и szCurrency передается величина суммы и название валюты. И тот, и другой параметр задается в виде текстовой строки.

Третий параметр dwMerchantID — идентификатор покупателя, назначенный ему при регистрации и зарегистрированный в процессинговой компании. По этому идентификатору компания сможет определить, кто выполняет платеж.

Через последние три параметра интерфейсный модуль передает серверу магазина адреса URL-страниц, на которые попадает посетитель в зависимости от результата выполнения платежа. Если результат успешный, посетитель попадает на страницу, адрес которой задан параметром szSuccessURL, если нет — на страницу с адресом szErrorURL, а если при выполнении операции появилась дополнительная информация — на страницу с адресом szNoyificationURL.

Функция fnSendPayData возвращает код выполнения операции. Если этот код равен нулю, операция выполнена успешно, а если нет, то это означает, что были ошибки.

Рассмотрим действия, выполняемые функцией fnSendPayData. Ее исходный текст Вы найдете в листинге 12-4.

Листинг 12-4 хранится в файле chap12/CreditCardInterface/CreditCardInterface.cpp на прилагаемом к книге компакт-диске.

Прежде всего, эта функция проверяет идентификатор покупателя:

if(dwMerchantID != 12345)
  return 1;

Мы разрешаем выполнение платежа только для посетителя с идентификатором 12345. При ошибке возвращается значение 1. Процессинговая компания проверяет этот идентификатор по своей базе данных.

Далее функция fnSendPayData убеждается, что параметры szAmount и szCurrency не содержат нулевых значений:

if(strlen(szAmount) == 0 || strlen(szCurrency) == 0)
  return 2;

В случае ошибки функция возвращает значение 2.

Если же параметры указаны правильно, функция fnSendPayData записывает имена файлов по адресам, хранящимся в параметрах szSuccessURL, szErrorURL и szNoyificationURL:

if(szSuccessURL != NULL)
  strcpy(szSuccessURL, "sucsess.html");

if(szErrorURL != NULL)
  strcpy(szErrorURL, "error.html");

if(szNoyificationURL != NULL)
  strcpy(szNoyificationURL, "notify.html");

Далее функция возвращает нулевой значение в качестве признака успешного завершения своей работы.

Помимо функции fnSendPayData в исходном тексте нашей библиотеки DLL определена стандартная функция DllMain:

BOOL APIENTRY DllMain(HANDLE hModule,
 DWORD ul_reason_for_call, LPVOID lpReserved)
{
  switch (ul_reason_for_call)
  {
     case DLL_PROCESS_ATTACH:
     case DLL_THREAD_ATTACH:
     case DLL_THREAD_DETACH:
     case DLL_PROCESS_DETACH:
       break;
  }
  return TRUE;
}

Она вызывается, когда процесс или поток обращается к библиотеке. Эта функция была создана мастером Microsoft Visual C++ и не выполняет никаких действий.

Тестовая программа для вызова имитатора интерфейса

Прежде чем вызывать только что описанный модуль из серверного сценария ASP, выполним его тестирование. Для этого мы подготовили небольшую консольную программу, исходный текст которой Вы найдете в листинге 12-5.

Листинг 12-5 хранится в файле chap12/CreditCardInterfaceTest/CreditCardInterfaceTest.cpp на прилагаемом к книге компакт-диске.

В области глобальных переменных этой программы мы подготовили определение типа DLLFN:

typedef int (* DLLFN)(LPSTR szAmount, LPSTR szCurrency,
  DWORD dwMerchantID,LPSTR szSuccessURL, LPSTR szErrorURL,
  LPSTR szNoyificationURL);

Как видите, этот тип представляет собой указатель на функцию fnSendPayData, описанную ранее.

Тестовая программа вызывает функцию fnSendPayData, предварительно выполняя динамическую загрузку библиотеки DLL с именем CreditCardInterface.dll.

Библиотека DLL загружается функцией LoadLibrary, как это показано ниже:

HINSTANCE hCreditCardInterfaceDLL;
hCreditCardInterfaceDLL = LoadLibrary("CreditCardInterface.dll");

Идентификатор загруженной библиотеки сохраняется в локальной переменной hCreditCardInterfaceDLL.

Если загрузка прошла успешно, тестовая программа вызывает функцию fnSendPayData, предварительно получив на нее указатель с помощью  функции GetProcAddress:

DLLFN fn;

char szSuccessURL[512];
char szErrorURL[512];
char szNoyificationURL[512];
. . .
if(hCreditCardInterfaceDLL != NULL)
{
  fn = (DLLFN)GetProcAddress(
     (HMODULE)hCreditCardInterfaceDLL, "fnSendPayData");

  if(fn != NULL)
  {
     int n = fn("123", "usd", 12345,
       szSuccessURL, szErrorURL, szNoyificationURL);
  }
  FreeLibrary(hCreditCardInterfaceDLL);
}

Здесь мы передаем функции сумму 123, название валюты — «usd», идентификатор покупателя 12345. Адреса URL функция запишет в переменные szSuccessURL, szErrorURL и szNoyificationURL.

Убедившись с помощью тестовой программы и отладчика, что интерфейсная библиотека DLL работает нормально, мы переходим к созданию серверного элемента управления ActiveX, доступного из страниц ASP и предназначенного для вызова функции fnSendPayData.

Элемент управления CreditCard

В начале этой главы мы подробно рассмотрели процедуру создания серверного элемента управления ActiveX с применением библиотеки шаблонов ATL. Поэтому сейчас мы не будем вдаваться в детали.

Нам нужно создать модуль CreditCardMod. Идентификатор элемента управления должен быть указан как «Frolov.CreditCard».Исходный текст главного модуля элемента управления, созданный мастером и дополненный нами, Вы найдете в листинге 12-6.

Листинг 12-6 хранится в файле chap12/CreditCardMod/CreditCard.cpp на прилагаемом к книге компакт-диске.

Рассмотрим определения свойств и методов, добавленных нами к объекту CreditCard.

Для свойства Amount, предназначенного для хранения суммы денег, мы предусмотрели две функции с именами get_Amount и put_Amount:

STDMETHODIMP CCreditCard::get_Amount(BSTR *pVal)
{
  *pVal=m_Amount.Copy();
  return S_OK;
}

STDMETHODIMP CCreditCard::put_Amount(BSTR newVal)
{
  m_Amount=newVal;
  return S_OK;
}

Обе эти функции обращаются к полю m_Amount типа CComBSTR:

CComBSTR m_Amount;

Мы добавили эту переменную в класс CreditCard, пользуясь мастером класса.

Функция put_Amount записывает в поле m_Amount значение, полученное через параметр newVal.

Что же касается функции get_Amount, то она извлекает значение из поля m_Amount и возвращает его, вызывая метод Copy.

Аналогичным образом устроены функции для работы со свойствами Currency, SuccessURL, ErrorURL и NotificationURL. Для них в классе CreditCard мы определили следующие поля:

CComBSTR m_ErrorURL;
CComBSTR m_NotificationURL;
CComBSTR m_SuccessURL;
CComBSTR m_Currency;

Функции для работы со свойством MerchantID, хранящим численное значение, определены так:

STDMETHODIMP CCreditCard::get_MerchantID(long *pVal)
{
  *pVal=m_MerchantID;
  return S_OK;
}

STDMETHODIMP CCreditCard::put_MerchantID(long newVal)
{
  m_MerchantID=newVal;
  return S_OK;
}

Эти функции обращаются к полю m_MerchantID типа long.

Теперь мы займемся методом SendPayData, предназначенным для вызова нашей библиотеки DLL, имитирующей интерфейс процессинговой компании.

Так же как и в тестовой программе, мы определили тип DLLFN, необходимый для вызова функции fnSendPayData через указатель:

typedef int (* DLLFN)(LPSTR szAmount, LPSTR szCurrency,
  DWORD dwMerchantID,LPSTR szSuccessURL, LPSTR szErrorURL,
  LPSTR szNoyificationURL);

В самом начале метода SendPayData, вызывающего эту функцию, мы расположили макрокоманду USES_CONVERSION, необходимую для работы с макрокомандами перекодировки OLE2A и A2OLE:

STDMETHODIMP CCreditCard::SendPayData()
{
  USES_CONVERSION;
  . . .
}

В области локальных переменных метода SendPayData мы определили переменную для хранения идентификатора библиотека DLL hCreditCardInterfaceDLL, переменную для хранения указатель на функцию fnSendPayData с именем fn, три массива для хранения адресов URL с именами szSuccessURL, szErrorURL и szNotificationURL, а также рабочую переменную bstrTemp типа CComBSTR:

HINSTANCE hCreditCardInterfaceDLL;
DLLFN fn;
char szSuccessURL[512];
char szErrorURL[512];
char szNotificationURL[512];
CComBSTR bstrTemp;

В начале своей работы метод SendPayData загружает интерфейсную библиотеку DLL из файла CreditCardInterface.dll и получает указатель на функцию fnSendPayData:

hCreditCardInterfaceDLL = LoadLibrary("CreditCardInterface.dll");
if(hCreditCardInterfaceDLL != NULL)
{
  fn = (DLLFN)GetProcAddress(
     (HMODULE)hCreditCardInterfaceDLL, "fnSendPayData");
  . . .
}

Если библиотека загрузилась, а указатель получен без ошибок и не равен NULL, метод преобразует значения из полей m_Amount и m_Currency в текстовые строки ANSI:

pszAmount = OLE2A(m_Amount);
pszCurrency = OLE2A(m_Currency);

Соответствующие указатели pszAmount и pszCurrency Вы должны определить в классе CreditCard:

char* pszCurrency;
char* pszAmount;

Теперь можно вызывать функцию fnSendPayData:

m_Result = fn(pszAmount, pszCurrency, m_MerchantID,
  szSuccessURL, szErrorURL, szNotificationURL);

Ее код возврата мы записываем в поле m_Result, определенное в классе CreditCard как long.

Если параметры для функции указаны без ошибок, метод освобождает идентификатор библиотеки DLL, а затем преобразует полученные строки адресов URL, записывая их в соответствующие поля класса CreditCard:

FreeLibrary(hCreditCardInterfaceDLL);
m_SuccessURL = A2OLE(szSuccessURL);
m_ErrorURL = A2OLE(szErrorURL);
m_NotificationURL = A2OLE(szNotificationURL);

При ошибке в поле m_Result записывается значение –1:

m_Result = -1;

Сценарий определяет результат вызова функции с помощью свойства Result, доступного только для чтения. Для этого свойства мы подготовили только одну функцию с именем get_Result:

STDMETHODIMP CCreditCard::get_Result(long *pVal)
{
  *pVal=m_Result;
  return S_OK;
}

Вызов элемента управления CreditCard

Для вызова элемента управления CreditCard мы подготовили документ HTML с формой, позволяющей задать идентификатор платежа, сумму и выбрать валюту (рис. 12-12).

Рис. 12-12. Форма для вызова элемента управления CreditCard

Полный исходный текст этого документа расположен в листинге 12-7.

Листинг 12-7 Вы найдете в файле chap12/CreditCardMod/pay.html на прилагаемом к книге компакт-диске.

Этот документ содержит форму, ссылающуюся на страницу ASP с именем dopay.asp:

<form method="POST" action="dopay.asp">

В форме определены поля ввода идентификатора MerchID, суммы платежа Amount, а также поле кода валюты Currency:

<tr>
  <td width="165">
Идентификатор:</td>
  <td width="184">
<input type="text" name="MerchID" size="20"></td>
  </tr>
  <tr>
  <td width="165">
Сумма:</td>
  <td width="184">
<input type="text" name="Amount" size="20"></td>
  </tr>
  <tr>
  <td width="165">
Валюта:</td>
  <td width="184">
<select name="Currency" size="1">
  <option selected value="rub">
Рубли</option>
  <option value="usd">
Доллары</option>
  <option value="dm">
Марки</option>
  </select></td>
</tr>

Исходный текст сценария, выполняющего вызов элемента управления CreditCard, представлен в листинге 12-8.

Листинг 12-8 Вы найдете в файле chap12/CreditCardMod/dopay.asp на прилагаемом к книге компакт-диске.

В начале своей работы сценарий создает объект CreditCard, указывая его идентификатор:

var cc = Server.CreateObject("Frolov.CreditCard");

Ссылка на объект записывается в переменную cc, с помощью которой мы будем обращаться к свойствам и методам объекта.

Первым делом необходимо извлечь из полей формы информацию о платеже. Мы это делаем с помощью объекта Request:

cc.Amount = Request("Amount")(1);
cc.Currency = Request("Currency")(1);
var MerchID="";
MerchID = Request("MerchID")(1);
if(MerchID != "")
  cc.MerchantID = parseInt(MerchID, 10);
else
  cc.MerchantID = 0;

Так как в форме может быть указан пустой идентификатор, мы обрабатываем такую ситуацию как ошибочную, записывая при этом в свойство MerchantID нулевое значение.

Далее сценарий вызывает метод SendPayData:

cc.SendPayData();

Результаты работы метода SendPayData извлекаются из свойств элемента управления и сохраняются в соответствующих локальных переменных:

var Result = cc.Result;
var sSuccessURL = cc.SuccessURL;
var sErrorURL = cc.ErrorURL;
var sNotificationURL = cc.NotificationURL;

Далее, если метод выполнен без ошибок (то есть если в свойстве cc.Result находится нулевое значение), наш сценарий отображает содержимое всех свойств объекта:

if(Result == 0)
{
%>
<table border=1>
<tr><td>Success URL</td><td><%=sSuccessURL%></td><tr>
<tr><td>Error URL</td><td><%=sErrorURL%></td><tr>
<tr><td>Notification URL</td><td><%=sNotificationURL%></td><tr>
<tr><td>Amount</td><td><%=cc.Amount%></td><tr>
<tr><td>Currency</td><td><%=cc.Currency%></td><tr>
<tr><td>Merchant ID</td><td><%=cc.MerchantID%></td><tr>
</table>
<%
}

В противном случае в окне браузера отобразится лишь код завершения.

Современные почтовые протоколы

Большинство приложений Web тем или иным способом интегрируется с почтовыми серверами. Например, сервер Web Интернет-магазина может выполнять ночную рассылку сообщений ленты новостей по электронной почте, или отправлять почтовые сообщения из форм, расположенных на его страницах. В этом разделе мы приведем некоторую информацию о почтовых протоколах, полезную для решения задачи интеграции Web-приложения с почтовым сервером.

Электронная почта появилась в Интернете еще до возникновения серверов Web вместе с операционной системой UNIX. Работать с электронной почтой в локальных и глобальных компьютерных сетях, выполненных на базе UNIX, позволял протокол копирования UNIX-UNIX (UNIX to UNIX Copy, UUCP). При использовании этого протокола почта передавалась последовательно с одного узла на другой, в результате чего она иногда долго путешествовала по сети, прежде чем достигала адресата.

Сегодня в Интернете протокол UUCP, хотя и применяется, но гораздо меньше: его постепенно заменяют современные протоколы:

·простой протокол передачи почты (Simple Mail Transfer Protocol, SMTP);

·протокол почтового отделения (Post Office Protocol, POP);

·протокол доступа к сообщениям Интернета (Internet Message Access Protocol, IMAP)

Эти протоколы надежнее и быстрее, чем UUCP. Все они описаны в документах RFC.

Что представляют собой эти документы?

В 1969 году специалистами организации под названием Рабочая группа инженеров Интернета (Internet Engineering Task Force, IETF) была создана серия документов Request for Comments (RFC), содержащих рекомендуемые стандарты и технологии для применения в Интернете. Обыкновенному пользователю едва ли когда-нибудь придется изучать документы RFC. Однако разработчикам сервисов и программ эти документы просто необходимы.

По мере создания новым документам RFC присваиваются последовательные номера. Даже новый документ, замещающий существующий, получает новый номер. Таким образом, номера документов RFC повторно не используются.

Документы RFC разделяются по необходимости применения и степени готовности.

С точки зрения необходимости использования документы RFC подразделяются на обязательные к применению (Required), рекомендованные (Recommended), факультативные (Elective), ограниченного применения (Limited Use) и не рекомендуемые к использованию (Not Recommended). 

Заметим, что все эти документы носят рекомендательный характер — никто не обязывает разработчиков аппаратного и программного обеспечения для Интернета следовать им в точности. Однако на практике ситуация такова: если созданные программы и системы не удовлетворяют требованиям, которые сформулированы в документах RFC, обязательных к применению (Required), то они не будут работать в Интернете. Например, это касается протокола IP. Поэтому, хотя никто и не заставляет разработчиков, те строго соблюдают требования документов RFC, иначе продукт окажется несовместимым со стандартами, реализованными в Интернете. А значит, не найдет своего потребителя.

По степени готовности различают экспериментальные (Experimental), предлагаемые к использованию (Proposed), черновые (Draft) и стандартные (Internet Standard) документы RFC. Кроме того, документы RFC подразделяются на информационные (Informational) и исторические (Historic).

По номеру RFC Вы легко найдете документ и загрузите его с соответствующего сервера (например, c http://www.rfc-editor.org).

Протокол SMTP

Простой протокол передачи почты Simple Mail Transfer Protocol (SMTP) предназначен только для передачи почты. Обычно его используют в паре с протоколами POP3 и IMAP, обеспечивающими прием почтовых сообщений.

Работа протокола SMTP основана на передаче текстовых строк. После того как почтовая программа установит соединение с почтовым сервером провайдера, между ней и сервером начинается обмен текстовыми строками.

Чуть позже мы покажем примерный сценарий такого обмена.

Например, почтовая программа установила соединение с почтовым сервером smtp.galsnet.ru. Это доменный адрес сервера SMTP компании провайдера Гласнет. Если Вы подключены к другому провайдеру, то и адрес сервера SMTP будет иным.

Сразу после установки соединения с использованием порта 25 почтовый сервер посылает почтовой программе сообщение следующего вида:

220-hawk.glas.apc.org Smail-3.2.0.109 (#4 1999-Nov-10) ready at Sun, 26 Dec 1999
20:42:55 +0300 (MSK)
220 ESMTP supported

Для другого сервера внешний вид приглашения будет отличаться от приведенного выше.

В ответ почтовая программа отправляет серверу SMTP команду подключения HELO, передавая ей в качестве параметра доменный адрес сервера:

HELO smtp.glasnet.ru

Если подключение выполнено без ошибок, сервер должен ответить сообщением с кодом 250:

250 hawk.glas.apc.org Hello smtp.glasnet.ru (smtp.glasnet.ru from address [195.178.200.74]).

Далее почтовая программа продолжит диалог с сервером. Теперь она отправит серверу команду MAIL FROM с адресом отправителя:

MAIL FROM: alexandre@frolov.pp.ru

Здесь в качестве адреса отправителя используется наш электронный почтовый адрес alexandre@frolov.pp.ru, так как мы отправляли сообщение именно снего.

В ответ почтовая программа получает следующее сообщение с кодом 250:

250 2.1.0 alexandre@frolov.pp.ru Sender Okay.

Теперь она должна сообщить серверу адрес получателя. Например:

RCPT TO: a_frolov@hotmail.com

Здесь указан наш второй адрес электронной почты на сервере Hotmail. Об этом сервере мы Вам расскажем в главе 5.

В ответ сервер посылает почтовой программе подтверждение следующего вида:

250 2.1.0 'a_frolov@hotmail.com' Recipient Okay.

Далее почтовая программа передает серверу команду, свидетельствующую о готовности к началу передачи данных (то есть тела сообщения):

DATA

В ответ она получает приглашение для ввода текста сообщения с кодом 354:

354 Enter mail, end with "." on a line by itself...

Далее почтовая программа передает серверу строки заголовка сообщения, начиная с Subject:

SUBJECT:Test

Передав все строки заголовка, почтовая программа отправляет серверу пустую строку и строки тела сообщения. Завершает ввод строка, состоящая из одной точки в первой позиции:

This is test
.

В нашем послании тело сообщения состоит только из одной строки (что вполне допустимо).

Сообщение будет отправлено, а почтовая программа получит извещение об этом в следующем виде:

250 2.6.0 Mail accepted, queue ID m122Hir-001oz7n.

Как видите, протокол SMTP действительно оправдывает свое название — он весьма прост: описание его работы занимает всего лишь страницу книги. В действительности мы опустили некоторые несущественные детали, не меняющие сути дела.

Обратите внимание, что текст сообщения передается в открытом виде, а для соединения с почтовым сервером не нужно указывать идентификатор пользователя и пароль. Из этого следует, что протокол SMTP не обеспечивает никакой, даже самой слабой защиты передаваемой информации. Ее легко перехватить или подделать.

Чтобы хоть как-то контролировать процесс отправки почты, провайдеры требуют установки прямого соединения между компьютером, отправляющим почту, и почтовым сервером SMTP провайдера (строго говоря, они должны находиться в сети с одним адресом IP). При этом, дабы вычислить пользователя, который, например, занимается массовой рассылкой рекламной почты, провайдер может сопоставить параметры отправленного сообщения и статистические данные о работе своих клиентов.

В табл. 12-1 мы кратко описали основные команды протокола SMTP.

Таблица 12-1. Команды SMTP

Команда

Параметр

Описание

HELO

Доменное имя сервера

Создание канала связи с сервером SMTP

QUIT

Нет

Закрытие канала связи с сервером SMTP

MAIL

FROM: обратный адрес

Запуск передачи почты

RCPT

TO: адрес получателя

Сообщение серверу адреса получателя

DATA

Нет

Запуск передачи данных

VRFY

имя пользователя

Проверка существования почтового ящика пользователя с заданным идентификатором. Из соображений безопасности эта команда обычно блокируется администратором почтового сервера

EXPN

Имя списка рассылки

Проверка существования списка рассылки и пополнение его новыми данными

HELP

Нет

Запрос от сервера списка допустимых команд

NOOP

Нет

Пустая операция (в ответ на эту команду почтовый сервер не выполняет никаких операций)

RSET

Нет

Повторная инициализация сеанса связи с сервером

Более подробно протокол SMTP и все его команды описаны в документе RFC 821.

Протокол POP3

Протокол почтового отделения Post Office Protocol версии 3 — POP3 — предназначен для получения почты с сервера и обычно применяется вместе с только что описанным протоколом SMTP.

Для того чтобы подключится к серверу POP3 (имеющему, как и сервер SMTP, свой доменный адрес), почтовая программа должна сообщить ему идентификатор и пароль пользователя. Протокол POP3 обеспечивает некоторую защиту. Во всяком случае, никто не сможет забрать Ваши почтовые сообщения, не зная Вашего идентификатора и пароля.

Когда почтовая программа загружает сообщение с сервера POP3, она может либо удалить оригинал сообщения, либо оставить его на постоянное или временное хранение.

Строго говоря, протокол POP3, в отличие от своего предшественника POP2, способен не только принимать, но и передавать сообщения. Поэтому при его использовании протокол SMTP не требуется. Однако многие почтовые программы все же используют оба протокола — и SMTP, и POP3.

Чтобы Вы получили некоторое представление о том, что представляет собой протокол POP3, расскажем, как с его помощью почтовые программы получают информацию о содержимом почтового ящика пользователя и загружают сообщения. В качестве примера мы рассмотрим сервер POP3 с вымышленным именем pop3.goods.ru (не пытайтесь обращаться к нему; если хотите потренироваться, воспользуйтесь сервером своего провайдера).

После того как почтовая программа установит соединение с сервером POP3 с использованием порта 110, она получит от него сообщение следующего вида:

+OK QPOP (version 2.53) at pop3.goods.ru starting.

В ответ она посылает серверу команду подключения USER, указывая в качестве параметра идентификатор пользователя username:

USER username

Сервер отправляет почтовой программе сообщение о необходимости предоставления пароля пользователя с идентификатором username:

+OK Password required for username.

Почтовая программа пересылает его с помощью команды PASS:

PASS *******

Далее сервер «докладывает» почтовой программе, сколько сообщений хранится в почтовом ящике пользователя и сколько они занимают места на диске сервера:

+OK username has 6 messages (27973 octets).

Эта же информация может быть получена посредством команды STAT:

STAT
+OK 6 27973

Команда LIST позволяет почтовой программе дополнительно получить список сообщений, хранящихся в почтовом ящике пользователя:

LIST
+OK 6 messages (27973 octets)
1 3084
2 6132
3 1026
4 2735
5 7453
6 7543
.

Для каждого сообщения в списке приводится его размер в количестве символов. Список завершается строкой, состоящей из одной точки.

Если команде LIST указать номер сообщения, сервер выдаст информацию только о заданном сообщении:

LIST 1
+OK 1 3084

Чтобы извлечь полный текст сообщения с заданным номером, почтовая программа должна отправить серверу POP3 команду RETR, указав номер сообщения в качестве параметра:

RETR 4

Командой TOP можно извлечь только часть сообщения. Команде нужно передать два параметра — номер сообщения и количество извлекаемых строк сообщения:

TOP 4 10

Вот что вернул сервер в ответ на эту команду:

+OK 2735 octets
Return-path: <alexandre@frolov.pp.ru>
Envelope-to: alexandre@frolov.pp.ru
Delivery-date: Thu, 11 May 2000 22:28:04 +0400
Received: from [193.124.5.42] (helo=nnfalcon.glasnet.ru)
 by pop3.goods.ru with esmtp (Exim 3.10 #1) id 12pxgq-00018O-00
 for alexandre@frolov.pp.ru; Thu, 11 May 2000 22:28:04 +0400
Received: from hawk.glasnet.ru([193.124.5.50]) (2123 bytes) by nnfalcon.glasnet.ru
 via sendmail with P:esmtp/R:inet_hosts/T:inet_zone_smtp
 (sender: <alexandre@frolov.pp.ru>)
 id <m12pxYP-0015w9n@nnfalcon.glasnet.ru>
 for <alexandre@frolov.pp.ru>; Thu, 11 May 2000 22:19:21 +0400 (MSD)
 (Smail-3.2.0.111 2000-Feb-17 #1 built 2000-Mar-3)
Received: from saturn(ppp1283.glas.apc.org[194.154.81.229]) (1782 bytes) by hawk.glasnet.ru
 via sendmail with P:smtp/R:smart_host/T:smtp
 (sender: <alexandre@frolov.pp.ru>)
 id <m12pxYN-001ozkn@hawk.glasnet.ru>
 for <alexandre@frolov.pp.ru>; Thu, 11 May 2000 22:19:19 +0400 (MSD)
 (Smail-3.2.0.111 2000-Feb-17 #1 built 2000-Mar-3)
Message-ID: <002401bfbb75$b1f31990$140115ac@saturn>
Reply-To: "Alexandre Frolov" <alexandre@frolov.pp.ru>
From: "Alexandre Frolov" <alexandre@frolov.pp.ru>
To: <alexandre@frolov.pp.ru>
Subject: Test message
Date: Thu, 11 May 2000 21:43:41 +0400
MIME-Version: 1.0
Content-Type: multipart/alternative;
        boundary="----=_NextPart_000_001D_01BFBB91.F92D4990"
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 5.00.2919.6600
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2919.6600
X-UIDL: 04ed453aebf416737f367b9af906d5a0
Status: RO

This is a multi-part message in MIME format.

------=_NextPart_000_001D_01BFBB91.F92D4990
Content-Type: text/plain;
        charset="koi8-r"
Content-Transfer-Encoding: quoted-printable

This is test message
.

Почтовая программа анализирует ответ сервера POP3, извлекает из него поля заголовка, текст сообщения и отображает все это в окне просмотра сообщения.

В табл. 12-2 мы кратко описали некоторые команды протокола POP3. Полную информацию о протоколе POP3 и его командах Вы найдете в документе RFC 1939.

Таблица 12-2. Команды POP3

Команда

Параметры

Описание

USER

идентификатор пользователя

Передает серверу POP3 идентификатор пользователя

PASS

пароль пользователя

Передает серверу POP3 пароль пользователя

STAT

-

Позволяет получить от сервера информацию о содержимом почтового ящика (количестве хранящихся в нем сообщений и об объеме, занимаемом сообщениями на диске сервера)

LIST

[номер сообщения]

Если команда использована без параметра, возвращается список сообщений с номерами, если указан номер сообщения, то команда передает информацию о данном сообщении (номер и размер сообщения)

RETR

номер сообщения

Сервер передает почтовой программе полный текст сообщения с заданным номером

TOP

номер сообщения, количество извлекаемых строк

Сервер передает почтовой программе заданное количество первых строк сообщения с заданным номером

DELE

номер сообщения

Удаление сообщения с заданным номером

RSET

-

Отмена операции удаления сообщений, выполненных командой DELE

NOOP

-

Пустая операция (в ответ на эту команду почтовый сервер не выполняет никаких операций)

QUIT

-

Завершение сеанса связи с сервером

Команды TOP и DELE позволяют почтовой программе выполнить частичную загрузку сообщений. Если пользователь решит, что некоторые из них ему не нужны, он удалит их. Таким образом, удается избежать загрузки ненужных сообщений большого размера. К сожалению, подобная возможность реализована не во всех почтовых программах.

Протокол IMAP

Пара протоколов SMTP и POP3 очень удобна для обмена почтой в пакетном режиме. При этом за относительно короткий промежуток времени, когда Ваш компьютер подключен к серверу провайдера, осуществляется отправка Ваших сообщений (по протоколу SMTP) и получение сообщений, адресованных Вам (по протоколу POP3). Когда обмен почтой завершен, можно отключаться от Интернета и работать с полученными сообщениями в автономном режиме.

Этот режим позволяет экономить деньги, так как передача и прием сообщений занимает немного времени (если, конечно, Вы не отправляете письма с файлами приложений, имеющими размеры в несколько мегабайт), а вот на их чтение его требуется гораздо больше.

Тем не менее в последнее время все более популярна становится такая услуга, как постоянное подключение к Интернету: Вы платите фиксированную сумму в месяц и получаете неограниченный доступ в глобальную сеть. При этом для работы с электронной почтой удобнее использовать современный протокол доступа к сообщениям Интернета Internet Message Access Protocol (IMAP). Он описан в документе RFC 2060 и используется совместно с протоколом SMTP.

В отличие от протокола POP3, загружающего на Ваш компьютер все сообщения целиком или частично, протокол IMAP позволяет передавать только заголовки сообщений. Это отнимает намного меньше времени. Просмотрев заголовки, Вы решите, какие сообщения Вас заинтересовали и загрузите именно их. Остальные сообщения Вы вправе стереть, не читая.

Что же касается новых сообщений, то, как только Вы их создадите, они немедленно будут скопированы на почтовый сервер и затем переданы адресату.

Среди возможностей четвертой версии этого протокола — IMAP4 нам хотелось бы отметить поиск сообщений, хранящихся на сервере, по ключевым словам.

Заметим, что протокол POP3 тоже позволяет отказаться от загрузки некоторых сообщений, параметры которых описаны в специальном фильтре. Например, от сообщений, пришедших с каких-то конкретных адресов или содержащие в своем теле или в строке Тема (Subject) какие либо слова. Однако в этом режиме Вы не сможете просмотреть заголовки отвергнутой почты, так как она сразу по поступлении удаляется с сервера.

Режим постоянного подключения к Интернету или тарифный план без ограничения времени подключения позволяет, не торопясь, просматривать сообщения и загружать их при необходимости с почтового сервера с помощью протокола IMAP. Но если Вам приходится оплачивать каждую минуту сеанса связи с Интернетом, лучше воспользоваться протоколами SMTP и POP3.

Внутренняя структура электронного сообщения

Любое сообщение, передаваемое по каналам электронной почты, состоит максимум из трех основных компонентов: заголовка, тела сообщения и присоединенных файлов, передаваемых вместе с сообщением.

Заголовок содержит адресную и служебную информацию, тело сообщения — текст электронного письма, а присоединенные файлы — любые данные. Заголовок и тело сообщения неотъемлемая часть сообщения, а вот присоединенные файлы Вы вставляете в послание сами, по необходимости или по желанию. Фактически присоединенные файлы можно рассматривать как необязательную часть тела сообщения.

Заголовок сообщения

Заголовок почтового сообщения состоит из нескольких полей, некоторые из них Вы должны заполнить сами (это поля To, Cc, Bcc и Subject). Остальные почтовая программа заполнит автоматически.

Ниже показан заголовок письма, отправленного нами на наш же электронный адрес:

From: "Alexandre Frolov" <alexandre@frolov.pp.ru>
To: <alexandre@frolov.pp.ru>
Cc: <a_frolov@hotmail.ru>
Bcc: "usa.net" <a_frolov@usa.net>
Subject: This is a test
Date: Thu, 16 Mar 2000 15:01:36 +0300
MIME-Version: 1.0
Content-Type: multipart/alternative;
  boundary="----=_NextPart_000_001C_01BF8F58.8645AB30"
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 5.00.2314.1300
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300

Вам не придется создавать такие заголовки вручную, так как эта работа выполняется почтовыми программами. Тем не менее в ряде случаев (например, для борьбы с отправителями надоедливых рекламных сообщений) знание формата заголовка и назначения его полей Вам весьма пригодится.

Рассмотрим некоторые поля заголовка по отдельности.

From. В этом поле, заполняющемся автоматически почтовой программой, указывается имя и электронный адрес отправителя сообщения. В данном случае сообщение было отправлено Александром Фроловым (Alexandre Frolov) с адреса alexandre@frolov.pp.ru.

To. Здесь указывают электронный почтовый адрес получателя сообщения. Так как мы послали почту сами себе, то ввели здесь тот же адрес, что и в поле From. Обычно же содержимое этого поля не совпадает с содержимым поля From.

Cc. Имя поля образовано как сокращение от словосочетания Carbon Copy, которое можно перевести как «копия». Это поле Вам понадобится, когда Вы захотите послать одно и то же сообщение сразу по нескольким адресам. Надо указать в этом поле список электронных адресов, и сообщение будет отправлено по ним всем.

Bcc. Названия этого поля образовано от Blind Carbon Copy —  «слепая», или «блокированная», копия. По своему назначению это поле аналогично полю Cc, однако между ними есть одно существенное отличие. Поле Bcc позволяет скрыть от получателей список рассылки: имена других адресатов сообщения останутся неизвестны Вашим корреспондентам.

Subject. Здесь Вам надо кратко описать содержимое почтового сообщения. Когда адресат получит Ваше послание, по описанию в поле Subject он поймет, о чем речь в сообщении.

Date. Это поле заполняется автоматически почтовой программой. Оно содержит дату создания сообщения. В конце строки даты указано значение смещения часового пояса (разница между местным временем и временем по Гринвичу), для Москвы оно равно +3 часам.

MIME-Version. Электронные сообщения передаются в закодированном виде. В поле MIME-Version почтовая программа указывает номер версии стандарта многоцелевых расширений почты Интернета (Multipurpose Internet Mail Extension, MIME), использованной для кодирования данного сообщения.

Content-Type. В соответствии со стандартом MIME сообщение может содержать данные различного типа — как текстовые, так и двоичные. Причем в заголовке сообщения обязательно находится строка описания типа данных для каждого конкретного сообщения.

Тип данных описывается в поле заголовка с именем Content-Type (данное поле автоматически заполняется почтовой программой), например:

Content-Type: multipart/alternative;
  boundary="----=_NextPart_000_001C_01BF8F58.8645AB30"

В данном случае тип multipart означает, что в сообщении возможно несколько фрагментов данных. При этом параметр boundary определяет строку, разделяющую отдельные фрагменты сообщения. В данном случае в качестве разделителя выступает строка «----=_NextPart_000_001C_01BF8F58.8645AB30».

X-Priority и X-MSMail-Priority. Здесь определяется так называемый приоритет почтового сообщения.

Что это такое?

Почтовые программы позволяют задавать для сообщения низкий, обычный или высокий приоритет. В списке полученных сообщений, отображаемом в окне почтовой программы, высокоприоритетные сообщения выделяются цветом или специальным символом таким образом, чтобы пользователь сразу обратил на них внимание (способ выделения разный в разных почтовых программах). Низкоприоритетные сообщения также выделяются, но по-другому.

Присваивать своим сообщениям повышенный приоритет нужно только при необходимости. В противном случае обилие выделенных сообщений будет лишь раздражать Ваших корреспондентов.

X-Mailer. Здесь почтовая программа записывает свое собственное название и версию. Анализируя содержимое данного поля, нетрудно узнать, какой почтовой программой пользуется Ваш корреспондент. Например, взглянув на приведенный выше заголовок, можно установить, что для подготовки сообщения использовалась почтовая программа Microsoft Outlook Express версии 5.00.2314.1300 (цифры 2314.1300 означают номер модификации для версии 5.0).

X-MimeOLE. Здесь указано название и версию программной компоненты, выполняющей кодирование данных в стандарте MIME.

Тело сообщения

Чтобы показать, как выглядит тело сообщения, мы подготовили с помощью почтовой программы Microsoft Outlook Express небольшое электронное письмо, состоящее из одной строки «Test message». Заголовок этого сообщения показан в предыдущем разделе книги.

Ниже мы приводим тело сообщения полностью, таким, как его создает почтовая программа:

This is a multi-part message in MIME format.

------=_NextPart_000_001C_01BF8F58.8645AB30
Content-Type: text/plain;
  charset="koi8-r"
Content-Transfer-Encoding: quoted-printable

Test message

------=_NextPart_000_001C_01BF8F58.8645AB30
Content-Type: text/html;
  charset="koi8-r"
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META content=3D"text/html; charset=3Dkoi8-r" http-equiv=3DContent-Type>
<META content=3D"MSHTML 5.00.2314.1000" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY bgColor=3D#ffffff>
<DIV><FONT face=3DArial size=3D2>Test message</FONT></DIV></BODY></HTML>

------=_NextPart_000_001C_01BF8F58.8645AB30--

Как видите, собственно строка сообщения составляет лишь малую часть тела сообщения.

Почтовая программа Microsoft Outlook создала два фрагмента для двух вариантов сообщения, один из которых текстовый, а другой подготовлен с применением языка разметки гипертекста HTML, применяемого для создания страниц серверов Web.

Каждый фрагмент имеет свой заголовок, начинающийся со строки разделителя. Вы видите, что первый фрагмент имеет тип text/plain, а второй — text/html. Этот тип определяется содержимым поля Content-Type.

Кроме типа данных, в заголовке фрагмента указано название набора символов сообщения charset и тип кодирования Content-Transfer-Encoding. В данном случае для текста использован набор символов koi8-r, а для кодирования этого текста при передаче в теле сообщения используется формат quoted-printable. О наборах символов и кодировке текста сообщения мы расскажем в следующем разделе этой главы.

Когда Вы получите сообщение, то его текст будет извлечен почтовой программой из тела, перекодирован и отображен в окне этой программы без служебной информации. Вам не придется каждый раз вручную вычленять его из приведенного выше листинга, хотя при необходимости такую операцию можно выполнить (это требуется, например, для извлечения отдельных фрагментов сообщений, поврежденных в процессе передачи).

Для чего почтовая программа поместила в тело сообщения два фрагмента?

Первый из них предназначен для устаревших почтовых программ, которые не способны отображать сообщения в формате HTML. Такие программы проигнорируют фрагмент типа text/html.

Что же касается новых программ, то они способны реализовать богатые возможности языка HTML, применяя их для шрифтового и стилевого оформления почтовых сообщений, а также для размещения в таких сообщениях графических изображений и активных объектов. Сообщения, подготовленные с применением языка разметки HTML, выглядят более привлекательно, чем обычный неформатированный текст.

Наборы символов и кодировка сообщения

Остановимся подробнее на названии набора символов, указанного в поле charset.

Как Вы, наверное, знаете, серверы Интернета создаются на базе различных операционных систем, однако большинство почтовых серверов и серверов Web работают в среде Unix. На компьютерах пользователей Интернета, однако, может быть установлена не только операционная система Windows, но и Unix, IBM OS/2 и даже DOS. Из-за того, что в разных операционных системах применяются различные несовместимые между собой наборы символов, появляются, в частности, сложности с отображением кириллицы.

Обычно почтовые сообщения с символами кириллицы передаются через Интернет с применением набора символов KOI8-R, принятого по умолчанию в операционной системе Unix, однако возможны и другие наборы. В табл. 12-3 мы перечислили названия стандартных наборов символов кириллицы для некоторых наиболее распространенных операционных систем.

Таблица 12-3. Наборы символов кириллицы в разных операционных системах

Операционная система

Набор символов кириллицы

Microsoft Windows 95/98,
Microsoft Windows NT,
Microsoft Windows 2000

Windows-1251

Unix, Linux, FreeBCD, Sun OS и другие версии Unix

KOI8-R, ISO-8859-5

IBM OS/2, DOS

DOS CP866

Помимо этого, существует и другая проблема при передаче сообщений по каналам электронной почты. Она связана с тем, что некоторые почтовые серверы способны пропускать через себя только 7-битовые потоки данных, а для представления полного набора символов (т. е. букв, цифр, знаков пунктуации) нужно 8 бит.

Чтобы решить эту проблему, применяется дополнительное кодирование текста передаваемых сообщений.

На заре развития электронной почты для кодирования использовался метод «кодировка UNIX-UNIX» (UNIX to UNIX Encode, UUEncode): двоичные 8-битовые данные представлялись 7-битовым эквивалентом в виде символов латинского алфавита. Имейте в виду, что латинские символы кодируются числами в диапазоне от 0 до 127 и могут быть представлены только посредством 7 бит.

Как мы уже говорили, сегодня кодирование почтовых сообщений выполняется в соответствии со стандартом многоцелевых расширений почты Интернета (Multipurpose Internet Mail Extension, MIME). При этом в заголовке фрагмента помимо названия набора символов сообщения charset указан и тип кодирования Content-Transfer-Encoding.

А теперь мы перечислим различные типы кодирования (примеры использования некоторых из них показаны в следующем разделе):

binary — означает отсутствие кодирования. Символы сообщения передаются в исходном виде, даже если для их представления используется 8 бит. Передаваемые строки сообщения могут иметь большую длину;

8-bit — аналогично предыдущему, но используется небольшой размер передаваемых строк;

7-bit — аналогично предыдущему, но для представления символов передаваемого сообщения используется только 7 бит;

quoted-printable — используется для кодирования текста, большинство символов которого могут быть представлены посредством 7 бит. Это латинские символы. Если же для представления символа необходимо 8 бит, то такой символ кодируется с использованием 7-битовых символов;

base-64 — в результате кодирования данного типа выполняется преобразование групп из трех 8-битовых символов в группы по четыре 6-битовых символа. В результате такой кодировки размер сообщения увеличивается примерно на треть;

x-token — если указан этот тип кодирования, то отправляющая и принимающая программа сами договариваются между собой об использовании той или иной кодировки символов.

Присоединенный файл

Как мы уже говорили, в электронное письмо можно вставить присоединенные файлы. В зависимости от содержимого присоединенного файла применяется тот или иной метод кодирования.

Например, вот какие данные будут добавлены к телу сообщения в том случае, если в роли дополнительного выступает текстовый файл, содержащий только латинские символы:

------=_NextPart_000_0019_01BFE386.575BA590
Content-Type: text/plain;
  name="Text Document.txt"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
  filename="Text Document.txt"

Hello, Dear Friend!
I send to You this plain text file.
Alexandre Frolov

Email: alexandre@frolov.pp.ru
Web:   http://www.frolov.pp.ru
------=_NextPart_000_0019_01BFE386.575BA590--

Мы добавили к сообщению файл с именем Text Document.txt.

Как видите, в заголовке фрагмента указан тип данных application/octet-stream, имя файла и 7-битовая кодировка. Далее следует пустая строка и данные файла до строки разделителя.

Если к сообщению присоединяется двоичный файл, он будет закодирован по-другому:

------=_NextPart_000_004A_01BF8F69.E9392E40
Content-Type: application/octet-stream;
  name="RK.COM"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
  filename="RK.COM"

6RkqKEMpIDE5ODkgIEEuU3RyYWtob3YsIEFjYWRlbXlTb2Z0AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAASQ9CD00PTB9hH3QfUk91T3NPAehfAw4HuQYAv1wAvtwB9sNAdAO+0AHzpbQD
uAARuwAOuQABugAAvmAAxCzNEFpZtAG3AM0QtAK3AM0Q6DYDwwAAAAAAAAAAAAAAABwF
HBsAAP7LeAd0CP7LdBvDjMjDoCAELoQGQAC0AHUDgMwCqCB0A4DMAcMuoCAEitj2x4B1
AXQDgMsg9scCdQOAy0AuiB4gBDLDJEB0A+hS/8P7LvYGigECdQqA/BF0dfbEf3QFLv8u
HmwAWOiQAiR/M9uOwyaKJoUEvlwAJjoGSQR0BOiQAs+AJooB/jwDdxuADooBAbcIgPwO
DnQFvmQAtxDpjwAzyTwHdN88E3fOsQiA/A58aL5gALEOdGG+ZACxEOta6Cz/zzxVdPg8
IwK+XAA8AnRdPBJ0WTwQdH20CDwgdDs8IXQsPCN0KL5gALQOPAF0NTwRdDE8InQXvmQA
. . .
Lv8ubAC46AH/0M9QUx4zwI7Y9sNAuBwJjMt0Brg0Ers0EqN8AIkefgAfW1jDAAAAAAAA

------=_NextPart_000_004A_01BF8F69.E9392E40--

Мы пропустили часть листинга.

Как видите, для двоичного файла с именем RK.COM применена кодировка base64. В результате 8-битовый поток данных удалось представить в виде 7-битовых символов ANSI.

Кодировка ANSI

Американский национальный институт стандартов American National Standards Institute (ANSI) разработал стандарт для представления символов. Согласно этому стандарту, каждому из 256 символов соответствует одно 8-битовое значение (т.е. один символ кодируется одним байтом).

При этом первые 128 значений (с кодами в пределах от 0 до 127) соответствуют латинским буквам и символам. Значения от 128 до 255 предназначены для представления специальных символов и букв национальных алфавитов. Используя 7-битовую кодировку, можно представить только латинские буквы и символы.

Вы можете познакомиться со стандартами ANSI на сервере Web http://www.ansi.org.

Отправка почтового сообщения из сценария ASP

Как правило, в Интернет-магазинах, созданных за рубежом, оплата товаров производится с помощью кредитных карточек. Что же касается отечественных Интернет-магазинов, то в них преобладают другие формы: предварительный сбор заказов и оплата наличными или через сберегательный банк.

При этом торгующий сервер Web выступает в роли автоматизированной системы сбора заказов. Такие заказы обычно отправляются в магазин в виде электронных почтовых сообщений и затем обрабатываются сотрудниками магазина вручную. Для подтверждения покупки сделавшему заказ посетителю сервера перезванивают по телефону или запрашивают подтверждение по электронной почте. Это не слишком удобно, но в более или менее надежно. В качестве примера можно книжный магазин http://www.itbook.ru, созданный нами по заказу издательства «Русская Редакция».

Для практической реализации системы автоматизированного сбора предварительных заявок обычно используются программы CGI типа FormMail. Эти программы принимают данные из полей формы и отправляют их почтовому серверу при помощи протокола SMTP.

А сейчас речь пойдет о применении серверного элемента управления ActiveX с названием MTASend, разработанного Максимом Синевым из компании Spektrum Web Development (http://www.spektrum.org.ru) и предназначенным для отправки почты. Так как этот элемент реализует интерфейс автоматизации, его можно вызывать из клиентских и серверных сценариев, а также из любых программ, способных работать с объектами COM.

Элемент управления MTASend

Мы покажем, как использовать элемент управления MTASend для отправки почтовых сообщений из формы, расположенной на Вашем сервере Web.

Форма показана на рис. 12-13.

Рис. 12-13. Форма для отправки почты

При ее заполнении нужно указать адрес отправителя и получателя, тему письма Subject, а также адрес почтового сервера Вашего провайдера. Для отправки сообщения щелкните кнопку Отправить.

Если электронное письмо отправлено без ошибок, в окне браузера появится сообщение, показанное на рис. 12-14.

Рис. 12-14. Сообщение об успешной отправке почты

Полный исходный текст формы отправки сообщения показан в листинге 12-9.

Листинг 12-9 Вы найдете в файле chap12/MTASend/sendmail.html на прилагаемом к книге компакт-диске.

Форма ссылается на страницу ASP с именем mta.asp, которая и выполняет обращение к элементу управления MTASend:

<form method="POST" action="mta.asp">

В форме также определены поля с именами To, From, Subject, SMTPServer и Message:

<tr><td width="171">Кому:</td><td width="297">
<input type="text" name="To" size="20"></td>
  </tr><tr>
  <td width="171">От кого:</td><td width="297">
<input type="text" name="From" size="20"></td>
  </tr><tr> <td width="171">Subject:</td><td width="297">
<input type="text" name="Subject" size="20"></td>
  </tr><tr><td width="171">Сервер SMTP:</td><td width="297">
<input type="text" name="SMTPServer" size="20"></td>
  </tr><tr><td width="171">Сообщение:</td>   <td width="297">
<textarea rows="4" name="Message" cols="31"></textarea>

Исходный текст страницы mta.asp Вы найдете в листинге 12-10.

Листинг 12-10 Вы найдете в файле chap12/MTASend/mta.asp на прилагаемом к книге компакт-диске.

В самом начале этого листинга находится тег <OBJECT>, закрытый символами комментария:

<!-- <OBJECT RUNAT=SERVER ID=MTAMail CLASSID="CLSID:F477288F-ABB2-11D3-9DE9-204C4F4F5020"></OBJECT> -->

Вы можете использовать его вместо метода CreateObject для создания объекта MailMTA, средствами которого выполняется передача почты:

var MTAMail = Server.CreateObject("MailATL.MTA");

После того как сценарий создает объект MailMTA и записывает его в переменную MTAMail, он получает данные из полей формы и помещает их в соответствующие свойства:

MTAMail.To=Request("To")(1);
MTAMail.From=Request("From")(1);
MTAMail.Subject=Request("Subject")(1);
MTAMail.Message=Request("Message")(1);
MTAMail.SMTPServer=Request("SMTPServer")(1);
MTAMail.SMTPPort=25;

В свойство SMTPPort мы записываем значение 25, так этот порт используется стандартными почтовыми серверами.

Подготовив свойства, сценарий вызывает метод Send, выполняющий отправку почты:

MTAMail.Send();

Результат выполнения операции отправки почты сохраняется в свойстве Status, и наш сценарий может его проверить. Если почта отправлена без ошибок, сценарий записывает в создаваемый документ HTML соответствующее сообщение:

if(MTAMail.Status == 1)
{
%>
<html>
<head>
<title>Результаты отправки почты</title>
</head>
<body>
<p>Почта успешно отправлена
</body>
</html>
<%}

В противном случае в текст сообщения об ошибке включается код завершения из свойства Status, а также уточняющий код ошибки из свойства StatusEx:

else {
%>
<html>
<head>
<title>
Результаты отправки почты</title>
</head>
<body>
<p>
Ошибка. RC = <%=MTAMail.Status%> - <%=MTAMail.StatusEx%>
</body>
</html>
<%
}%>

Теперь мы кратко рассмотрим исходные тексты элемента управления MTASend.

Прежде всего, обратите внимание на файл MailATL.cpp (листинг 12-11).

Листинг 12-11 Вы найдете в файле chap12/MTASend/MailATL.cpp на прилагаемом к книге компакт-диске.

В нем нам интересны функции DllRegisterServer и DllUnregisterServer, предназначенные соответственно для регистрации и для отмены регистрации элемента управления:

STDAPI DllRegisterServer(void)
{
  WSADATA wsaData;
  WSAStartup(0, &wsaData);
  return _Module.RegisterServer(TRUE);
}

STDAPI DllUnregisterServer(void)
{
  WSACleanup();
  return _Module.UnregisterServer(TRUE);
}

В них мы вызываем функции WSAStartup и WSACleanup, предназначенные для инициализации сокетов и для освобождения ресурсов приложения, полученных для работы с сокетами.

Определение методов Вы найдете в файле MTA.cpp (листинг 12-12).

Листинг 12-12 хранится в файле chap12/MTASend/MTA.cpp на прилагаемом к книге компакт-диске.

Для сокращения объема исходных текстов, определяющих функционирование сходных методов, в этом файле определена макрокоманда MTAStringProperty:

#define MTAStringProperty(v) \
STDMETHODIMP MTA::get_##v(BSTR *pVal) \
{                                            \
  AFX_MANAGE_STATE(AfxGetStaticModuleState()) \
  CComBSTR bstrTemp(m_##v);             \
  *pVal = bstrTemp.Detach();            \
  return S_OK;                          \
}                                       \
STDMETHODIMP MTA::put_##v(BSTR newVal)  \
{                                       \
  AFX_MANAGE_STATE(AfxGetStaticModuleState()) \
  m_##v=newVal;                         \
  return S_OK;                          \
}                                     

Она определяет два метода, один из которых извлекает значение свойства из соответствующего поля класса, а другой — сохраняет значение в поле класса.

Вот как просто выглядит определение нескольких свойств, сделанное с помощью этой макрокоманды:

MTAStringProperty(From)
MTAStringProperty(To)
MTAStringProperty(SMTPServer)
MTAStringProperty(Message)
MTAStringProperty(Subject)
MTAStringProperty(Encoding)
MTAStringProperty(Format)

В классе MTA определен конструктор, выполняющий начальную инициализацию свойств значениями, взятыми из ресурсов элемента управления:

MTA::MTA()
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState( ));

  if(m_SMTPServer.LoadString(IDS_PORT))
     m_Port=atoi(m_SMTPServer);
  else
  {
     m_Port=25;
  }

  m_From.LoadString(IDS_FROM);
  m_To.LoadString(IDS_TO);
  m_SMTPServer.LoadString(IDS_SMTPSERVER);
  m_Subject.LoadString(IDS_SUBJECT);
  m_Encoding.LoadString(IDS_ENCODING);
  m_Format.LoadString(IDS_FORMAT);
}

В файле MTA.cpp Вы также найдете определение свойств SMTPPort, Status и StatusEx.

Файл send.cpp (листинг 12-13) содержит определение функций и методов, применяемых для установки соединения с почтовым сервером и для отправки сообщения.

Листинг 12-13 хранится в файле chap12/MTASend/send.cpp на прилагаемом к книге компакт-диске.

Рассмотрим метод Send, посылающий сообщение:

STDMETHODIMP MTA::Send()
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState())
  m_Status=1;
  if(Connect(m_SMTPServer, m_Port))
  {
     try
     {
       Transfer();
     }
     catch(...)
     {
       m_Status=3;
     }
  }
  else
  {
     m_Status=2;
  }
  Disconnect();
  return S_OK;
}

Прежде чем приступить к работе, этот метод устанавливает код завершения, равный 1, в поле m_Status. Этот код впоследствии можно извлечь из свойства Status.

Далее метод Send устанавливает соединение с почтовым сервером, вызывая метод Connect. Если эта процедура выполнена успешно, метод Send вызывает метод Transfer, выполняющий передачу данных. Далее программа отключается от почтового сервера при помощи метода Disconnect. В случае возникновения каких-либо ошибок устанавливается новое состояние в поле m_Status.

Исходный текст метода Connect мы рассмотрим ниже.

Получив управление, метод Connect инициализирует структуру srv_addr типа sockaddr_in, записывая в нее адрес почтового сервера:

m_StatusEx=0;
int rc=TRUE;
struct sockaddr_in   srv_addr,cli_addr;

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(Host);

Далее мы получаем адрес IP почтового сервера и сохраняем его в переменной he:

if(srv_addr.sin_addr.s_addr==INADDR_NONE)
{
  hostent *he=gethostbyname(Host);
  if(he) memcpy((char FAR *)&srv_addr.sin_addr.s_addr, 
     he->h_addr, he->h_length);
  else
  {
     TRACE("gethostbyname() failed code %d\n", WSAGetLastError());
     rc=FALSE; m_StatusEx=1;
  }
}

Полученный адрес копируется в поле s_addr структуры srv_addr.sin_addr.

На следующем этапе метод Connect получает сокет для связи с почтовым сервером:

srv_addr.sin_port=htons(Port);
m_Socket=socket(PF_INET,SOCK_STREAM,0);
if (m_Socket==INVALID_SOCKET)
{
  . . .
}

Этот сокет затем привязывается функцией bind к адресу IP почтового сервера:

cli_addr.sin_family=AF_INET;
cli_addr.sin_addr.s_addr=INADDR_ANY;        
cli_addr.sin_port=0;     

if(bind(m_Socket,(LPSOCKADDR)&cli_addr,sizeof(cli_addr))
     == SOCKET_ERROR)
{
  . . .
}

Далее выполняется соединение:

if(rc && connect(m_Socket,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))
     ==SOCKET_ERROR)
{
  . . .
}

Теперь мы займемся методом Transfer, выполняющим обмен данными с почтовым сервером по каналу, созданному методом Connect.

В начале с помощью макрокоманды WAIT этот метод дожидается получения от почтового сервера сообщения о готовности:

WAIT(220);

Когда сообщение, имеющее код 220, приходит, мы посылаем почтовому серверу команду HELO, указав ей в качестве параметра адрес почтового сервера:

WriteLn("HELO "+m_SMTPServer);

Для отправки почтовому серверу текстовой строки мы вызываем функцию WriteLn, определенную в нашей программе.

Дождавшись подтверждения с кодом 250, мы сообщаем серверу адрес отправителя:

CHECK(250);
WriteLn("MAIL FROM:<"+m_From+">");

Далее метод снова ожидает подтверждения и посылает почтовому серверу аналогичным образом адрес получателя и содержимое письма, включая все стандартные заголовки. Отправка заканчивается пустой строкой и строкой, содержащей одну точку в первой позиции:

WriteLn("");
WriteLn(".");
CHECK(250);

Далее наша программа отключается от почтового сервера:

WriteLn("QUIT");
CHECK(221);

Работа методов WriteLn и ReadLn. с помощью которых наш элемент управления общается с почтовым сервером, основана на применении функций send и recv. Эти функции предназначены для обмена данными через потоковые сокеты.

Метод Disconnect, выполняющий отключение от почтового сервера, закрывает сокет функцией closesocket:

void MTA::Disconnect()
{
  if(m_Socket!=INVALID_SOCKET)
  {
     closesocket(m_Socket);
     m_Socket=INVALID_SOCKET;
  }
  m_Stoped=-1;
}

Перекодировка текстовых строк

Перед тем как передавать сообщения с символами кириллицы по электронной почте, их необходимо перекодировать в КОИ-8. Эта кодировка общепринята для передачи сообщений с символами кириллицы. Хотя современные почтовые программы «умеют» показывать содержимое сообщений в различных кодировках, чтобы не заставлять получателя выполнять лишние действия, нужно отправлять сообщения в КОИ-8.

Для удобства выполнения перекодировки из Windows-1251 в КОИ-8 и обратно мы подготовили элемент управления ActiveX. В нем определены два метода — koi2w и w2koi (листинг 12-14).

Листинг 12-14 хранится в файле chap12/CyrCoder/CyrCoderMod.cpp на прилагаемом к книге компакт-диске.

Метод koi2w выполняет преобразование из КОИ-8 в Windows-1251:

STDMETHODIMP CCyrCoderMod::get_koi2w(BSTR *src, BSTR *dst)
{
  USES_CONVERSION;

  if(dst == NULL)
    return E_POINTER;

  m_szSrc= OLE2A(*src);
  int cbBuf=strlen(m_szSrc);
 
  LPSTR szBuf;
  szBuf=(LPSTR)CoTaskMemAlloc(cbBuf + 1);

  UCHAR *table, ch;
 
  table = kw;

  for(int i=0; i<cbBuf; i++)
  {
  ch=m_szSrc[i];

    if(ch >= 128)
      szBuf[i] = table[ch & 0177];
  else
      szBuf[i] = ch;
  }

  szBuf[cbBuf]='\0';

  CComBSTR bstrTemp;
  bstrTemp = A2OLE(szBuf);

  if(!bstrTemp)
    return E_OUTOFMEMORY;

  *dst = bstrTemp.Detach();
  CoTaskMemFree(szBuf);
  return S_OK;
}

Макрокоманда USES_CONVERSION указывает на то, что в данном методе будут использованы макрокоманды преобразования строк из Unicode в ANSI. В данном случае мы применяем макрокоманду OLE2A, преобразующую исходную строку Unicode^

m_szSrc= OLE2A(*src);
int cbBuf=strlen(m_szSrc);

Результат преобразования сохраняется в буфере m_szSrc, а длина полученной таким образом строки — в переменной cbBuf.

В переменной szBuf мы готовим указатель на выходной буфер, в который будет записана перекодированная строка:

LPSTR szBuf;
szBuf=(LPSTR)CoTaskMemAlloc(cbBuf + 1);

Память для этой строки должна быть выделена функцией CoTaskMemAlloc.

Далее мы выполняем перекодировку в цикле, используя для этого таблицу перекодировки kw:

UCHAR *table, ch;
table = kw;
for(int i=0; i<cbBuf; i++)
{
  ch=m_szSrc[i];
  if(ch >= 128)
    szBuf[i] = table[ch & 0177];
  else
    szBuf[i] = ch;
}

Если код символа больше 128, то мы дополнительно используем маску.

Таблица перекодировки КОИ-8 в Windows-1251 выглядит следующим образом:

UCHAR kw[] = {128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
254,224,225,246,228,229,244,227,245,232,233,234,235,236,237,238,
239,255,240,241,242,243,230,226,252,251,231,248,253,249,247,250,
222,192,193,214,196,197,212,195,213,200,201,202,203,204,205,206,
207,223,208,209,210,211,198,194,220,219,199,216,221,217,215,218};

Она определена в файле CyrCoder.cpp (листинг 12-15).

Листинг 12-15 хранится в файле chap12/CyrCoder/CyrCoder.cpp на прилагаемом к книге компакт-диске.

После завершения цикла метод закрывает строку двоичным нулем в выполняет обратное преобразование из ASCII в Unicode:

szBuf[cbBuf]='\0';
CComBSTR bstrTemp;
bstrTemp = A2OLE(szBuf);

Полученная таким образом строка записывается в переменную bstrTemp.

Далее результат преобразования передается методу Detach, после чего память, выделенная для перекодированной строки, освобождается функцией CoTaskMemFree:

*dst = bstrTemp.Detach();
CoTaskMemFree(szBuf);

Обратное перекодирование из Windows-1251 в КОИ-8 выполняется методом w2koi:

STDMETHODIMP CCyrCoderMod::get_w2koi(BSTR *src, BSTR *dst)
{
  USES_CONVERSION;
  if(dst == NULL)
    return E_POINTER;
  m_szSrc= OLE2A(*src);
  int cbBuf=strlen(m_szSrc);
  LPSTR szBuf;
  szBuf=(LPSTR)CoTaskMemAlloc(cbBuf + 1);
  UCHAR *table, ch;
  table = wk;
  for(int i=0; i<cbBuf; i++)
  {
  ch=m_szSrc[i];

    if(ch >= 128)
      szBuf[i] = table[ch & 0177];
  else
      szBuf[i] = ch;
  }

  szBuf[cbBuf]='\0';
  CComBSTR bstrTemp;
  bstrTemp = A2OLE(szBuf);

  if(!bstrTemp)
    return E_OUTOFMEMORY;

  *dst = bstrTemp.Detach();
  CoTaskMemFree(szBuf);
  return S_OK;
}

Он работает аналогично только что рассмотренному методу koi2w, но использует таблицу перекодировки wk (листинг 12-15):

UCHAR wk[] = {128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
225,226,247,231,228,229,246,250,233,234,235,236,237,238,239,240,
242,243,244,245,230,232,227,254,251,253,255,249,248,252,224,241,
193,194,215,199,196,197,214,218,201,202,203,204,205,206,207,208,
210,211,212,213,198,200,195,222,219,221,223,217,216,220,192,209};

Ниже мы показали фрагмент кода сценария WSH, выполняющего рассылку новостей по электронной почте. В этом фрагменте демонстрируется применение только что рассмотренного элемента управления ActiveX для перекодировки текстовой строки из Windows-1251 в КОИ-8:

var CyrCoder = WScript.CreateObject("CyrCoder.CyrCoder");
var subj=CyrCoder.w2koi("Новости издательства ‘Русская Редакция’");

Полностью исходный текст этого сценария приведен в листинге 12-16.

Листинг 12-16 хранится в файле chap12/CyrCoder/SendNews.js на прилагаемом к книге компакт-диске.

Преобразование числа в сумму прописью

При необходимости автоматизированной выписки счетов и аналогичной бухгалтерской документации встает задача преобразования числовых значений в сумму прописью. Например, число 2345 нужно преобразовать в строку вида «Две тысячи триста сорок пять рублей 00 копеек».

В Интернете можно найти большое количество исходных текстов программ, решающих данную задачу. При создании Интернет-магазина http://www.itbook.ru мы использовали элемент управления ActiveX с названием CyrMoney, разработанный Максимом Синевым.

Преобразование рублей выполняется методом Convert, определенным следующим образом (полный текст Вы найдете в листинге 12-17):

STDMETHODIMP CMoneyConv::Convert(UINT i, BSTR *Result)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState())
  CString rc;
  ConvertTxt(rc, i);
  m_rubl=rc;
  *Result=m_rubl.m_str;
  return S_OK;
}

Листинг 12-17 хранится в файле chap12/RusMoney/MoneyConv.cpp на прилагаемом к книге компакт-диске.

Преобразование выполняется методом ConvertTxt, который представлен ниже:

void ConvertTxt(CString &Str, DWORD Val)
{
  if(ConvertTxt1000(Str, Val, TRUE, Val<1000)) {
    int i10=(Val%10);
    int i100=(Val%100);
    if(i10==1 && i100!=11) Str+=" ðóáëü";
    else if((i10==2 || i10==3 || i10==4) && i100/10!=1) Str+="
рубля";
    else if(i10==2) Str+="
рублей";
    else Str+="
рублей";
    }
  else
    Str="
рублей";

  Val/=1000;

  if(Val>0) {
    CString Wk;
    if(ConvertTxt1000(Wk, Val, FALSE, Val<1000)) {
      int i10=(Val%10);
      int i100=(Val%100);
      if(i10==1 && i100!=11) Wk+="
тысяча ";
      else if((i10==2 || i10==3 || i10==4) && i100/10!=1) Wk+="
тысячи ";
      else Wk+="
тысяч ";
      Str=Wk+Str;
      }
    }
  Val/=1000;
  if(Val>0) {
    CString Wk;
    if(ConvertTxt1000(Wk, Val, TRUE, Val<1000)) {
      int i10=(Val%10);
      int i100=(Val%100);
      if(i10==1 && i100!=11) Wk+="
миллион ";
      else if((i10==2 || i10==3 || i10==4) && i100/10!=1) Wk+="
миллиона ";
      else Wk+="
миллионов ";
      Str=Wk+Str;
      }
    }
  Val/=1000;
  if(Val>0) {
    CString Wk;
    if(ConvertTxt1000(Wk, Val, TRUE, Val<1000)) {
      int i10=(Val%10);
      int i100=(Val%100);
      if(i10==1 && i100!=11) Wk+="
миллиард ";
      else if((i10==2 || i10==3 || i10==4) && i100/10!=1) Wk+="
миллиарда ";
      else Wk+="
миллиардов ";
      Str=Wk+Str;
      }
    }
  }

Как видите, в зависимости от тех или иных условий метод выбирает нужный падеж для слов «рубль», «тысяча», «миллион» и «миллиард».

Ниже мы привели фрагмент сценария ASP, в котором используется элемент управления ActiveX с названием MoneyConv для преобразования числовой стоимости покупки в сумму прописью:

<%
var mc = Server.CreateObject("MoneyConv.MoneyConv.1");
%>
<b>
Сумма: <%=mc.Convert(FullTotal)%> 00 копеек</b>

[Назад] [Содержание] [Дальше]