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 для профессионалов. Пер. с

Пакет 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 рабочих дней удалим его.