WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 21 | 22 || 24 |

«Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...»

-- [ Страница 23 ] --

Пакет UTL_FILE Стандартный пакет UTL_FILE позволяет читать и создавать текстовые файлы в файловой системе сервера в среде PL/SQL. Здесь существенны следующие ключевые слова. • Текстовые файлы. Пакет UTL_FILE позволяет читать и создавать простые текстовые файлы. В частности, его нельзя использовать для чтения или создания двоичных файлов. Специальные символы, содержащиеся в двоичных данных, приводят к некорректной работе пакета UTL_FILE. • В файловой системе сервера. Пакет UTL_FILE позволяет читать и записывать файлы только в файловой системе сервера баз данных. Он не позволяет читать или записывать в файловую систему компьютера, на котором работает клиент, если последний не подключен локально к серверу. Пакет UTL_FILE подходит для создания отчетов и сброса данных из базы в текстовые файлы, а также для чтения и загрузки данных. В главе 9 первой части книги представлен полный пример использования пакета UTL_FILE для создания текстового файла в формате, упрощающем загрузку. Пакет UTL_FILE также помогает при отладке. В главе 21, посвященной средствам тщательного контроля доступа, представлен пакет DEBUG. Этот пакет интенсивно использует средства пакета UTL_FILE для записи сообщений в файловую систему. Пакет UTL_FILE — очень полезен, но, используя его, нужно помнить об определенных ограничениях. В противном случае результаты могут оказаться некорректными (причем, выявиться это может не при тестировании, а при внедрении) и вызовут разочарование.

Приложение А Я рассмотрю ряд проблем, часто встречающихся при использовании пакета UTL_FILE, в том числе: • установку параметра инициализации UTL_FILE_DIR;

• доступ к дискам других компьютеров (сетевым дискам) в среде Windows (проблем с этим в среде Unix нет);

• обработку исключительных ситуаций, возникающих при использовании пакета UTL_FILE;

• применение пакета UTL_FILE для периодического пересоздания статических Web-страниц;

• печально известное ограничение — 1023 байта;

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

Параметр инициализации UTL_FILE_DIR Это наиболее существенная особенность использования пакета UTL_FILE, который всегда работает от имени пользователя — владельца СУБД Oracle, — ввод-вывод выполняет выделенный или разделяемый сервер, всегда работающий от имени пользователя oracle. С учетом того, что работа с файлами выполняется от имени пользователя oracle, а пользователь oracle может читать и записывать файлы данных, файлы конфигурации и т.п., — не стоит разрешать доступ с помощью средств UTL_FILE ко всем каталогам. В файле параметров инициализации необходимость явно перечислять каталоги, к которым необходим доступ на запись, — это средство защиты, а не излишняя сложность. Если бы пакет UTL_FILE позволял записывать файлы в доступных пользователю oracle каталогах, то любой пользователь с помощью функции UTL_FILE.FOPEN смог бы переписать файлы данных системы. Это, конечно же, недопустимо. Поэтому администратор базы данных должен явно открывать доступ к конкретным каталогам. Нельзя разрешить доступ к корневому каталогу поддерева, позволив тем самым обращаться ко всем каталогам этого поддерева, — надо явно перечислить все каталоги, в которых предполагается чтение и изменение файлов с помощью пакета UTL_FILE. Следует помнить, что изменять параметр инициализации в процессе работы сервера нельзя. Для добавления или удаления каталога необходимо перезапускать экземпляр. Параметр инициализации UTL_FILE_DIR можно задавать одним из следующих способов: u t l _ f i l e _ d i r = (c:\temp,c:\temp2) или utl_file_dir = c:\temp utl_file_dir = c:\temp Другими словами, можно либо задавать список каталогов через запятую, в круглых скобках, либо перечислять каталоги по одному в последовательных строках. Главное здесь — "в последовательных строках". Если файл параметров инициализации (init.ora) завершается следующими строками:

Пакет UTL_FILE utl_file_dir = c:\temp timed_statistics=true utl_file_dir = c:\temp2 учтена будет только последняя запись — UTL_FILE_DIR. Первый каталог будет проигнорирован. Это может сбивать с толку, поскольку никаких предупреждающих сообщений или записей в файле alert.log, свидетельствующих об игнорировании записи UTL_FILE_DIR, не появляется. Все записи UTL_FILE_DIR в файле параметров инициализации должны идти подряд. Хочу предупредить об одной особенности использования параметра инициализации на платформе Windows. Если добавить к значению параметра UTL_FILE_DIR завершающую обратную косую черту (\), например, так: utl_file_dir = c:\temp\ utl_file_dir = c:\temp2 при запуске сервера будет выдано следующее сообщение об ошибке: SVRMGR> Startup LRM-00116: syntax error at 'c:\terrputl_file_' following '=' LRM-00113: error when processing file -> 'C:\oracle\admin\tkyte816\pfile\init.ora' ORA-01078: failure in processing system parameters Дело в том, что символ \ является управляющим, если стоит последним в строке файла параметров инициализации. Он позволяет продолжить длинную запись на следующей строке. Чтобы избежать такой конкатенации строк, нужно просто указывать две обратных косых подряд: utl_file_dir = c:\temp\\ utl_file_dir = c:\oracle И последняя особенность этого параметра инициализации. Если в файле параметров инициализации использована завершающая обратная косая черта в имени каталога, необходимо указывать завершающую обратную косую и в вызовах функции fopen. Если обратная косая не указана в файле параметров инициализации, не нужно ее задавать и в вызовах функции fopen. Параметр функции fopen, задающий имя каталога, должен буквально совпадать с одним из значений, заданных в файле параметров инициализации.

Обращение к сетевым дискам в Windows Эта задача часто вызывает затруднения, особенно у тех, кто привык работать в ОС Unix. В ОС Unix смонтированное устройство (например, смонтированный клиентом NFS удаленный диск) немедленно становится доступным всем пользователям, независимо от того, в каком сеансе оно смонтировано. У каждого пользователя могут быть свои права доступа к нему, но смонтированный диск — это атрибут системы, а не конкретного сеанса. В среде Windows все не так. На сервере может работать несколько пользовательских сеансов, каждый со своим набором доступных "дисков". Может оказаться, что, зарегистрировавшись, я обнаружу сетевой ресурс — диск D:, физически находящийся на дру Приложение А гой машине. Но это не означает, что процесс, работающий на этой машине, сможет увидеть этот диск. Вот тут и возникают проблемы. Многие пользователи регистрируются на сервере и видят диск D:. Они включают в файл параметров конфигурации запись UTL_FILE_dir = d:\reports — каталог, в котором предполагается создание отчетов с помощью средств пакета UTL_FILE. При выполнении, однако, они получают сообщение об ошибке: ERROR at line 1: ORA-06510: PL/SQL: unhandled user-defined exception ORA-06512: at "SYS.UTL_FILE", line 98 ORA-06512: at "SYS.UTL_FILE", line 157 С помощью обработчика исключительных ситуаций (ниже представлен код, который я обычно использую) это сообщение можно сделать более информативным, например: ERROR at line 1: ORA-20001: INVALID_PATH: File location or filename was invalid. ORA-06512: at "TKYTE.CSV", line 51 ORA-06512: at line 2 Итак, с точки зрения пользователя каталог D:\reports вполне допустим. Его можно найти с помощью программы Проводник (Explorer). Можно открыть окно командной строки DOS и обнаружить в нем этот каталог. Только СУБД Oracle его не видит. Причина в том, что при запуске системы диска D: нет и, более того, учетная запись, от имени которой работает сервер Oracle, по умолчанию вообще не может обращаться к сетевым ресурсам. Пытайтесь сколько угодно, монтируйте диск любым способом — сервер Oracle его не увидит. При создании экземпляра Oracle службы, поддерживающие его, настраиваются для регистрации в системе от имени учетной записи SYSTEM (как системные), а эта учетная запись имеет ограниченные привилегии, и домены Windows NT ей недоступны. Чтобы обращаться к другой машине, работающей под управлением Windows NT, служба OracleServiceXXXX должна зарегистрироваться в соответствующем домене Windows NT от имени пользователя, имеющего доступ к диску, который предполагается использовать с помощью пакета UTL_FILE. Чтобы изменить стандартные параметры регистрации для служб Oracle, выберите (в ОС Windows NT): Control Panel | Services | OracleServiceXXXX | Startup | Log On As;

(где XXXX — имя экземпляра) B OC Windows 2000 следует выбрать: Control Panel | Administrative Tools | Services | OracleServiceXXXX | Properties | Log On Tab;

(где XXXX — имя экземпляра) Выберите переключатель This Account и введите соответствующую информацию о регистрации в домене. После настройки служб для работы от имени пользователя с соответствующими привилегиями, параметр UTL_FILE_DIR можно задать одним из двух способов. • С помощью сопоставленного диска. Чтобы использовать сопоставленный удаленному ресурсу диск, необходимо, чтобы пользователь, от имени которого запуска Пакет UTL_FILE ется служба, настроил диск, указанный в значении параметра UTL_FILE_DIR, и был зарегистрирован на сервере при использовании пакета UTL_FILE. • С помощью универсальных соглашений по именованию. Использование универсальных соглашений по именованию (Universal Naming Conventions — UNC) предпочтительнее сопоставления дисков, поскольку не требует регистрации пользователя. При этом значение параметра инициализации UTL_FILE_DIR задается в виде \\<имя машины >\<имя общего ресурса >\<путь>>. Естественно, сервер Oracle после изменения свойств сетевой службы необходимо перезапустить.

Обработка исключительных ситуаций При возникновении ошибки пакет UTL_FILE возбуждает исключительную ситуацию. К сожалению, он возбуждает исключительные ситуации, задаваемые пользователем, — они определены в спецификации пакета. Если эти исключительные ситуации не перехвачены по имени, выдаются совершенно бесполезные сообщения об ошибках: ERROR at line 1: ORA-06510: PL/SQL: unhandled user-defined exception ORA-06512: at "SYS.UTL_FILE", line 98 ORA-06512: at "SYS.UTL_FILE", line 157 О самой ошибке в этом сообщении ничего не сказано. Для решения этой проблемы надо включить обращения к пакету UTL_FILE в блок обработки исключительных ситуаций, где каждая из них перехватывается по имени. Я предпочитаю преобразовывать исключительные ситуации в вызовы процедуры RAISE_APPLICATION_ERROR. Это позволяет задать код ошибки ORA- и выдать более информативное сообщение об ошибке. В предыдущем примере мы использовали этот прием для преобразования представленного выше сообщения об ошибке в следующее:

ORA-20001: INVALID_PATH: File location or filename was invalid.

Это сообщение намного полезнее. Я всегда использую блок обработки исключительных ситуаций следующего вида:* exception when utl_file.invalid_path then raise_application_error(-20001, — ошибка в имени файла 'INVALID_PATH: File location or filename was invalid.');

when utl_file.invalid_mode then raise_application_error(-20002, — недопустимое значение режима -> открытия файла 'INVALID_MODE: The open_mode parameter in FOPEN was invalid.');

when utl_file.invalid_filehandle then raise_application_error(-20002, — недопустимый дескриптор файла 'INVALID_FILEHANDLE: The file handle was invalid.');

* Исходные сообщения об ошибках приводятся на английском, а в строке с кодом ошибки добавлен комментарий на русском языке с описанием причины ее возникновения. - Прим. научн. ред.

Приложение А when utl_file.invalid_operation then raise_application_error(-20003, — файл нельзя открыть или -> обработать в указанном режиме 'INVALID_OPERATION: The file could not be opened or operated on as requested.');

when utl_file.read_error then raise_application_error(-20004, — в ходе чтения произошла ошибка -> операционной системы 'READ_ERROR: An operating system error occurred during the read operation.');

when utl_file.write_error then raise_application_error(-20005, — в ходе записи произошла ошибка -> операционной системы 'WRITE_ERROR: An operating system error occurred during the write operation.');

when utl_file.internal_error then raise_application_error(-20006, — неизвестная ошибка в PL/SQL 'INTERNAL_ERROR: An unspecified error in PL/SQL.');

end;

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

Как сбросить Web-страницу на диск?

Этот вопрос задают так часто, что я решил дать ответ здесь. Предполагается, что используется Oracle WebDB, Oracle Portal или другие процедуры на базе средств Web Toolkit (пакетов htp). Хотелось бы не генерировать динамически отчет, который можно получить с помощью этих средств, отдельно для каждого пользователя, а периодически, один раз в X часов или минут, создавать статический файл с отчетом. Именно так я и генерирую начальную страницу на своем сайте. Вместо того чтобы генерировать ее динамически для каждого из тысяч поступающих за этот период обращений, раз в 5 минут я генерирую статическую начальную страницу заново на основе динамических данных. Это существенно сокращает ресурсы, необходимые для поддержки сайта. Такой прием я применяю для популярных динамических станиц, базовые данные которых меняются сравнительно редко. Ниже представлена универсальная процедура, которую я для этого использую: create or replace procedure dump_page(p_dir in varchar2, p_fname in varchar2) is l_thePage htp.htbuf_arr;

l_output utl_file.file_type;

l_lines number default 99999999;

begin l_output := utl_file.fopen(p_dir, p_fname, 'w', 32000);

owa.get_page(l_thePage, l_lines);

for i in 1.. l_lines loop Пакет UTL_FILE utl_file.put(l_output, l_thePage(i));

end loop;

utl_file.fclose (l_output) ;

end dump_page;

/ Все очень просто. Необходимо открыть файл, получить HTML-страницу, выдать каждую ее строку в файл и закрыть его. Если вызвать эту процедуру после WebDB-npoцедуры, она сохранит результат работы WebDB-процедуры в заданном файле. Единственная проблема при использовании этого подхода состоит в том, что WebDBпроцедура выполняется непосредственно, а не через Web. Если в коде WebDB-процедуры используется среда CGI, эта процедура не выполнится, поскольку среда не настроена. Для решения этой проблемы достаточно использовать небольшой фрагмент кода для настройки среды: declare nm owa.vc_arr;

vl owa.vc_arr;

begin nm(l) := 'SERVER_PORT';

vl(l) := '80';

owa.init_cgi_env(nm.count, nm, vl);

— здесь выполните необходимую webdb-процедуру dump_page('directory', 'filename');

end;

/ Например, если WebDB-процедура проверяет, запущена ли она с порта 80 на сервере, надо создать для нее соответствующую среду. В этом блоке надо задать и все остальные переменные среды, существенные для приложения. Теперь осталось только перечитать раздел, посвященный пакету DBMS_JOB, и обеспечить периодическое выполнение этого блока кода.

Ограничение длины строки — 1023 байт Когда-то у пакета UTL_FILE было ограничение длины записываемой в файл строки: не более 1023 байт. В противном случае возбуждалась исключительная ситуация, и процедура пакета UTL_FILE не выполнялась. К счастью, в версии Oracle 8.0.5 добавлена новая версия функции FOPEN, позволяющая при открытии файла задавать максимальнуюдлину строки вплоть до 32 Кбайт. 32 Кбайт — максимальный размер переменной в PL/SQL, и такой длины в большинстве случаев хватает. К сожалению, в документации эта перегруженная версия функции FOPEN описана через несколько страниц после исходной функции. Поэтому многие пользователи об этой возможности и не подозревают. Я по-прежнему получаю много вопросов об этом, хотя сейчас используются версии, начиная с 8.1.7. Пользователя не замечают перегруженную версию функции FOPEN;

они сталкиваются с ограничением и ищут способы обойти его. Ответ простой, но, чтобы найти его, надо прочитать описание всех функций пакета UTL_FILE!

Приложение А Для решения этой проблемы необходимо использовать пакет UTL_FILE так, как это делалось в представленной выше процедуре DUMP_PAGE. Четвертый параметр вызова функции UTL_FILE.FOPEN задает максимальную длину строки текста, которую предполагается использовать. Я допускал использование строк длиной до 32000 байт.

Чтение содержимого каталога Этой возможности в пакете UTL_FILE не хватает. Часто необходимо создать периодически выполняющееся задание для просмотра каталога в поисках новых файлов и обработки этих файлов, например загрузки их содержимого в базу данных. К сожалению, стандартного способа прочитать список файлов в каталоге в языке PL/SQL нет. Можно, однако, реализовать эту возможность с помощью небольшого фрагмента кода на языке Java. Следующий пример демонстрирует, как это сделать. Сначала я создам пользователя с минимальным набором привилегий, необходимых для выполнения действий по загрузке данных и получения списка файлов в каталоге /tmp. Если необходимо читать содержимое других каталогов, придется добавить соответствующие вызовы dbmsJava.grant_permission (подробнее о них см. в главе 19) или заменить /tmp на *, что позволит получить список файлов любого каталога. SQL> connect system/manager system@DEV816> drop user dirlist cascade;

User dropped. system@DEV816> grant create session, create table, create procedure 2 to dirlist identified by dirlist;

Grant succeeded. system@DEV816> begin 2 dbms_java.grant_permission 3 ('DIRLIST', 4 'java.io.FilePermission', 5 '/tmp', 6 'read');

7 end;

8/ PL/SQL procedure successfully completed. Затем, подключившись от имени пользователя DirList, мы создаем глобальную временную таблицу в его схеме (для хранения списка файлов каталога). Так, через временную таблицу, мы сможем получить результаты выполнения хранимой процедуры на языке Java в вызывающей среде. Можно использовать для этого и другие способы (строки, массивы и т.п.). SQL> connect dirlist/dirlist Connected. dirlist@DEV816> create global temporary table DIR_LIST 2 (filename varchar2(255)) 3 on commit delete rows 4/ Table created.

Пакет UTL_FILE Теперь создадим хранимую процедуру на языке Java для получения списка файлов в указанном каталоге. Чтобы упростить программирование, я использую средства препроцессора SQLJ — это позволяет избежать написания большого количества вызовов JDBC: dirlist@DEV816> create or replace 2 and compile java source named "DirList" 3 as 4 import java.io.*;

5 import java.sql.*;

6 7 public class DirList 8{ 9 public static void getList(String directory) 10 throws SQLException 11 { 12 File path = new File(directory);

13 String[] list = path.list();

14 String element;

15 16 for (int i = 0;

i < list.length;

i++) 17 { 18 element = list[i];

19 #sql { INSERT INTO DIR_LIST (FILENAME) 20 VALUES (:element) };

21 } 22 } 23 24 } 25 / Java created. Затем необходимо создать процедуру сопоставления, связывающую языки PL/SQL и Java. Она достаточно проста: dirlisteDEV816> create or replace 2 procedure get_dir_list(p_directory in varchar2) 3 as language java 4 name 'DirList.getList(java.lang.String)';

5/ Procedure created.

Теперь можно использовать процедуру get_dir_list:

dirlist@DEV816> exec get_dir_list('\tmp');

PL/SQL procedure successfully completed. dirlist@DEV816> select * from dir_list where rownum < 5;

FILENAME -----------------lost+found.rpc_door ps_data.pcmcia Приложение А Вот и все. В соответствующей временной таблице теперь можно получить список файлов каталога. К данным таблицы можно применять фильтры, например LIKE, или сортировать результаты.

Резюме Пакет UTL_FILE — замечательная утилита, которая пригодится во многих приложениях. В этом разделе мы рассмотрели как настроить сервер для использования средств пакета UTL_FILE, и описали особенности его работы. Мы рассмотрели наиболее часто возникающие проблемы при использовании пакета UTL_FILE, в частности обращение к сетевым дискам в среде Windows, ограничение длины строки 1023 байтами, и обработку исключительных ситуаций. Для каждой из этих проблем были представлены решения. Мы также изучили ряд утилит, которые можно создать с помощью пакета UTL_FILE, в частности процедуру UNLOADER, описанную в главе 9, средства чтения списка файлов каталога и сброса Web-страницы на диск.

Пакет UTL_HTTP В этом разделе мы рассмотрим, когда и как использовать пакет UTL_HTTP. Кроме того, я хочу представить новую расширенную версию пакета UTL_HTTP, созданную на основе типа SocketType, который рассматривается в разделе, посвященном пакету UTL_TCP. Его производительность сравнима с обеспечиваемой стандартным пакетом UTL_HTTP, а возможности — намного шире. Стандартный пакет UTL_HTTP, поставляемый вместе с сервером, реализует весьма упрощенный подход. Он содержит две функции. • UTL_HTTP.REQUEST: возвращает до 2000 первых байт содержимого с заданным адресом URL. • UTL_HTTP.REQUEST_PIECES: возвращает PL/SQL-таблицу элементов типа VARCHAR2(2000). Если конкатенировать последовательно все элементы, будет получено содержимое соответствующей страницы.

В пакете UTL_HTTP, однако, не хватает многих возможностей. • Нельзя проверить заголовки HTTP. Это не позволяет выдавать сообщения об ошибках. Нельзя, например, различить ошибки доступа Not Found и Unauthorized. • Нельзя пересылать информацию на Web-сервер с помощью метода POST. Можно использовать только метод GET. Кроме того, не поддерживается метод HEAD протокола HTTP. • С помощью пакета UTL_HTTP нельзя получать двоичные данные.

Приложение А • Интерфейс запроса страницы по частям (REQUEST_PIECES) неочевиден — намного проще было бы использовать данные типа CLOB или BLOB для возврата данных в виде "потока" (что обеспечило бы заодно и доступ к двоичным данным). • Пакет не поддерживает "ключики" (cookies). • Пакет не поддерживает даже простейшую аутентификацию. • В пакете нет методов кодирования адреса URL. Одна из возможностей, которые пакет UTL_HTTP поддерживает, — это использование протокола SSL. C помощью диспетчера Oracle Wallet можно выполнять запросы по протоколу HTTPS (HTTPS — это реализация протокола HTTP поверх SSL). Я продемонстрирую использование пакета UTL_HTTP для доступа по протоколу SSL, но соответствующие возможности в пакете HTTP_PKG реализовывать не будем. Полный текст пакета HTTP_PKG вследствие большого размера в этом разделе не приводится;

он доступен на сайте издательства Wrox по адресу http://www.wrox.com.

Возможности пакета UTL_HTTP Рассмотрим сначала функциональные возможности пакета UTL_HTTP, поскольку предполагается реализовать аналогичные в пакете HTTP_PKG. Простейший вариант использования средств пакета UTL_HTTP представлен далее. В этом примере myserver — имя моего Web-сервера. Вы, разумеется, должны выполнить этот пример, обращаясь в Webсерверу, который вам доступен: ops$tkyte@DEV816> select utl_http.request('http://myserver/') from dual;

UTL_HTTP.REQUEST('HTTP://MYSERVER/') 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с Можно просто вызвать функцию UTL_HTTP.REQUEST, передав ей адрес URL. Пакет UTL_HTTP подключится к соответствующему Web-серверу и запросит (GET) указанную страницу, а затем вернет первые 2000 ее символов. Как уже было сказано, не пытайтесь использовать указанный в примере адрес URL — это адрес моего рабочего Web-сервера в корпорации Oracle. Вы не сможете к нему добраться — по истечении времени ожидания будет выдано сообщение об ошибке. Большинство сетей сегодня защищено брандмауэрами (межсетевыми экранами). Если необходимая страница доступна только через промежуточный сервер брандмауэра, я мог Пакет UTL_HTTP бы запросить ее и так. Обсуждение брандмауэров и промежуточных серверов выходит за рамки этой книги. Однако если известно имя хоста, на котором работает промежуточный сервер, можно выбрать через него страницу из Internet следующим образом: ops$tkyte@DEV816> select utl_http.request('http://www.yahoo.com', 'www-> proxy') from dual;

UTL_HTTP.REQUEST('HTTP://WWW.YAHOO.COM','WWW-PROXY') 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с

select utl_http.request('http://www.yahoo.com', 2 'myserver:8000') from dual 3/ UTL_HTTP.REQUEST('HTTP://WWW.YAHOO.COM','MYSERVER:8000') 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с declare 2 pieces utl_http.html_pieces;

3 n number default 0;

4 l_start number default dbms_utility.get_time;

5 begin 6 pieces := 7 utl_http.request_pieces(url => 'http://vww.oracle.com/', 8 max_pieces => 99999, 9 proxy=> 'www-proxy');

10 for i in 1.. pieces.count 11 loop 12 loop 13 exit when pieces(i) is null;

14 dbms_output.put_line(substr(pieces(i),l,255));

15 pieces(i) := substr(pieces(i), 256);

16 end loop;

17 end loop;

18 end;

19 / Приложение А 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с Функцию REQUEST_PIECES нельзя вызывать в SQL-операторах, поскольку она возвращает не SQL-тип, а PL/SQL-таблицу. Поэтому функцию REQUEST_PIECES можно использовать только в PL/SQL-блоках. В представленном выше примере мы запросили первые 99999 фрагментов Web-страницы http://www.oracle.com/, каждый из которых длиной 2000 байт. При этом используется промежуточный сервер www-proxy. Мы должны передать функции REQUEST_PIECES количество фрагментов по 2000 байт, которые мы хотим получить (обычно я задаю очень большое значение, поскольку хочу получить всю страницу). Если на запрошенной странице вас всегда интересуют первые 5000 байт информации, для ее получения можно запросить 3 фрагмента.

Добавление поддержки протокола SSL в пакете UTL_HTTP Пакет UTL_HTTP также поддерживает использование протокола SSL (Secure Sockets Layer — уровень защищенных сокетов). Если вы не знакомы с протоколом SSL и его назначением, прочитайте краткое описание на странице http://www.rsasecurity.com/ rsalabs/faq/5-l-2.html. Обе функции, REQUEST и REQUEST_PIECES, пакета UTL_HTTP поддерживают получение URL, защищенных протоколом SSL. Однако в документации это описано, мягко говоря, в общих чертах. Поддержка протокола SSL обеспечивается путем передачи двух дополнительных параметров функциям UTL_HTTP.REQUEST и UTL_HTTP.REQUEST_PIECES. Это параметры WALLET_PATH и WALLET_PASSWORD. Сервер Oracle использует понятие "бумажник" (wallet) по аналогии с тем, как люди хранят важные документы, — паспорт, водительские права и кредитные карточки принято держать в бумажнике на случай, если их придется предъявлять. Сервер Oracle держит в своем "бумажнике" регистрационную информацию для протокола SSL. Параметр WALLET_PATH задает каталог на машине, где работает сервер базы данных, в котором хранится этот "бумажник". Доступ к бумажнику защищен паролем, чтобы другой пользователь не мог зарегистрироваться от вашего имени. Пароль передается как параметр WALLET_PASSWORD — он используется для получения доступа к "бумажнику". Пароль предотвращает копирование каталога "бумажника" другими пользователями, пытающимися выдать себя за вас, поскольку без него они не смогут работать с этим "бумажником". Это аналогично использованию PIN-кода при снятии денег с карточки в банкомате. Если кто-то украдет вашу кредитку, чтобы добраться до счетов ему понадобится ввести ваш PIN-код. Метафора "бумажника" используется не только сервером Oracle, но и Web-браузерами. Интересно, как при подключении к сайту, например, http://www.amazon.com, вы Пакет UTL HTTP можете удостовериться, что это действительно служба Amazon.com? Необходимо получить их сертификат, с цифровой подписью авторитетной организации. Такие организации иногда называют бюро сертификации (Certificate Authority — CA). Откуда браузер или сервер базы данных знает, что можно доверять бюро, подписавшему сертификат? Например, я могу создать сертификат для Amazon.com и подписать его от имени hackerAttackers.com. Мой браузер и сервер базы данных не должны принять такой сертификат, хотя он полностью соответствует стандарту X.509. Ответ на вопрос о доверии связан с использованием "бумажника", в котором хранится набор надежных сертификатов. Надежным считается сертификат, выданный бюро сертификации, которому вы доверяете. В "бумажник" Oracle входит ряд сертификатов, которым обычно доверяют. Вы можете добавлять сертификаты при необходимости. То же самое делает и браузер. При подключении к сайту, сертификата для которого нет в "бумажнике" браузера, появляется окно, уведомляющее об этом, и мастер подключения, позволяющий продолжить подключение или отказаться от него. Давайте рассмотрим несколько примеров использования протокола SSL. Сначала надо создать новый "бумажник". Для этого надо вызвать программу OWM (Oracle Wallet Manager) в ОС UNIX или выбрать ее из меню кнопки Пуск в Windows (она находится в группе ORACLE HOME|NETWORK ADMINISTRATION). При этом появится окно следующего вида:

Достаточно щелкнуть на пиктограмме нового "бумажника" (зеленый куб), которая находится слева. Будет выдано приглашение на ввод пароля для "бумажника" — задается новый пароль, так что можете ввести любой. Может быть выдано предупреждение о том, что каталог не существует. Проигнорируйте его. Так и должно быть, если раньше "бумажники" не создавались. Затем программа OWM предложит создать новый запрос сертификата:

Приложение А Его создавать не надо. Запрос сертификата означает, что вы хотите получить сертификат для своего компьютера. Он будет использоваться протоколом SSL v.3, когда серверу понадобится идентифицировать клиента. Большинство Web-сайтов аутентифицирует пользователей не по сертификатам, а по регистрационному имени и паролю. Причина в том, что сайту электронной коммерции все равно, кто покупает товар, — лишь бы деньги заплатили. А вот вам важно точно знать, кому посылаются деньги (и информация о кредитной карточке), поэтому мы используем протокол SSL v.2 для идентификации сервера, например, Amazon.com и для шифрования данных. Поэтому щелкните на кнопке NO в ответ на это предложение, сохраните "бумажник", щелкнув на пиктограмме SAVE WALLET (желтая дискета), и продолжайте работу. Давайте сначала обратимся к сайту Amazon.com. Сертификат Amazon был подписан бюро Secure Server Certificate Authority, RSA Data Security, Inc. Это одно из стандартных бюро в "бумажнике" Oracle. tkyte@TKYTE816> declare 2 l_output long;

3 4 l_url varchar2(255). default 5 'https://www.amazon.com/exec/obidos/flex-sign-in/';

6 7 l_wallet_path varchar2(255) default 8 'file:C:\Documents and Settings\Thomas Kyte\ORACLE\WALLETS';

9 10 11 begin 12 l_output := utl_http.request 13 (url => l_url, 14 proxy => 'www-proxy.us.oracle.com', 15 wallet_path => l_wallet_path, 16 wallet_password => 'oracle' 17 );

18 dbms_output.put_line(trim(substr(l_output,1,255)));

19 end;

20 / 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с Пакет UTL_HTTP

так и должно быть. Причина выдачи этой страницы в том, что не передана информация о сеансе. Мы выбрали документ, защищенный протоколом SSL, нам просто нужно проверить, что подключение работает. Давайте обратимся к другому сайту. Например, к E*Trade: tkyte@TKYTE816> declare 2 l_output long;

3 4 l_url varchar2(255) default 5 'https://trading.etrade.com/';

6 7 l_wallet_path varchar2(255) default 8 'file:C:\Documents and Settings\Thomas Kyte\ORACLE\WALLETS';

9 10 11 begin 12 l_output := utl_http.request 13 (url => l_url, 14 proxy => 'www-proxy.us.oracle.com', 15 wallet_path => l_wallet_path, 16 wallet_password => 'oracle' 17 );

18 dbms_output.put_line(trim(substr(l_output,1,255)));

19 end;

20 / declare * ERROR at line 1: ORA-06510: PL/SQL: unhandled user-defined exception ORA-06512: at "SYS.UTL_HTTP", line 174 ORA-06512: at line 12 Очевидно, не получается. У сайта E*Trade сертификат подписан бюро www.verisign com/CPS Incorp.by Ref, которое не обладает доверием по умолчанию. Чтобы обратиться к этой странице, необходимо добавить сертификат в "бумажник" Oracle — если, конечно, вы доверяете Verisign! Вот как это сделать. Перейдите на соответствующий сайт (https://trading.etrade.com). Щелкните дважды на пиктограмме замка в правом нижнем углу (в браузере Microsoft Internet Explorer). B результате появится окно следующего вида:

Приложение А Выберите вкладку Certification Path в верхней его части. Здесь представлен соответствующий сертификат E*Trade (trading.etrade.com), а также информация о том, кто выдал этот сертификат. Необходимо добавить бюро, подписавшее (выдавшее) сертификат, к списку бюро, сертификатам которых вы доверяете, в "бумажник" Oracle. Сертификат выдало бюро www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD, как видно из следующей иерархии:

Пакет UTL_HTTP Щелкните на кнопке View Certificate при выбранной строке www.verisign.com/CPS Incorp. by Ref. Будет выдана информация о сертификате. Выберите вкладку Details, и увидите:

Теперь необходимо щелкнуть на кнопке Copy to File. Сохраните файл локально как файл сертификата Base-64 encoded X.509 (CER). Следующий снимок экрана показывает, какой выбор надо сделать;

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

Теперь можно импортировать этот файл в "бумажник" Oracle Wallet. Откройте "бумажник" в программе OWM и щелкните правой кнопкой мыши на Trusted Certificates;

при этом отобразится меню, в котором есть пункт import Trusted Certificate:

Приложение А Выберите эту опцию, и появится следующее диалоговое окно;

нажмите Select для выбора файла, содержащего сертификат.

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

Теперь сохраните бумажник, щелкнув на соответствующей пиктограмме (желтая дискета) и попробуйте повторно выполнить пример: tkyte@TKYTE816> declare 2 l_output long;

Пакет UTL_HTTP 3 4 l_url varchar2(255) default 5 'https://trading.etrade.com/cgi-bin/gx.cgi/AppLogic%2bHome';

6 7 l_wallet_path varchar2(255) default 8 'file:C:\Documents and Settings\Thomas Kyte\ORACLE\WALLETS';

9 10 11 begin 12 l_output := utl_http.request 13 (url => l_url, 14 proxy => 'www-proxy.us.oracle.com', 15 wallet_path => l_wallet_path, 16 wallet_password => 'oracle' 17 );

18 dbms_output.put_line(trim(substr(l_output,1,255)));

19 end;

20 / 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с

Table created. tkyte@TKYTE816> insert into t values (utl_raw.cast_tq_raw('helloWorld'));

1 row created. tkyte@TKYTE816> select dump(r) rl, dump(utl_raw.cast_to_varchar2(r)) rl 2 from t;

Rl Rl ------------------------------------------- -----------------------Typ=23 Len=10: Typ=l Len=10: 104,101,108,108,111,87,111,114 104,101,108,108,111,87,111,114,108,100,108,100 Как видно по результату DUMP, единственное, что изменилось в данных — это значение атрибута TYP. Оно изменилось с 23 на 1. Если обратиться к руководству Oracle Call interface Programmer's Guide и посмотреть таблицу встроенных типов, можно обнаружить, что значение 23 соответствует типу RAW длинной до 2000 байт, а значение 1 — типу VARCHAR2 длиной до 4000 байт. Функция CAST_TO_VARCHAR2 просто меняет атрибут типа переменной, данные она не трогает. Именно это и требовалось, когда мы применяли функцию DBMS_LOB.SUBSTR к объекту типа BFILE, а в нем оказывался простой текст. Требовалось привести данные типа RAW к типу VARCHAR2 так, чтобы они не преобразовались в шестнадцатиричный вид, т.е. надо было поменять только тип данных. Функция UTL_RAW.CAST_TO_RAW выполняет обратное действие. При наличии данных типа VARCHAR2, которые необходимо обрабатывать как RAW, она позволит только поменять тип. Мы использовали эту функцию при реализации SIMPLE_TCP_CLIENT в разделе, посвященном стандартному пакету UTL_SMTP. PL/SQL-клиент посылает данные типа VARCHAR2, но на уровне языка Java необходимы массивы байтов (данные типа RAW). B языке PL/SQL выполнить соответствующее преобразование очень легко. Еще две заслуживающие внимание функции — это UTL_RAW.LENGTH и UTL_RAW.SUBSTR. При передаче данных типа RAW стандартным функциям LENGTH и SUBSTR данные сначала неявно преобразуются в тип VARCHAR2 (в шестнадцатиричное представление). К сожалению, встроенные функции не перегружены и не принимают данные типа RAW: они преобразуют их в VARCHAR2. Это означает, что возвращаемая функцией LENGTH длина будет вдвое больше фактической, а функция SUBSTR будет всегда возвращать шестнадцатиричную строку, причем придется также уточнять значения параметров, задающих смещение и длину подстроки. Функции пакета UTL_RAW восполняют этот пробел в функциональных возможностях. Они эквивалентны следующим SQL-операторам:

Пакет UTL_RAW tkyte@TKYTE816> select utl_raw.length(r), length(r)/2 from t;

UTL_RAW.LENGTH(R) LENGTH(R)/2 10 tkyte@TKYTE816> select utl_raw.substr(r,2,3) rl, 2 hextoraw(substr(r,3,6)) r2 3 from t 4/ R1 656C6C R2 656C6C Функции пакета UTL_RAW очень упрощают программирование. Вычисление смещений в байтах для стандартной функции SUBSTR выполняется сложнее, и при этом всегда нужно помнить о делении на два.

Пакет UTL_SMTP и отправка электронной почты Пакет UTL_SMTP, впервые появившийся в версии Oracle 8.1.6, представляет собой интерфейс к протоколу Simple Mail Transfer Protocol (простой протокол передачи электронной почты). Он требует наличия в сети SMTP-сервера. Обычно в сети организаций работает по крайней мере один SMTP-сервер, поскольку протокол SMTP является наиболее популярным способом отправки сообщений электронной почты. Пакет UTL_SMTP лучше всего подходит для отправки небольших текстовых сообщений из базы данных. Хотя он и поддерживает передачу вложений в любых форматах, но формировать документ с вложениями (multi-part document), например преобразовыватьдвоичные файлы-вложения в документы, закодированные с помощью средств mime, должен сам пользователь. Здесь я возвращаюсь к примеру, который мы рассматривали в разделе, посвященном пакету DBMS_JOB (в этом примере использовались средства пакета UTL_SMTP), но добавлю к созданному в нем пакету новые функциональные возможности. Мы также рассмотрим альтернативы пакету UTL_SMTP, обеспечивающие больше возможностей, включая отправку вложений вместе с сообщениями. Поскольку SMTP — протокол низкого уровня, используем существующий общедоступный код для создания интерфейса более высокого уровня к протоколу SMTP;

для этого понадобится очень немного кода.

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

при откате транзакции сообщение не отправлялось, а при фиксации — отправлялось. Поэтому я настоятельно рекомендую использовать пакет DBMS_JOB как дополнительный уровень интерфейса при создании средств передачи сообщений. Ранее в примере рассматривалась следующая процедура, работающая с пакетом UTL_SMTP: tkyte@TKYTE816> create or replace 2 PROCEDURE send_mail (p_sender IN VARCHAR2, 3 p_recipient IN VARCHAR2, 4 p_message IN VARCHAR2) 5 as 6 l_mailhost VARCHAR2(255) := 'yourserver.acme.com1;

7 l_mail_conn utl_smtp.connection;

8 BEGIN 9 l_mail_conn :=utl_smtp.open_connection(l_mailhost, 25);

10 utl_smtp.helo(l_mail_conn, l_mailhost);

11 utl_smtp.mail(l_mail_conn, p_sender);

12 utl_smtp.rcpt(l_mail_conn, p_recipient);

13 utl_smtp.open_data(l_mail_conn);

14 utl_smtp.write_data(l_mail_conn, p_message);

15 utl_smtp.close_data(l_mail_conn);

16 utl_smtp.quit (l_mail_conn);

17 end;

18 / Procedure created. tkyte@TKYTE816> begin 2 send_mail('me@acme.com', 3 'you@acme.com', 4 'Hello Tom');

5 end;

6/ PL/SQL procedure successfully completed. Эта процедура работает, но ее возможности весьма ограничены. Она посылает сообщение только одному адресату, т.е. нельзя посылать копии (CC — Carbon Copy) или скрытые копии (BCC — Blind Carbon Copy), нельзя задать тему сообщения (сообщение всегда приходит с пустой строкой темы. Хотелось бы поддержать больше вариантов посылки сообщений. Полное описание всех возможностей пакета UTL_SMTP требует глубокого изучения протокола SMTP, что выходит за рамки книги. Читатели, которых интересуют все возможности протокола SMTP, должны обратиться к рабочему документу RFC812, описанию протокола SMTP. Его можно получить по адресу http://www.faqs.org/rfcs/ rfc821.html. Ниже я кратко опишу, как послать с помощью пакета UTL_SMTP сообщение, в котором поддерживаются: • • несколько адресатов 'To';

несколько адресатов 'CC';

• Приложение А несколько адресатов 'BCC';

• размер текста до 32 Кбайт;

• строка темы;

• описательная строка 'from' (так, что в клиенте в поле From: указывается не только адрес электронной почты отправителя). Ниже представлена спецификация PL/SQL- пакета, поддерживающего все эти возможности. В нем задается тип массива, позволяющий вызывающему давать список адресатов, а также спецификация PL/SQL-процедуры, которую мы будем реализовать: tkyte@TKYTE816> create or replace package mail_pkg 2 as 3 type array is table of varchar2(255);

4 5 procedure send(p_sender_e-mail in varchar2, 6 p_from in varchar2, 7 p_to in array default array(), 8 p_cc in array default array(), 9 p_bcc in array default array(), 10 p_subject in varchar2, 11 p_body in long);

12 end;

13 / Package created. Тело пакета, реализующего перечисленные возможности, покажется достаточно простым, если понимать основы работы протокола SMTP, a также структуру сообщения электронной почты (как клиенты получают поля From, To, CC и т.д.)- Прежде чем изучать код, давайте разберемся, какую структуру может иметь сообщение. Рассмотрим следующий текст: From: Oracle Database Account Subject: This is subject To: you@acme.com, us@acme.com Cc: them@acme.com Hello Tom, this is the mail you need Именно это надо передать в качестве текста сообщения с помощью пакета UTL_SMTP, чтобы клиент электронной почты установил значения полей From, Subject и т.д. Для этого нет специальных команд в протоколе SMTP;

заголовки помещаются в сообщение и отделяются от текста сообщения пустой строкой. Разобравшись в этом, послать сообщение со всеми необходимыми опциями будет очень легко. Для того чтобы послать сообщение нескольким адресатам, надо вызвать UTL_SMTP.RCPT несколько раз с различными адресами. Итак, переходим к телу пакета. Оно начинается с объявления глобальных переменных. Разумеется, необходимо заменить в коде значение g_mailhost, указав имя сервера, к которому вы имеете доступ. Я указал формальное имя, yourserver.acme.com: tkyte@TKYTE816> create or replace package body mail_pkg 2 as Пакет UTL_SMTP и отправка электронной почты 3 4 g_crlf 5 g_mail_conn 6 g_mailhost char(2)defaultchr(13)||chr(10);

utl_smtp.connection;

varchar2(255) := 'yourserver.acme.com';

Далее идет служебная (не опубликованная) функция для передачи сообщения нескольким адресатам — она, по сути, формирует адреса для сообщения. В то же время, она формирует строки То: или CC:, которые посылаются как часть текста сообщения, и возвращает строку в соответствующем формате. Функция реализована отдельно, поскольку это действие придется выполнять отдельно для списков адресатов To, CC и BCC: 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function address_email(p_string in varchar2, p_recipients in array) return varchar2 is l_recipients long;

begin for i in 1.. p_recipients.count loop utl_smtp.rcpt(g_mail_conn, p_recipients(i));

if (l_recipients is null) then l_recipients :=p_string || p_recipients(i) ;

else l_recipients := l_recipients || ', ' || p_recipients(i);

end if;

end loop;

return l_recipients;

end;

Теперь рассмотрим функцию, входящую в спецификацию, — с ее помощью пользователи и будут посылать сообщения. Она начинается со служебной процедуры writeData, упрощающей передачу заголовков сообщения (записей To:, From:, Subject:). Если запись заголовка не пуста, процедура будет использовать для ее передачи соответствующий вызов пакета UTL_SMTP, добавляя необходимую метку конца строки (возврат каретки/перевод строки): 27 procedure send(p_sender_email in varchar2, 28 p_from in varchar2 default NOLL, 29 p_to in array default array(), 30 p_cc in array default array(), 31 p_bcc in array default array(), 32 p_subject in varchar2 default NOLL, 33 p_body in long default NOLL) 34 is 35 l_to_list long;

36 l_cc_list long;

37 l_bcc_list long;

38 l_date varchar2(255) default 39 to_char(SYSDATE, 'ddMonyyhh24:mi:ss');

40 41 42 43 44 45 46 47 Приложение А procedure writeData(p_text in varchar2) as begin if (p_text is not null) then utl_smtp.write_data(g_mail_conn, p_text || g_crlf);

end if;

end;

Теперь все готово для передачи сообщения. Эта часть мало отличается от простейшей процедуры, с которой мы начали. Она начинается точно так же — с подключения к SMTP-серверу и организации сеанса: 49 50 51 52 53 54 begin g_mail_conn :=utl_smtp.open_connection(g_mailhost, 25);

utl_smtp.helo(g_mail_conn, g_roailhost);

utl_smtp.mail(g_mail_conn, p_sender_email);

Вот тут начинаются отличия — вместо однократного вызова UTL_SMTP.RCPT мы вызываем функцию address_email, которая (потенциально) может вызвать эту процедуру несколько раз, создавая попутно списки То: и CC:. Создается также список BCC:, но мы не буем включать его в сообщение (мы не хотим, чтобы адресаты видели этот список) 55 56 57 58 l_to_list :=address_email('To: ', p_to);

l_cc_list :=address_email('Cc: ', p_cc);

l_bcc_list := address_email('Bcc: ', p_bcc);

Теперь вызовем процедуру OPEN_DATA, чтобы передать текст сообщения. Код в строках 61-68 генерирует раздел заголовков. Строка 69 посылает текст сообщения (его содержимое), а строка 70 завершает передачу. 59 utl_smtp.open_data(g_mail_conn);

60 61 writeData('Date: ' || l_date);

62 writeData('From: ' || nvl( p_from, p_sender_email));

63 writeData('Subject: ' || nvl(p_subject, "(nosubject)'));

64 65 writeData(l_to_list);

66 writeData(l_cc_list);

67 68 utl_smtp.write_data(g_mail_conn, '' || g_crlf);

69 utl_smtp.write_data(g_mail_conn, p_body);

70 utl_smtp.close_data(g_mail_conn) ;

71 utl_smtp.quit (g_mail_conn) ;

72 end;

73 74 75 end;

Пакет UTL_SMTP и отправка электронной почты 76 / Package body created. Работу этого пакета можно проверить следующим образом: tkyte@TKYTE816> begin 2 mail_pkg.send 3 (p_sender_email => 'me@acme.com', 4 p_from => 'Oracle Database Account ', 5 p_to =>mail_pkg.array('you@acme.com','us@acme.com ' ), 6 p_cc => mail_pkg.array(' them@acme.com ' ), 7 p_bcc => mail_pkg.array('noone@dev.null'), 8 p_subject => 'This is a subject', 9 p_body => 'Hello Tom, this is the mail you need');

10 end;

11 / PL/SQL procedure successfully completed. В результате генерируется сообщение со следующим текстом: Date: 13 May 01 12:33:22 From: Oracle Database Account

• закодировать двоичные данные алгоритмом Base-64 (или любым аналогичным методом кодирования, таким так uuencode, binhex и т.д.). Для этого потребуется (по скромной оценке) написать несколько сотен, если не тысяч, строк PL/SQL-кода. Вместо этого я рекомендую использовать уже написанный и очень надежный интерфейс JavaMail, который рассматривается далее.

Загрузка и использование интерфейса JavaMail Чтобы использовать пакет UTL_SMTP, поддержка языка Java должна быть сконфигурирована в базе данных на сервере Oracle 8i. Дело в том, что пакет UTL_SMTP использует средства пакета UTL_TCP, а тот, в свою очередь, построен на основе Javaфункций. (Помните: если поддержка языка Java в базе данных не включена, для передачи Приложение А простых сообщений по электронной почте можно использовать пакет UTL_HTTP;

пример представлен в соответствующем разделе приложения А). Так что, если вы можете использовать пакет UTL_SMTP, обратитесь на сайт компании Sun и загрузите интерфейс JavaMail. Это позволит посылать из базы данных более сложные сообщения, включающие вложения. Дальнейшее изложение основано на результатах работы моего коллеги, Марка Пьермарини (Mark Piermarini), который помогает мне решить множество проблем, связанных с использованием языка Java. Обратившись к странице http://java.sun.com/products/javamal/index.html, вы сможете загрузить с нее интерфейс JavaMail API. B загруженном архиве будет несколько сотен файлов, из которых понадобится только один. После загрузки интерфейса JavaMail API не забудьте также загрузить расширения JavaBeans™ Activation Framework, или JAF javax.activation). Это необходимо для работы пакета JavaMail. После загрузки этих двух наборов файлов извлеките из архива JavaMail API файл mail.jar, а из архива JAF — файл activation.jar. Нам понадобятся только они, но вы можете прочитать соответствующую документацию и обнаружить множество других возможностей, которые мы не используем. Мы используем только часть интерфейса, связанную с передачей сообщений по электронной почте. Интерфейс включает также функции получения сообщений по протоколам IMAP, POP и другие средства. С помощью loadjava загрузите в базу данных архивы mail.jar и activation.jar, но перед этим их необходимо упаковать в другом формате. Эти jar-файлы упакованы в формате, который не понимает интерпретатор байт-кода сервера. Необходимо распаковать ("unjar") и снова упаковать ("rejar") без сжатия или использовать средства типа WinZip для "переупаковки" их в виде zip-файла. В Windows 2000 я делал следующее: • с помощью WinZip распаковывал содержимое файла mail.jar в каталог c:\temp\mail;

• с помощью WinZip создавал новый архив, c:\temp\mail8i.zip;

• помещал в этот новый архив содержимое c:\temp\maiI\*.*, в том числе подкаталоги. То же самое я проделал для activation.jar, заменяя mail на activation. Теперь все готово для загрузки полученных zip-файлов (или jar-файлов, если вы заархивировали их в этом формате) в базу данных. Файлы должны загружаться в базу данных от имени пользователя SYS, поскольку они включают защищенные пакеты Java, которые обычным пользователям загружать не разрешено. Используем следующие команды loadjava: loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public activation8i.zip loadjava -u sys/manager -o -r -v -f -noverify -synonym -g public mail8i.zip Где: • u sys/manager — идентификатор и пароль пользователя SYS. Защищенные пакеты должны загружаться от имени пользователя SYS. • о — сокращение для -oci8. Я использую драйвер oci8. Вы можете использовать и thin-драйвер, но тогда команду придется соответственно изменить.

Пакет UTL_SMTP и отправка электронной почты • r — сокращение для -resolve. Выполняется разрешение всех внешних ссылок в загруженных классах;

это позволяет убедиться, что Java-классы после загрузки смогут работать. • v — сокращение для -verbose. Эта опция обеспечивает выдачу сообщений по ходу работы программы loadjava, т.е. можно следить за ходом выполнения. • f — сокращение для -force. При первой загрузке эту опцию указывать необязательно, но не помешает. Если при выполнении команды loadjava произойдет ошибка, можно будет исправить ее и повторить загрузку. В этом случае придется либо сначала удалить jar-файл из базы данных с помощью команды dropjava, либо указать опцию -force. Использование опции -force упрощает повторную загрузку. • noverify — не проверять байт-код. Для выполнения этой опции необходимо получить привилегию oracle.aurora.security.JServerPermission(Verifier). Кроме того, эту опцию необходимо указывать вместе с опцией -r. Пользователь SYS имеет соответствующую привилегию. Опцию необходимо указывать, потому что средства проверки байт-кода сообщат о проблемах в файле mail.jar, а так мы сможем его успешно загрузить. • synonym — создает общедоступные синонимы для классов. Поскольку мы не создаем от имени пользователя SYS пакет, использующий эти Java-классы, эта опция позволит нам увидеть классы, загруженные от имени SYS. • g public — предоставляет привилегию execute на загруженные классы роли PUBLIC. Если это нежелательно, укажите после -g только имя пользователя, в схеме которого будут создавать процедуры отправки почты, например -g UTILITY_ACCT. Подробнее о программе loadjava и ее опциях можно почитать в руководстве Oracle8i Java Developers Guide. После загрузки этих пакетов все готово для создания хранимой процедуры на языке Java, фактически обеспечивающей передачу сообщений. Процедура, обращаясь к интерфейсу JavaMail API, позволит создать в PL/SQL связывающую функцию с такой спецификацией: tkyte@TKYTE816> desc send FUNCTION send RETURNS NUMBER Argument Name P_FROM P_TO P_CC P_BCC P_SUBJECT P_BODY P_SMTP_HOST P_ATTACHMENT_DATA P_ATTACHMENT_TYPE P_ATTACHMENT_FILE_NAME Type VARCHAR2 VARCHAR2 VARCHAR2 VARCHAR2 VARCHAR2 VARCHAR2 VARCHAR2 BLOB VARCHAR2 VARCHAR In/Out Default? IN IN IN IN IN IN IN IN IN IN Приложение А Эта функция позволит использовать списки адресатов CC и BCC и посылать вложения. В качестве упражнения предлагаю читателю реализовать передачу массивов объектов типа BLOB или перезагрузку этой функции для работы с вложениями типа CLOB или BFILE. Далее представлена соответствующая хранимая процедура на Java. Она использует базовые средства интерфейса класса JavaMail API и весьма проста. Мы снова не будем вникать в детали использования JavaMail API (этому можно посвятить отдельную книгу), рассмотрим только основы. Представленный ниже класс mail имеет один метод, send. Именно этот метод мы будем использовать для передачи сообщения. В представленной реализации он возвращает число 1 в случае успешной передачи и 0 — в противном случае. Эта реализация очень проста, можно предложить куда более сложную, поддерживающую вложение данных многих других типов (CLOB, BFILE, LONG и т.д.). Можно также выдавать вызывающему точную причину ошибки, полученную с сервера SMTP, например invalid recipient, no transport. tkyte@TKYTE816> create or replace and compile 2 java source named "mail" 3 as 4 import java.io.*;

5 import java.sql.*;

6 import java.util.Properties;

7 import java.util.Date;

8 import javax.activation.*;

9 import javax.mail.*;

10 import javax.mail.internet.*;

11 import oracle.jdbc.driver.*;

12 import oracle.sql.*;

13 14 public class mail 15 { 16 static String dftMime = "application/octet-stream";

17 static String dftName = "filename.dat";

18 19 public static oracle.sql.NUMBER 20 send(String from, 21 String to, 22 String cc, 23 String bcc, 24 String subject, 25 String body, 26 String SMTPHost, 27 oracle.sql.BLOB attachmentData, 28 String attachmentType, 29 String attachmentFileName) Представленный выше список аргументов соответствует спецификации вызова в PL/SQL, которая была показана ранее, — назначение аргументов вполне очевидно. Разъяснений требуют лишь два: attachmentType и attachmentFileName. В качестве attachmentType необходимо указывать тип MIME (Multi-purpose Internet Mail Extensions — многоцелевые расширения электронной почты для Internet), с которым вы могли встре Пакет UTL_SMTP и отправка электронной почты чаться, например, в HTML-документах. MIME-типдля GIF-файла, например, — image/ gif, mime-тип для обычного текстового документа — text/plain, a вложение в формате НТМL должно иметь тип text/html. Значение attachmentFileName в данном случае — это не имя существующего файла ОС, который будет вкладываться в сообщение, а имя файла в самом вложении;

оно будет выдано адресату сообщения в качестве имени вложения. Вложение передается процедуре фактически как данные типа oracle.sql.BLOB. Теперь переходим к телу функции. Оно начинается с установки свойству mail.smtp.host переданного значения имени SMTP-сервера — средства JavaMail API определяют по этому значению, к какому SMTP-серверу подключаться: 30 31 32 33 34 35 36 37 38 39 { int rc = 0;

try { Properties props = System.getProperties();

props.put("mail.smtp.host", SMTPHost);

Message msg = newMimeMessage(Session.getDefaultInstance(props, null));

Затем мы устанавливаем заголовки сообщения. Указывается, кто послал сообщение, кому его переслать, кому слать копии (CC) или скрытые копии (BCC), тема сообщения и дата отправки: 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 msg.setFrom(new InternetAddress(from));

if (to != null && to.length() > 0) msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false));

if (cc != null && cc.length() > 0) msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false));

if (bcc != null && bcc.length() > 0) msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(bcc, false));

if (subject != null && subject.length() > 0) msg.setSubject(subject);

elsemsg.setSubject("(no subject)");

msg.setSentDate(new Date());

Затем мы используем один из двух методов передачи сообщения. Если аргумент attachmentData — не пустой, будем кодировать сообщение с помощью MIME, стандарта, обеспечивающего передачу вложений и других многосекционных документов. Для этого мы создаем несколько частей MIME-текста;

в данном случае две: текст сообщения и вложение. Строки 76-78 требуют дополнительного разъяснения. С их помощью Приложение А обеспечивается передача вложений, переданных как объект типа BLOB. Интерфейс JavaMail изначально не принимает данные типа oracle.sql.BLOB (ведь это универсальный интерфейс). Для отправки вложения в виде объекта типа BLOB необходимо задать метод получения его данных для интерфейса JavaMail. Для этого мы создаем специализированный класс DataHandler, интерфейс которого средства JavaMail смогут вызывать для получения данных вложения. Этот класс (BLOBDataHandIer) реализован как вложенный: 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 if (attachmentData != null) { MimeBodyPart mbpl = new MimeBodyPart();

mbpl.setText((body != null ? body : " " ) ) ;

mbpl.setDisposition(Part.INLINE);

MimeBodyPart mbp2 = new MimeBodyPart();

String type = (attachmentType != null ? attachmentType : dftMime);

String fileName = (attachmentFileName != null ? attachmentFileName : dftName);

mbp2.setDisposition(Part.ATTACHMENT);

mbp2.setFileName(fileName);

mbp2.setDataHandler(new DataHandler(newBLOBDataSource(attachmentData, type)) );

MimeMultipart mp = new MimeMultipart();

mp.addBodyPart(mbpl);

mp.addBodyPart(mbp2);

msg.setContent(mp);

} else ( msg.setText((body != null ? body } Transport.send(msg);

rc = 1;

} catch (Exception e) { e.printStackTrace();

rc = 0;

} finally { return new oracle.sql.NUMBER(rc);

} } Если же вложения нет, текст сообщения передается очень просто — вызовом setText:

));

Пакет UTL_SMTP и отправка электронной почты Теперь рассмотрим вложенный класс, BLOBDataSource. Он реализует универсальный интерфейс для JavaMail API, позволяющий обращаться к данным типа oracle.sql.BLOB. Реализация этого класса очень проста: 101 // Вложенный класс, реализующий интерфейс DataSource. 102 static class BLOBDataSource implements DataSource 103 { 104 private BLOB data;

105 private String type;

106 107 BLOBDataSource(BLOB data, String type) 108 { 109 this.type = type;

110 this.data = data;

111 } 112 113 public InputStreamgetInputStream() throws IOException 114 { 115 try 116 { 117 if(data == null) 118 throw new IOException("HeT данных.");

119 120 return data.getBinaryStream();

121 } catch(SQLException e) 122 { 123 throw new 124 IOException("He удалось получить входной поток двоичных -> данных из BLOB.");

125 } 126 } 127 128 public OutputStream getOutputStream() throws IOException 129 { 130 throw new IOException("He удалось это сделать.");

131 } 132 133 public String getContentType() 134 { 135 return type;

136 } 137 138 public String getName() 139 { 140 return "BLOBDataSource";

141 } 142 } 143 ) 144 / Java created.

Приложение А Теперь, когда у нас есть Java-класс, созданный для связи с языком PL/SQL, необходимо создать функцию связи, сопоставляющую PL/SQL-типы Java-типам и связывающуюся с Java-классом. Она создается следующим образом: tkyte@TKYTE816> create or replace function send( 2 p_from in varchar2, 3 P_to in varchar2, 4 P_cc in varchar2, 5 p_bcc in varchar2, 6 psubject in varchar2, 7 p_body in varchar2, 8 p_smtp_host in varchar2, 9 p_attachment_data in blob, 10 p_attachment_type in varchar2, 11 p_attachment_file_name in varchar2) return number 12 as 13 language java name 'mail.send(java.lang.String, 14 java.lang.String, 15 java.lang.String, 16 java.lang.String, 17 java.lang.String, 18 java.lang.String, 19 java.lang.String, 20 oracle.sql.BLOB, 21 java.lang.String, 22 java.lang.String 23 ) return oracle.sql.NUMBER';

24 / Function created. Последнее, что надо сделать, прежде чем использовать функцию, — убедиться, что пользователь (владелец класса mail и хранимой процедуры send) имеет достаточно привилегий для ее выполнения. Необходимые привилегии предоставляются следующим образом: sys@TKYTE816> begin 2 dbms_java.grant_permission( 3 grantee => 'USER', 4 permission_type => 'java.util.PropertyPermission', 5 permission_name => '*', 6 permission_action => 'read,write' 7 );

8 dbms_java.grant_permission( 9 grantee => 'USER', 10 permission_type => 'java.net.SocketPermission', 11 permission_name => '*', 12 permission_action => 'connect,resolve' 13 );

14 end;

15 / PL/SQL procedure successfully completed.

Пакет UTL_SMTP и отправка электронной почты Обратите внимание: предоставляя привилегии для java.net.SocketPermission, я указал звездочку (*) в качестве permission_name. Это позволяет пользователю получать по имени IP-адрес и подключаться к хосту. Строго говоря, вы можете указать здесь только имя того SMTP-сервера, который будете использовать. Это будет минимально необходимая привилегия. Эта привилегия нужна, чтобы можно было получить lPадрес по имени SMTP-хоста, а затем подключиться к нему. Другая привилегия, java.util.PropertyPermission, необходима для установки свойства сеанса mail.smtp.host. Теперь все готово для проверки. Я использовал фрагмент кода из раздела, посвященного пакету DBMS_LOB, где рассматривалась процедура load_a_file. Изменив ее и таблицу DEMO так, чтобы в ней был столбец типа BLOB вместо CLOB, я загрузил в таблицу файл mail8i.zip, в котором находится использованный нами класс. Теперь я могу использовать такой PL/SQL-блок для передачи самому себе из базы данных этого класса как вложения в сообщении электронной почты: tkyte@TKYTE816> set serveroutput on size 1000000 tkyte@TKYTE816> exec dbms_java.set_output(1000000) tkyte@TKYTE816> declare 2 ret_code number;

3 begin 4 for i in (select theBlob from demo) 5 loop 6 ret_code := send( 7 p_from=> 'me@acme.com', 8 p_to -> 'you@acme.com', 9 p_cc => NULL, 10 p_bcc => NULL, 11 p_subject => 'Use the attached Zip file', 12 p_body => 'to send email with attachments....', 13 p_smtp_host=> 'yourserver.acme.com', 14 p_attachment_data => i.theBlob, 15 p_attachment_type => 'application/winzip', 16 p_attachment_file_name => 'mail8i.zip');

17 if ret_code = 1 then 18 dbms_output.put_line ('Сообщение успешно послано...');

19 else 20 dbms_output.put_line ('Сообщение послать не удалось...');

21 end if;

22 end loop;

23 end;

24 / Successfully sent message... PL/SQL procedure successfully completed. При проверке, несомненно, необходимо выполнить команду set serverouput on и вызвать процедуру DBMS_JAVA.SET_OUTPUT. Причина в том, что сообщения об исключительных ситуациях в хранимых процедурах на языке Java выдаются в System.out и по умолчанию попадают в файл трассировки на сервере. Если вы хотите получать сообщения об ошибках в сеансе SQL*Plus, необходимо выполнить эти два действия. Это очень поможет при отладке.

Приложение А Резюме В этом разделе мы кратко рассмотрели стандартный пакет UTL_SMTP. Вы узнали, как посылать сообщения нескольким адресатам, явно формировать заголовки From: и Subject:. Этого достаточно для удовлетворения требований большинства пользователей к средствами передачи сообщений электронной почты из базы данных. Пакет UTL_SMTP хорошо подходит для отправки простых, текстовых сообщений, но передача вложений или сложных, многосекционных сообщений выходит за пределы его возможностей (если только вы сами не сформируете тело сообщения). Если эти возможности необходимы, можно использовать средства JavaMail API. Поскольку компания Sun любезно предоставила необходимые для этого алгоритмы, мы просто воспользовались ее кодом. В этом разделе продемонстрировано не только, как посылать сообщения, но и какие побочные эффекты связаны с использованием Java в качестве альтернативного языка написания хранимых процедур. Теперь можно использовать общедоступный код и библиотеки классов. Можно обеспечить выполнение сервером многих действий, которые ранее были невозможны. Разработчики языка PL/SQL в корпорации Oracle сами используют этот метод. Пакет UTL_TCP в Oracle 8i построен на базе Java-функций.

Пакет UTL_TCP В версии Oracle 8.1.6 появился пакет UTL_TCP. Он позволяет, работая в PL/SQL, устанавливать через сетевой сокет TCP/IP-соединение с любым сервером. Если известен протокол сервера, можно "общаться" с ним из PL/SQL. Например, зная протокол HTTP (Hyper Text Transfer Protocol — протокол передачи гипертекста), можно с помощью пакета UTL_TCP выполнить следующее: test_jsock@DEV816> DECLARE 2 c utl_tcp.connection;

— TCP/IP-подключение к Web-серверу 3 n number;

4 buffer varchar2(255);

5 BEGIN 6 c :=utl_tcp.open_connection('proxy-server', 80);

7 n :=utl_tcp.write_line(c, 'GEThttp://www.wrox.com/HTTP/1.0');

8 n :=utl_tcp.write_line(c);

9 BEGIN 10 LOOP 11 n:=utl_tcp.read_text(c, buffer, 255);

12 dbms_output.put_line(buffer);

13 END LOOP;

14 EXCEPTION 15 WHEN utl_tcp.end_of_input THEN 16 NULL;

— конец входных данных 17 end;

18 utl_tcp.close_connection(c);

19 END;

20 / HTTP/1.1 200 OK Приложение А Date: Tue, 30 Jan 2001 11:33:50 GMT Server: Apache/1.3.9 (Unix) mod_perl/1.21 ApacheJServ/1.1 Content-Type: text/html 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с Этот PL/SQL-блок позволил мне подключиться к серверу, в данном случае — промежуточному, с именем proxy-server. Через брандмауэр я попал в Intemet. Это произошло в строке 6. Затем я запросил Web-страницу, в строках 7 и 8. В строках с 10 по 13 мы получаем содержимое этой Web-страницы, включая все существенные заголовки HTTP (которые, кстати, стандартный пакет UTL_HTTP не позволяет получить). Затем пакет UTL_TCP возбуждает исключительную ситуацию UTL_TCP.END_OF_INPUT, страница получена, и мы выходим из цикла. После этого мы отключаемся. Этот простой пример демонстрирует большую часть функциональных возможностей пакета UTL_TCP. Мы не вызываем функции типа AVAILABLE, информирующие о том, есть ли данные для получения. Мы не вызываем процедуру FLUSH, передающую все результаты, находящихся в буфере (буферизация не используется, поэтому такой вызов не нужен). Мы не использовали все возможные варианты вызовов READ, WRITE и GET для обмена данными через сокет, но представленный пример достаточно полно демонстрирует особенности использования пакета UTL_TCP. Меня не всегда устраивает скорость работы подобных фрагментов кода. По моему опыту, пакет UTL_TCP, хотя и работает, но в данной версии (Oracle 8i) имеет недостаточную производительность. В версии 8.1.7.1 проблема низкой производительности решена (речь идет об исправлении ошибки #1570972). Насколько же медленно работает подобный код? Представленный выше код для выборки документа размером 16 Кбайт требует от четырех до десяти секунд, в зависимости от платформы. Это, конечно, — медленно, особенно по сравнению с тем, что соответствующая функция пакета UTL_HTTP позволяет загрузить такой же документ за доли секунды. К сожалению, пакет UTL_HTTP не позволяет работать с ключиками, заголовками HTTP, двоичными данными, выполнять простейшую аутентификацию и т.п., поэтому альтернативные варианты часто оказываются полезными. Я думаю, можно сделать лучше. Для этого мы реализуем собственный пакет UTL_TCP. При этом будем использовать объектные типы, работа с которыми рассматривалась в главе 20. Мы реализуем в PL/SQL тип SocketType, частично на языке Java. В разделе, посвященном пакету UTL_HTTP, мы использовали этот же тип, SocketType, для создания более удобного пакета UTL_HTTP. Поскольку интерфейс создаваемого типа построен по аналогии с возможностями пакета UTL_TCP, когда в версии Oracle9i появится встроенная, более эффективная реализация пакета UTL_TCP, мы легко сможем изменить тело типа, используя обращения к пакету UTL_TCP, и отказаться от нынешней реализации на базе Java.

Тип SocketType Объектный тип SocketType будет иметь следующую спецификацию:

Пакет UTL_TCP tkyte@TKYTE816> create or replace type SocketType 2 as object 3( 4 — Приватные данные вместо передачи контекста 5 — каждой процедуре, как при использовании 6 — пакета UTL_FILE. 7 g_sock number, 8 9 — Функция, возвращающая CRLF. Для удобства. 10 static function crlf return varchar2, 11 12 — Процедуры для передачи данных через сокет. 13 member procedure send(p_data in varchar2), 14 member procedure send(p_data in clob), 15 16 member procedure send_raw(p_data in raw), 17 member procedure send_raw(p_data in blob), 18 19 — Функции для получения данных через сокет. При закрытии сокета 20 — (получении eof) они возвращают Null. Будут ждать данных, 21 — блокируя работу. Если это нежелательно, используйте 22 — представленную ниже функцию РЕЕК, чтобы узнать, есть ли -> данные для чтения. 23 member function recv return varchar2, 24 member function recv_raw return raw, 25 26 — Служебная функция. Читает данные, пока не обнаружит CRLF. 27 — Может удалять CRLF, при желании пользователя (или не удалять, -> по умолчанию). 28 member function getline(p_remove_crlf in boolean default FALSE) 29 return varchar2, 30 31 — Процедуры для подключения к хосту и отключения от него. 32 — Важно не забывать отключаться, а то произойдет утечка 33 — ресурсов и, рано или поздно, подключиться не удастся. 34 member procedure initiate_connection(p_hostname in varchar2, 35 p_portno in number), 36 member procedure close_connection, 37 38 — Функция, информирующая о том, сколько байтов (как минимум) 39 — можно прочитать. 40 member function peek return number 41 );

42 / Type created. Функциональные возможности этого типа во многом подобны предлагаемым пакетом UTL_TCP, да и интерфейс практически тот же. При желании тип можно реализовать на базе средств этого пакета. Мы, однако, собираемся реализовать его на базе другого пакета — SIMPLE_TCP_CLIENT. Это обычный пакет PL/SQL, на базе которого будет создан тип SocketType. Вот спецификация нашей версии пакета UTL_TCP:

Приложение А tkyte@TKYTE816> CREATE OR REPLACE PACKAGE simple_tcp_client 2 as 3 — Функция для подключения к хосту. Возвращает "сокет", 4 — который на самом деле представляет собой просто число. 5 function connect_to(p_hostname in varchar2, 6 p_portno in number) return number;

7 8 — Передача данных. Мы знаем только, как посылать данные типа 9 — RAW. Вызывающие должны приводить данные типа VARCHAR2 к типу 10 — RAW. На низком уровне, все проходящие через сокет данные — -> байты. 11 12 procedure send(p_sock in number, 13 p_data in raw);

14 15 — Процедура recv предназначена для получения данных. 16 — Если maxlength имеет значение -1, мы попытаемся получить 17 — 4 Кбайт данных. Если maxlengthis имеет ЛЮБОЕ значение, кроме 18 — -1, мы попытаемся прочитать все байты данных p_data. Другими 19 — словами, я ограничиваю объем получаемых данных до 4 Кбайт, -> если не сказано иначе. 20 procedure recv(p_sock in number, 21 p_data out raw, 22 p_maxlength in number default - 1 ) ;

23 24 — Получает строку данных из входного сокета. Т.е. данные 25 — вплоть до символа новой строки, \n. 26 procedure getline(p_sock in number, 27 p_data out raw);

28 29 30 — Обеспечивает отключение от сервера. 31 procedure disconnect(p_sock in number);

32 33 — Получает время сервера по Гринвичу (GMT) в формате yyyyMMdd 34 HHmmss z procedure get_gmt(p_gmt out varchar2);

35 36 — Получает часовой пояс сервера. Полезно для некоторых -> протоколов Internet. 37 procedure get_timezone(p_timezone out varchar2);

38 39 — Получает имя хоста, на котором работает ваш сервер. Это тоже 40 — пригодится для некоторых протоколов Internet. 41 procedure get_hostname(p_hostname out varchar2);

42 43 — Возвращает количество байтов, которые можно прочитать. 44 function peek(p_sock in number) return number;

45 46 — Кодирует данные типа RAW алгоритмом base64. Пригодится для 47 — отправки вложений в сообщениях электронной почты или при 48 — работе по протоколу HTTP, требующему скрывать имя -> пользователя/пароль путем кодирования с помощью base64.

Пакет UTL_TCP 49 procedure b64encode(p_data in raw, p_result out varchar2);

50 end;

51 / Package created. Поскольку ни одну из этих функций нельзя реализовать на PL/SQL, мы реализуем их на языке Java. Java-код, необходимый для этого, на удивление, невелик — занимает всего 94 строки. Будем использовать стандартный класс Socket языка Java и поддерживать небольшой массив сокетов, обеспечивающий в PL/SQL возможность поддерживать одновременно до десяти подключений. Если необходимо более десяти подключений, увеличьте размер массива socketUsed в представленном далее коде. Я пытался сделать код как можно проще и короче, предпочитая основную работу выполнять с помощью PL/SQL. Я представлю небольшой класс, который нам понадобится, и прокомментирую его: tkyte@TKYTE816> set define off tkyte@TKYTE816> CREATE or replace and compile JAVA SOURCE 2 NAMED "jsock" 3 AS 4 import java.net.*;

5 import java.io.*;

6 import java.util.*;

7 import java.text.*;

8 import sun.misc.*;

9 10 public class jsock 11 { 12 static int socketUsed[] = { 0,0,0,0,0,0,0,0,0,0 };

13 static Socket sockets[] = new Socket[socketUsed.length];

14 static DateFormat tzDateFormat = new SimpleDateFormat("z");

15 static DateFormat gmtDateFormat = 16 new SimpleDateFormat("yyyyMMdd HHmmss z");

17 static BASE64Encoder encoder = new BASE64Encoder();

18 В этом классе есть несколько статических переменных. Основные — это массивы socketUsed и sockets. При вызове функций из PL/SQL мы должны возвращать что-то, что можно будет передать в последующих вызовах для идентификации используемого сокета подключения. Мы не можем возвращать объекты Java-класса Socket в PL/SQLподпрограммы, поэтому я использую массив, в котором они хранятся, и возвращаю в PL/SQL индекс этого массива. Метод java_connect_to просматривает массив socketsUsed в поисках пустого слота и выделяет этот слот подключению. Именно индекс в массиве socketsUsed и возвращается PL/SQL-подпрограммам. Эта особенность используется во всех остальных функциях работы с сокетами для получения доступа к фактическому Javaобъекту, представляющему сокет. Остальные статические переменные используются для повышения производительности. Мне необходимы объекты, форматирующие дату, и для того, чтобы не создавать их каждый раз при вызове java_get_gmt или java_get_timezone, я выделяю их один раз и в дальнейшем использую при необходимости. Наконец, объект для выполнения кодиро Приложение А вания по алгоритму base 64. По той же причине, что и объекты для форматирования даты, я выделяю объект encoder. Теперь рассмотрим функции для подключения к серверу по протоколу TCP/IP. Мы проходим в цикле по массиву socketUsed в поисках пустого слота (для которого значение socketUsed[I] отлично от 1).Если мы находим пустой слот, используется Java-класс Socket для подключения к указанному хосту/порту, и флаг socketUsed для этого слота массива устанавливается равным 1. В случае ошибки (нет свободного слота) возвращается значение -1;

в случае успешного подключения — положительное число: 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static public int java_connect_to(String p_hostname, int p_portno) throws java.io.IOException { int i;

for(i = 0;

i < socketUsed.length && socketUsed[i] == 1;

i++);

if (i < socketUsed.length) { sockets[i] = new Socket(p_hostname, p_portno);

socketUsed[i] = 1;

} return i

} Следующие две Java-функции вызываются чаще всего. Они отвечают за передачу и прием данных через подключенный TCP/IP-сокет. Функция java_send_data понятна: она получает выходной поток, соответствующий сокету, и выдает в него данные. Функция java_recv_data несколько сложнее. Она использует для возврата данных параметры типа OUT, отсюда, например, и объявление int[] p_length. Эта функция проверяет длину данных, переданных вызывающим, и, если она имеет значение -1, выделяет для чтения буфер размером 4 Кбайт;

в противном случае она выделяет буфер указанного размера. Затем она пытается прочитать из сокета соответствующий объем данных. Реальный объем прочитанных данных (который будет меньше или равен затребованному) возвращается как результат в параметре p_length: 34 35 36 37 38 39 40 41 42 43 44 45 46 47 static public void java_send_data(int p_sock, byte[] p_data) throws java.io.IOException { (sockets[p_sock].getOutputStream()).write(p_data);

} static public void java_recv_data(int p_sock, byte[][] p_data, int[] p_length) throws java.io.IOException { p_data[0] = new byte[p_length[0] = -1 ? 4096:p_length[0] ];

p_length[0] = (socketstp_sock].getInputStream()).read(p_data[0]);

} Пакет UTL_TCP java_getline — это служебная функция. Многие протоколы Internet выдают результаты "построчно", и возможность получить строку текста всегда пригодится. Например, возвращаемые протоколом HTTP заголовки — это строки текста. Эта функция работает с помощью метода DataInputStream.readLine, возвращая сроку полученных данных и добавляя при этом символы новой строки, удаленные функцией readLine Если данные не получены возвращается пустое значение: 48 49 50 51 52 53 54 55 56 static public void java_getline(int p_sock, String[] p_data) throws java.io.IOException { DataInputStream d = new DataInputStream((sockets[p_sock].getInputStream()));

p_data[0] = d.readLine();

if (p_data[O] != null) p_data[0] += "\n";

} Функция java_disconnect тоже очень проста. Она устанавливает флаг в массиве socketUsed для сокета снова в ноль, показывая, что этот слот в массиве сокетов можно использовать повторно, и закрывает сокет: 57 58 59 60 61 62 63 static public void java_disconnect(int p_sock) throws java.io.IOException { socketUsed[p_sock] = 0;

(sockets[p_sock]).close();

} С помощью функции java_peek_sock можно проверить, есть ли в сокете данные для чтения. Это пригодится в ситуациях, когда клиент не хочет ждать поступления данных, блокируя другие действия. Проверив, есть ли что читать, можно предвидеть, будет ли получение данных заблокировано или данные будут сразу возвращены: 64 65 66 67 68 69 static public int java_peek_sock(int p_sock) throws java.io.IOException { return (sockets[p_sock].getInputStream()).available();

} Осталось еще несколько функций, связанных со временем. Функция java_get_timezone используется для получения часового пояса, установленного на сервере базы данных. Она особенно пригодится, если данные типа DATE надо преобразовать из одного часового пояса в другой с помощью встроенной функции NEW_TIME или необходимо узнать, в каком часовом поясе работает сервер. Вторая функция, java_get_gmt, позволяет получить текущую дату и время на сервере по Гринвичу (GMT — Greenwich Mean Time): 70 static public void java_get_timezone(String[] p_timezone) 71 { 72 tzDateFormat.setTimeZone(TimeZone.getDefault());

73 p timezone[0] = tzDateFormat.fomat(new Date());

74 75 76 77 78 79 80 81 Приложение А } static public void java_get_gmt(String[] p_gmt) { gmtDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

p_gmt[0] =gmtDateFormat.format(newDate());

} Функция b64encode кодирует по алгоритму base64 переданную строку данных. Кодирование по алгоритму base64 — стандартный для Internet метод кодирования данных в виде последовательности 7-битовых символов ASCII для передачи по сетям. Мы будем использовать эту функцию, в частности, при реализации пакета для поддержки протокола HTTP, поскольку он поддерживает простейшую аутентификацию (используемую на многих Web-сайтах, требующих регистрации путем передачи имени пользователя и пароля). 83 static public void b64encode(byte[] p_data, String[] p_b64data) 84 { 85 p_b64data[0] = encoder.encode(p_data);

86 } 87 Последняя функция в этом классе возвращает имя хоста, на котором работает сервер базы данных. Некоторые протоколы Internet требуют передавать эту информацию (например, протокол SMTP — простой протокол передачи сообщений электронной почты): 88 static public void java_get_hostname(String[] p_hostname) 89 throws java.net.UnknownHostException 90 { 91 p_hostname[0] = (InetAddress.getLocalHost()).getHostName();

92 } 93 94 } 95 / Java created. Java-методы достаточно просты. Если вспомнить главу 19, для получения параметров в режиме OUT мы обязаны передавать массивы в Java-функцию. Поэтому большинство функций имеет вид: 40 static public void java_recv_data(int p_sock, 41 byte[][] p_data, int[] p_length) Это позволяет возвращать значение в параметрах p_data и p_length. Теперь, при наличии Java-класса, можно создать тело пакета SIMPLE_TCP_CLIENT. Оно почти полностью состоит из интерфейсов к Java-методам: tkyte@TKYTE816> CREATE OR REPLACE PACKAGE BODY simple_tcp_client 2 as Пакет UTL_TCP 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function connect_to(p_hostname in varchar2, p_portno in number) return number as language java name 'jsock.java_connect_to(java.lang.String, int) return int';

procedure send(p_sock in number, p_data in raw) as language java name 'jsock.java_send_data(int, byte[])';

procedure recv_i(p_sock in number, p_data out raw, p_maxlength in out number) as language java name 'jsock.java_recv_data(int, byte[][], int[])';

procedure recv(p_sock in number, p_data out raw, p_maxlength in number default -1) is l_maxlength number default p_maxlength;

begin recv_i(p_sock, p_data, l_maxlength);

if (l_maxlength <> -1) then p_data := utl_raw.substr(p_data, 1, l_maxlength);

else p_data := NULL;

end if;

end;

В данном случае есть две процедуры: RECV_I и RECV. RECV_I — это служебная процедура (суффикс _I в имени означает internal), которую нельзя вызвать непосредственно за пределами пакета. Она вызывается процедурой RECV. Процедура RECV обеспечивает "дружественный" интерфейс к процедуре RECV_I: она проверяет, были ли прочитаны данные из сокета и если — да, устанавливает соответствующую длину. Если вспомнить представленный ранее Java-код, мы выделяли буфер фиксированного размера в функции RECV и читали из сокета не более определенного количества байтов. В данном же случае необходимо изменить размер буфера так, чтобы он был равен количеству прочитанных байтов;

для этого используется функция UTL_RAW.SUBSTR. Если же данные не прочитаны, возвращается Null. 34 35 36 37 38 39 40 41 42 procedure getline_i(p_sock in number, p_data out varchar2) as language java name 'jsock.java_getline(int, java.lang.String[])';

procedure getline(p_sock p_data as in number, out raw) 43 44 45 46 Приложение А l_data long;

begin getline_i(p_sock, l_data);

p_data := utl_raw.cast_to_raw(l_data);

end getline;

Аналогично рассмотренным выше процедурам RECV_I/RECV, GETLINE_I — внутренняя процедура, вызываемая только процедурой GETLINE. Внешний интерфейс PL/SQL представляет все данные как данные типа RAW, и функция GETLINE просто приводит данные типа VARCHAR2 к типу RAW. 48 49 procedure disconnect(p_sock in number) 50 as language java 51 name 'jsock.java_disconnect(int)';

52 53 procedure get_gmt(p_gmt out varchar2) 54 as language java 55 name 'jsock.java_get_gmt(java.lang.String[])';

56 57 procedure get_timezone(p_timezone out varchar2) 58 as language java 59 name 'jsock.java_get_timezone(java.lang.String[])';

60 61 procedure get_hostname(p_hostname out varchar2) 62 as language java 63 name 'jsock.java_get_hostname(java.lang.String[])';

64 65 function peek(p_sock in number) return number 66 as language java 67 name 'jsock.java_peek_sock(int) return int';

68 69 procedure b64encode(p_data in raw, p_result out varchar2) 70 as language java 71 name 'jsock.b64encode(byte[], java.lang.String[])';

72 end;

73 / Package body created. Теперь можно протестировать процедуры, чтобы убедиться в их работоспособности: tkyte@TKYTE816> declare 2 l_hostname varchar2(255);

3 l_gmt varchar2(255);

4 l_tz varchar2(255);

5 begin 6 simple_tcp_client.get_hostname(l_hostname);

7 simple_tcp_client.get_gmt (l_gmt) ;

8 simple_tcp_client.get_timezone(l_tz);

9 10 dbms_output.put_line('hostname ' || l_hostname);

11 dbms_output.put_line('gmt time ' || l_gmt);

Пакет UTL_TCP dbms_output.put_line('timezone ' || l_tz);

13 end;

14 / hostname tkyte-dell gmt time 20010131 213415 GMT timezone EST PL/SQL procedure successfully completed. При использовании в базе данных компонентов этого пакета, связанных с протоколом TCP/IP, важно учитывать, что для этого необходимы соответствующие привилегии. Подробнее о пакете DBMS_JAVA и привилегиях, связанных с использованием Java, можно прочитать в разделе приложения А, посвященном пакету DBMS_JAVA. В данном случае необходимо выполнить: sys@TKYTE816> begin 2 dbms_java.grant_permission( 3 grantee => 'TKYTE', 4 permission_type => 'java.net.SocketPermission', 5 permission_name => '*', 6 permission_action=> 'connect,resolve');

7 end;

8/ PL/SQL procedure successfully completed. Подробнее о том, как и почему работает эта процедура, см. в разделе, посвященном пакету DBMS_JAVA. Если коротко, она позволяет пользователю TKYTE создавать подключения и получать по именам хостов IP-адреса (вот почему передано значение '*'). Если вы работаете с версией Oracle 8.1.5, пакета DBMS_JAVAy вас нет. В этой версии придется предоставить владельцу jsock привилегию JAVASYSPRIV. Учтите, что привилегия JAVASYSPRIV дает слишком широкие полномочия. Тогда как вызов DBMS_JAVA.GRANT_PERMISSION позволяет установить привилегию очень точно, привилегия JAVASYSPRIV предполагает широкий набор привилегий. Теперь, когда необходимые привилегии получены, можно создавать и тестировать тип SocketType, аналогично тому, как тестировался пакет UTL_TCP. Ниже представлено тело типа SocketType. Тело типа содержит очень немного кода — это просто оболочка для созданного пакета SIMPLE_TCP_CLIENT. Она скрывает "сокеты" от вызывающего: tkyte@TKYTE816> create or replace type body SocketType 2 as 3 4 static function crlf return varchar2 5 is 6 begin 7 return chr(13)||chr(10);

8 end;

9 10 member function peek return number 11 is 12 begin 13 return simple_tcp_client.peek(g_sock);

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Приложение А end;

member procedure send(p_data in varchar2) is begin simple_tcp_client.send(g_sock, utl_raw.cast_to_raw(p_data));

end;

member procedure send_raw(p_data in raw) is begin simple_tcp_client.send(g_sock, p_data);

end;

member procedure send(p_data in clob) is l_offset number default 1;

l_length number default dbms_lob.getlength(p_data);

l_amt number default 4096;

begin loop exit when l_offset > l_length;

simple_tcp_client.send(g_sock, utl_raw.cast_to_raw( dbms_lob.substr(p_data,l_amt,l_offset)));

l_offset := l_offset + l_amt;

end loop;

end;

Процедура SEND — перегруженная и поддерживает параметры различных типов, в том числе данные типа CLOB произвольной длины. Для передачи она разбивает объект типа CLOB на 4-килобайтовые фрагменты. Представленная ниже процедура SEND_RAW работает аналогично, но с данными типа BLOB: 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 member procedure send_raw(p_data in blob) is l_offset number default 1;

l_length number default dbms_lob.getlength(p_data);

l_amt number default 4096;

begin loop exit when l_offset > l_length;

simple_tcp_client.send (g_sock, dbms_lob.substr(p_data,l_amt,l_offset));

l_offset := l_offset + l_amt;

end loop;

end;

member function recv return varchar2 is Пакет UTL_TCP 60 l_raw_data raw(4096);

61 begin 62 simple_tcp_client.recv(g_sock, l_raw_data);

63 return utl_raw.cast_to_varchar2(l_raw_data);

64 end;

65 66 67 member function recv_raw return raw 68 is 69 l_raw_data raw(4096);

70 begin 71 simple_tcp_client.recv(g_sock, l_raw_data);

72 return l_raw_data;

73 end;

74 75 member function getline(p_remove_crlf in boolean default FALSE) 76 return varchar2 77 is 78 l_raw_data raw(4096);

79 begin 80 simple_tcp_client.getline(g_sock, l_raw_data);

81 82 if (p_remove_crlf) then 83 return rtrim( 84 utl_raw.cast_to_varchar2(l_raw_data), SocketType.crlf);

85 else 86 return utl_raw.cast_to_varchar2(l_raw_data);

87 end if;

88 end;

89 90 member procedure initiate_connection(p_hostname in varchar2, 91 p_portno in number) 92 is 93 l_data varchar2(4069);

94 begin 95 — выполняется 10 попыток подключения и если ни одна из них не 96 — увенчается успехом, распространяется исключительная ситуация -> в вызывающую среду 97 for i in 1.. 10 loop 98 begin 99 g_sock := simple_tcp_client.connect_to(p_hostname, p_portno);

100 exit;

101 exception 102 when others then 103 if (i = 10) then raise;

end if;

104 end;

105 end loop;

106 end;

Такое количество попыток выполняется для того, чтобы избежать проблем с сообщениями типа "server busy". Это необязательно, но скрывает от вызывающего ошибки, 107 108 109 110 111 112 113 114 115 Приложение А которые он получал бы слишком часто при обращении к загруженному Web-серверу или другой популярной службе. member procedure close_connection is begin simple_tcp_client.disconnect(g_sock);

g_sock := NULL;

end;

end;

/ Type body created. Как видите, тело типа состоит в основном из вспомогательных подпрограмм, упрощающих использование средств пакета SIMPLE_TCP_CLIENT. Кроме того, мы инкапсулировали средства пакета SIMPLE_TCP_CLIENT в объектный тип. Используя тип SocketType вместо стандартного пакета UTL_TCP, можно реализовать блок кода для получения Web-страницы через промежуточный сервер: tkyte@TKYTE816> declare 2 s SocketType := SocketType(null);

3 buffer varchar2(4096);

4 BEGIN 5 s.initiate_connection('proxy-server', 80);

6 s.send('GET http://www.oracle.com/ HTTP/1.0'||SocketType.CRLF);

7 s.send(SocketType.CRLF);

8 9 loop 10 buffer := s.recv;

11 exit when buffer is null;

12 dbms_output.put_line(substr(buffer,l,255)) ;

13 end loop;

14 s.close_connection;

15 END;

16 / HTTP/1.1 200 OK Date: Thu, 01 Feb 2001 00:16:05 GMT Server: Apache/1.3.9 (Unix) mod_perl/1.21 ApacheJServ/1.1 yyyyyyyyyy: close Content-Type: text/html 23 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с Это код как будто не отличается принципиально от кода, использующего средства пакета UTL_TCP, но показывает, как инкапсуляция пакетов в объектные типы создает приятное впечатление объектно-ориентированного программирования на PL/SQL. Тем, кто привык программировать на языках Java или С++, будет очень удобно работать с Пакет UTL_TCP подобного рода кодом, объявляя тип SocketType и вызывая затем его методы. Это намного удобнее, чем объявлять переменную-запись определенного типа и передавать ее затем в каждую подпрограмму, как приходится делать при использовании пакета UTL_TCP. Такая реализация — скорее объектно-ориентированная (в начале раздела была представлена чисто процедурная).

Резюме В этом разделе мы рассмотрели новые возможности, предоставляемые пакетом UTL_TCP. Вы также познакомились с альтернативной реализацией этих возможностей на языке Java. На их основе мы сформировали новый объектный тип для языка PL/SQL, обеспечивающий все необходимые средства работы с сокетом TCP/IP. Вы увидели, как легко с помощью этого типа включить средства работы с сетью в PL/SQL-приложения (в разделе, посвященном пакету UTL_HTTP, было показано, как на основе этого объектного типа обеспечить полную поддержку протокола HTTP).

в Поддержка, ошибки и сайт p2p.wrox.com Одна из наиболее печальных ситуаций при чтении книг по программированию — когда взятый из книги фрагмент кода, который вы час набирали, не работает. Вы сто раз его проверяли, но потом обнаружили в тексте книги опечатку в имени переменной. Конечно, можно заклеймить позором авторов за то, что они были невнимательны и не протестировали код, редакторов — за то, что не справляются со своими обязанностями, корректоров — за то, что у них не орлиный глаз, но это не поможет решить проблему. Ошибки неистребимы. Мы очень старались, чтобы ошибки в тираж книги не попали, но не можем обещать, что эта книга совершенно свободна от ошибок. Мы делаем все от нас зависящее, предоставляя оперативную поддержку и консультации экспертов, работавших над книгой, и надеемся, что в последующих изданиях найденные ошибки будут исправлены. Кроме того, мы собираемся консультировать читателей не только по вопросам, касающимся книги, но и по разработке приложений, через наши сетевые форумы, где можно задать вопросы автору, рецензентам и лучшим специалистам в соответствующей области. В этом приложении описано, как: • подключиться к работе форумов Programmer To Programmer™ на сайте http:// p2p.wrox.com;

• найти (и послать) информацию об ошибках на нашем основном сайте, http:// www.wrox.com;

• послать службе поддержки запрос или сообщить ваше мнение о книгах по электронной почте.

Приложение В С помощью одной из этих процедур вы сможете быстро получить ответ по интересующей вас проблеме.

Форумы на сайте p2p.wrox.com Подпишитесь на список рассылки по Oracle для получения помощи от автора и читателей. Наша система обеспечивает поддержку разработчиков "Программист — программисту" (Programmer To Programmer™) c помощью списков рассылки, форумов и дискуссионных групп, а также персональной (one-to-one) системы переписки по электронной почте, которую мы вскоре рассмотрим. Будьте уверены: ваш запрос будет рассмотрен не только специалистом службы поддержки, но и многочисленными авторами издательства Wrox и другими экспертами, читающими наши списки рассылки.

Как обратиться за поддержкой Следуйте простой инструкции: • Обратитесь на сайт http://p2p.wrox.com в любом браузере. Здесь вы найдете новости, связанные с работой службы P2P — информацию о создании новых списков рассылки, закрытии списков и т.д. Для этого:

• щелкните на кнопке Databases в левом столбце;

• выберите список рассылки oracle;

• если вы не подписаны на этот список рассылки, можно просматривать сообщения списка, не подписываясь на него, с помощью соответствующих кнопок;

Поддержка, ошибки и сайт p2p.wrox.com • если вы решите подписаться, будет выдана форма, где вы укажете адрес электронной почты, имя и пароль (как минимум, из четырех алфавитно-цифровых символов). Выберите способ получения сообщений из списка и нажмите кнопку Subscribe. • Поздравляем! Вы подписались на список рассылки oracle.

Почему эта система обеспечивает наилучшую поддержку Подписываясь, вы можете выбрать получение сообщений по мере их поступления или раз в день. Можно также подписаться на еженедельный дайджест. Если у вас нет времени или возможности получать сообщения из списка рассылки, можете выполнять поиск в наших сетевых архивах. Вы можете искать по темам или по ключевым словам. Поскольку эти списки рассылки — контролируемые, можно быть уверенным в том, что вы быстро получите достоверную и точную информацию. Сообщения могут редактироваться или перемешаться в соответствующий раздел ведущим списка рассылки, что и делает данный ресурс наиболее эффективным. Ненужные и рекламные сообщения удаляются, а адреса электронной почты подписчиков защищаются уникальной системой Lyris от Web-роботов, которые автоматически перехватывают списки адресов дискуссионных групп. Любые вопросы, касающиеся подписки и списков, следует посылать no адресу support@wrox.com.

Поиск информации об ошибках на сайте www.wrox.com В следующем разделе будет описано по шагам, как послать информацию о найденных ошибках и обратиться за помощью на наш сайт. Поэтому далее будут представлены подразделы, посвященные: • поиску на Web-сайте списка известных ошибок;

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

• что происходит с сообщением после получения.

Поиск информации об ошибках на Web-сайте Прежде чем обращаться с запросом, вы можете сэкономить время, найдя, возможно, ответ на сайте издательства по адресу http://www.wrox.com.

Pages:     | 1 |   ...   | 21 | 22 || 24 |





© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.