Веб-сервисы широко применяются для интеграции между компонентами одной системы или между различными системами. Популярность веб-сервисов как способа интеграции обусловлена их универсальностью, а также простотой реализации и отладки. Универсальность связана с передачей данных с помощью интернета и протокола HTTP. Веб-сервисы дают возможность относительно легко построить интеграции между компонентами, написанными на разных языках, запускаемыми на разных операционных системах и платформах. Простота реализации веб-сервисов достигается за счет встроенных во многие IDE средств и компонентов, позволяющих быстро разработать как сам веб-сервис (provider side), так и необходимый код для вызова сервиса на стороне клиента (client side). Отладку сервисов упрощает использование понятных человеку форматов обмена данными — XML и JSON. Кроме того, существует множество утилит для отладки и тестирования сервисов, в том числе нагрузочного.
В этой статье рассмотрим несколько способов создания веб-сервисов непосредственно из СУБД Oracle, то есть без использования сторонних средств.
Native Oracle XML DB Web Services
Возможности
Настройка
Настройка Oracle XML DB HTTP server
Настройка доступа через HTTP
Создание сервлета для веб-сервиса
Настройка Access List (ACL)
Пример веб-сервиса с использованием пакетной процедуры
Выводы
Oracle REST Data Service
Возможности
Настройка
Настройка параметров
Установка
Запуск
Настройка доступа
Пример 1: Обработчик для POST-запроса
Создание тестового пользователя для WS
Настройка схемы БД
Создание модуля и шаблона
Создание обработчика HTTP-запроса
Пример вызова
Параметры из запроса
CGI-переменные
Пример 2: Доступ к таблице через ORDS
Создание тестовой таблицы
Открытие доступа к таблице через ORDS
Создание или изменение записи
Получение записей из таблицы
Удаление записи
Выводы
Альтернативные подходы
Java Servlet
Database Access Descriptor (PL/SQL Servlet)
Заключение
Предыстория
Дано: информационная бизнес-система крупной торговой сети (порядка тысячи розничных магазинов), состоящая из множества компонентов и подсистем. Внутри каждого магазина находится главный сервер — БД Oracle с основной бизнес-логикой. Кассовые узлы управляются отдельным ПО со своей локальной БД. Это ПО периодически забирает данные с главного сервера (через WS SOAP) и отдает обратно результаты продажи (файловый обмен).
Прогресс не стоял на месте, и в магазинах появилось новое оборудование. Данные из этого оборудования периодически должны попадать на главный сервер магазина (период — каждые несколько минут), интеграция обязательно должна проходить через веб-сервис, сообщение должно иметь определенный формат, аутентификация не нужна. Мы посмотрели контракты обмена данными и выяснили, что используемая технология веб-сервисов не позволит построить интеграцию с таким оборудованием. И начались поиски решения…
В итоге было рассмотрено несколько вариантов реализации нужного веб-сервиса, вплоть до написания своего отдельного компонента, который открыл бы для БД Oracle окно в мир HTTP: с одной стороны предоставлял бы веб-сервис, с другой — взаимодействовал бы с БД через JDBC. Сложность в том, что новый компонент, во-первых, нужно было бы установить на тысячу магазинов, и, во-вторых, появилось бы еще одно звено, которое нужно было бы сопровождать. Поэтому приоритетным все же был вариант реализации веб-сервиса встроенными средствами Oracle.
В результате поисков мы обнаружили четыре способа, которые рассмотрим в данной статье:
- Native Oracle XML DB Web Services
- Oracle REST Data Service
- Java Servlet
- Database Access Descriptor (PL/SQL servlet)
Первые два варианта рассмотрим более детально. Native Oracle XML DB Web Services использовался в нашей системе изначально, то есть достался, так сказать, по наследству. ORDS стал заменой этой устаревшей технологии (несмотря на то, что пришлось все же потрудиться и установить ORDS на тысячу магазинов).
Два других способа — Java Servlet и PL/SQL Servlet — мы рассматривали наряду с ORDS при поиске альтернативы Native Oracle WS, но в проекте применять не стали. Поэтому эти подходы детально рассматривать не будем и ограничимся короткой справкой.
В статье будут представлены некоторые практические примеры реализации веб-сервисов с инструкциями, которые помогут создать работающий сервис.
Native Oracle XML DB Web Services
Возможности
Позволяет организовать доступ к БД Oracle через HTTP, используя WS SOAP (версия 1.1):
- Выполнение SQL- или XQuery-запросов через SOAP.
- Вызов хранимых процедур или функций (отдельных или из пакета).
Плюсы
- Для поднятия веб-сервиса не требуется установка дополнительного ПО: все необходимое уже имеется в СУБД, включая встроенный HTTP-сервер.
- Для веб-сервиса автоматически формируется описание (WSDL), которое может быть использовано потребителем сервиса для автоматической генерации кода, вызывающего веб-сервис.
- Позволяет быстро реализовать или доработать веб-сервис. По сути, создание нового сервиса — это реализация нового пакета, а создание нового метода сервиса — это написание новой процедуры или функции в пакете.
- Автоматический парсинг входящих XML-запросов. С одной стороны, это плюс, потому что не нужно думать о структуре XML, реализовывать свои парсеры и следить за их актуальностью при изменении спецификации веб-сервиса — все это Oracle делает автоматически. С другой стороны, к этой внутренней кухне преобразования WS-запроса в вызов процедуры Oracle нет никакого доступа —никак нельзя переопределить эти алгоритмы, если вдруг понадобится что-то оптимизировать.
Минусы
- Автоматически генерируемый WSDL имеет ряд ограничений, и нет возможности править его вручную, например указать необязательность каких-то атрибутов. Для решения этой проблемы есть обходной путь — вместо автоматически генерируемого использовать сформированный вручную WSDL, но тогда придется написать текст WSDL самостоятельно, придерживаясь правил именования атрибутов.
- Может вести себя непредсказуемо на больших запросах (порядка 50 Мб). В лучшем случае падает с ошибкой нехватки памяти на сервере, в худшем — отдает ответ, в котором потеряна часть сообщения. Вообще, большие запросы при использовании веб-сервисов — это скорее исключение, чем правило, но лучше подстраховаться.
Стоит отметить, что для всех запросов к веб-сервису обязательна аутентификация (из документации: Basic Authentication: Oracle XML DB supports Basic Authentication, where a client sends the user name and password in clear text in the Authorization header). В Oracle можно настроить анонимный доступ к ресурсам сервера через HTTP — с помощью настройки Oracle XML DB Protocol Server, но по факту это работает только для GET-запросов, а для POST-запросов аутентификация обязательна. Поскольку Native Oracle WS работает только через POST-запросы, возможности настройки анонимного доступа для этой технологии нет.
Настройка
Для работы Native Oracle WS понадобится:
- Настроить встроенный в Oracle HTTP-сервер.
- Настроить доступ внутри БД Oracle (открыть HTTP-порт).
- Создать сервлет.
- Настроить ACL (Access List).
Настройка Oracle XML DB HTTP server
HTTP-сервер уже должен работать по умолчанию, но в некоторых случаях может потребоваться дополнительная конфигурация listener — добавление в DESCRIPTION_LIST следующего блока:
(DESCRIPTION=
(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=8080))(Presentation=HTTP)(Session=RAW)
)
После этого нужно перезапустить listener.
Настройка доступа через HTTP
- Проверка текущего порта для HTTP.
SELECT dbms_xdb.gethttpport() AS http_port FROM dual;
Значение «0» означает, что доступ через HTTP отключен. - Установка порта.
BEGIN -- Установка порта для web-services dbms_xdb.setHttpPort(8080); COMMIT; END; /
Создание сервлета для веб-сервиса
Для работы веб-сервиса требуется регистрация сервлета в конфигурации БД.
-- Выполнять под SYS
DECLARE
l_servlet_name VARCHAR2(32) := 'orawsv';
BEGIN
-- Регистрация сервлета orawsv для активации Native Web Services
DBMS_XDB.deleteServletMapping(l_servlet_name);
DBMS_XDB.deleteServlet(l_servlet_name);
DBMS_XDB.addServlet( NAME => l_servlet_name
, LANGUAGE => 'C'
, DISPNAME => 'Oracle Query Web Service'
, DESCRIPT => 'Servlet for issuing queries as a Web Service'
, SCHEMA => 'XDB');
DBMS_XDB.addServletSecRole( SERVNAME => l_servlet_name
, ROLENAME => 'XDB_WEBSERVICES'
, ROLELINK => 'XDB_WEBSERVICES');
DBMS_XDB.addServletMapping( PATTERN => '/orawsv/*'
, NAME => l_servlet_name);
COMMIT;
END;
/
Настройка Access List
Для доступа к Oracle через HTTP нужно добавить правила в конфигурации СУБД. Делается это с помощью встроенных утилит СУБД.
Для настройки ACL понадобятся:
- cкрипт, редактирующий конфигурацию БД (ниже);
- cхема БД, для которой делается настройка.
То есть схема БД, для которой делается настройка ACL, должна быть уже создана. В примерах ниже будут отсылки к этому разделу, в тех местах, где необходимо создать новые схемы БД — для них нужно будет выполнять настройку ACL.
-- Выполнять под SYS
DECLARE
l_ws_user VARCHAR2(32) := 'WS_SOAP_TEST'; -- Указать имя схемы БД, где предполагается развернуть объекты для обработки запросов WS
l_acl VARCHAR2(250) := 'acl_allow_all.xml';
l_tmp VARCHAR2(250);
BEGIN
EXECUTE IMMEDIATE 'GRANT XDB_WEBSERVICES TO "'||l_ws_user||'"';
EXECUTE IMMEDIATE 'GRANT XDB_WEBSERVICES_OVER_HTTP TO "'||l_ws_user||'"';
EXECUTE IMMEDIATE 'GRANT XDB_WEBSERVICES_WITH_PUBLIC TO "'||l_ws_user||'"';
-- Создание списка разрешений сетевого доступа из PL/SQL пакетов
BEGIN
dbms_network_acl_admin.drop_acl(acl => '/sys/acls/'||l_acl);
EXCEPTION
WHEN dbms_network_acl_admin.acl_not_found THEN
NULL;
END;
-- Создание ACL
dbms_network_acl_admin.create_acl( acl => l_acl
, description => 'Allow all connections'
, is_grant => TRUE
, start_date => SYSTIMESTAMP
, end_date => NULL
, principal => 'SYS'
, privilege => 'connect');
dbms_network_acl_admin.assign_acl( acl => l_acl
, host => '*'
, lower_port => NULL
, upper_port => NULL);
-- Права на разрешение (resolve)сетевого имени
dbms_network_acl_admin.add_privilege( acl => l_acl
, principal => l_ws_user
, is_grant => TRUE
, privilege => 'resolve'
, POSITION => NULL
, start_date => SYSTIMESTAMP
, end_date => NULL);
COMMIT;
END;
/
Сразу нужно отметить, что настройка ACL требуется при использовании не только Native Oracle WS, но и всех других рассмотренных в данной статье способов создания веб-сервисов.
Пример веб-сервиса с использованием пакетной процедуры
Нам понадобятся:
- Схема БД, в которой будут находиться объекты для обработки запросов веб-сервиса.
- Таблица для логгирования.
- Пакет с процедурой/функцией.
- Любое средство, позволяющее отправлять веб-запросы. В данном случае использовалось приложение SOAP UI, но можно выбрать любое другое средство, вплоть до командной строки.
Особенности:
- Параметры процедуры должны иметь либо простые, либо объектные типы. Иначе процедура не будет восприниматься как метод веб-сервиса и не будет включена в список методов сервиса.
- Для реализации сложных структур входных или выходных данных нужно использовать объектные типы (пакетные типы использовать нельзя).
Создаем необходимые объекты БД:
- Схема
WS_TEST
:
CREATE USER WS_SOAP_TEST IDENTIFIED BY ws_soap_test QUOTA 200M ON USERS; GRANT CREATE SESSION, RESOURCE TO ws_soap_test;
Сразу добавляем новую схему в ACL. Скрипт в предыдущем разделе. - В новой схеме создаем таблицу для логгирования
T_WS_REQ_LOG
:
CREATE TABLE T_WS_REQ_LOG ( id_log NUMBER GENERATED ALWAYS AS IDENTITY, message VARCHAR2(2000), proc VARCHAR2(128), dtm_request TIMESTAMP(6) DEFAULT SYSTIMESTAMP ); COMMENT ON TABLE T_WS_REQ_LOG IS 'Лог HTTP-запросов'; COMMENT ON COLUMN T_WS_REQ_LOG.id_log IS 'ПК'; COMMENT ON COLUMN T_WS_REQ_LOG.message IS 'Сообщение'; COMMENT ON COLUMN T_WS_REQ_LOG.proc IS 'Процедура'; COMMENT ON COLUMN T_WS_REQ_LOG.dtm_request IS 'Дата/время запроса';
- Пакет с простой процедурой:
CREATE OR REPLACE PACKAGE PK_NATIVE_WS_TEST IS PROCEDURE proc_simple ( a_id INTEGER , a_data VARCHAR2 , o_result OUT VARCHAR2 ); END PK_NATIVE_WS_TEST; / CREATE OR REPLACE PACKAGE BODY PK_NATIVE_WS_TEST IS PROCEDURE proc_simple ( a_id INTEGER , a_data VARCHAR2 , o_result OUT VARCHAR2 ) AS BEGIN INSERT INTO t_ws_req_log (message, proc) VALUES ('ID='||a_id||'; DATA='||a_data, 'proc_simple') RETURNING id_log INTO o_result; END proc_simple; END PK_NATIVE_WS_TEST; /
Простейший веб-сервис создан и готов к работе.
Для доступа к сервису нужно использовать URL следующего формата:
http://[server]:[port]/[servlet_name]/[DB_SCHEMA]/[WS_OBJ]?wsdl
Где:
[server] — доменное имя или IP-адрес сервера БД Oracle
[port] — порт для доступа через HTTP, указанный в разделе «Настройка доступа через HTTP»
[servlet_name] — имя сервлета, указанное в разделе «Создание сервлета для веб-сервиса»
[DB_SCHEMA] — имя схемы БД (в верхнем регистре)
[WS_OBJ] — имя сервиса (в верхнем регистре), которое равно имени объекта БД, в нашем случае – имени пакета
Обратите внимание, что в URL регистр важен!
Пример ссылки:
http://my.server:8080/orawsv/WS_SOAP_TEST/PK_NATIVE_WS_TEST?wsdl
Если перейти по этой ссылке в браузере, получим автоматически созданный на основе нашего пакета WSDL:
<definitions name="PK_NATIVE_WS_TEST"
targetNamespace="http://xmlns.oracle.com/orawsv/WS_SOAP_TEST/PK_NATIVE_WS_TEST"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://xmlns.oracle.com/orawsv/WS_SOAP_TEST/PK_NATIVE_WS_TEST"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<xsd:schema targetNamespace="http://xmlns.oracle.com/orawsv/WS_SOAP_TEST/PK_NATIVE_WS_TEST"
elementFormDefault="qualified">
<xsd:element name="PROC_SIMPLEInput">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="A_DATA-VARCHAR2-IN" type="xsd:string"/>
<xsd:element name="A_ID-NUMBER-IN" type="xsd:integer"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="PROC_SIMPLEOutput">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="O_RESULT" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</types>
<message name="PROC_SIMPLEInputMessage">
<part name="parameters" element="tns:PROC_SIMPLEInput"/>
</message>
<message name="PROC_SIMPLEOutputMessage">
<part name="parameters" element="tns:PROC_SIMPLEOutput"/>
</message>
<portType name="PK_NATIVE_WS_TESTPortType">
<operation name="PROC_SIMPLE">
<input message="tns:PROC_SIMPLEInputMessage"/>
<output message="tns:PROC_SIMPLEOutputMessage"/>
</operation>
</portType>
<binding name="PK_NATIVE_WS_TESTBinding"
type="tns:PK_NATIVE_WS_TESTPortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="PROC_SIMPLE">
<soap:operation
soapAction="PROC_SIMPLE"/>
<input>
<soap:body parts="parameters" use="literal"/>
</input>
<output>
<soap:body parts="parameters" use="literal"/>
</output>
</operation>
</binding>
<service name="PK_NATIVE_WS_TESTService">
<documentation>Oracle Web Service</documentation>
<port name="PK_NATIVE_WS_TESTPort" binding="tns:PK_NATIVE_WS_TESTBinding">
<soap:address
location="http://******:8080/orawsv/WS_SOAP_TEST/PK_NATIVE_WS_TEST"/>
</port>
</service>
</definitions>
Для проверки сервиса создаем новый SOAP-проект в SOAP UI. В качестве URL указываем адрес из примера выше.
При открытии
Request 1
видим, что шаблон запроса уже сформирован автоматически по указанному WSDL.Сразу же добавляем Basic-авторизацию для запроса — логин и пароль такие же, как при подключении в БД.
Пример запроса и ответа. Обратите внимание, что в автоматически сформированный запрос вручную добавлена строка
<pk:O_RESULT-VARCHAR2-OUT/>
. Дело в том, что в Oracle 12c есть баг: OUT-переменные не добавляются в WSDL для Native Oracle WS).Результат вызова видим в таблице лога.
Выводы
Технологию Native Oracle XML DB Web Services можно использовать как промышленное решение при условии, что на этот веб-сервис нет большой нагрузки (один запрос в несколько секунд). Этот вариант подойдет, когда у потребителей нет жестких требований к веб-сервису (его атрибутам, структурам данных, логике обработки) и нет строгого заранее определенного контракта. Если же нужно создать веб-сервис по заранее определенному WSDL (как в нашем случае с новым оборудованием), то технология Native Oracle WS окажется непригодна.
Oracle REST Data Service
Начиная с версии 11.1 в Oracle появилась полноценная поддержка RESTful в виде отдельного модуля, называемого Oracle REST Data Service (ORDS).
ORDS — это Java-приложение, которое позволяет создавать RESTful API для базы данных Oracle, используя SQL и PL/SQL. Является альтернативой использованию Oracle HTTP Server и mod_plsql. По сути ORDS — это HTTP-интерфейс между внешним миром и БД Oracle. Этот интерфейс позволяет замкнуть входящие HTTP-запросы на какой-либо объект базы данных — таблицу или PL/SQL-процедуру.
Все, что требуется сделать, — это установить, настроить и запустить ORDS для требуемой БД. Дальнейший процесс создания REST-сервисов сводится к написанию кода на PL/SQL (или даже кликанью мышкой в IDE, если код писать лень).
Для установки ORDS не требуется никаких дополнительных лицензий.
Требования:
- Java JDK 8 или выше;
- Oracle 11.1 или выше (также поддерживается Oracle 11 XE Release 2).
Поддерживается несколько вариантов развертывания ORDS:
- автономный (standalone) режим;
- на сервере приложений (Oracle WebLogic Server, Apache Tomcat).
Возможности
ORDS позволяет:
- Организовать доступ к ресурсам в стиле RESTful.
- Построить взаимодействие в стиле «Вызов удаленной процедуры» (RPC-style interaction).
Проще говоря, с помощью ORDS можно открыть доступ через HTTP к определенным объектам БД — таблицам, процедурам, функциям, пакетам.
В первом случае имеем дело с ресурсами в том смысле, как они понимаются в архитектурном стиле RESTful. Каждый ресурс определяется уникальным URI, а операции с ресурсами (CRUD — создание, чтение, изменение, удаление) определяются операциями из HTTP-запроса: PUT, POST, GET, DELETE. Например, если ресурс — это запись в таблице сотрудников, то эта запись будет доступна по URI вида:
GET https://server:port/ords/workspace/hr/employees/7369
Здесь в URI указывается имя схемы БД, имя таблицы и ID записи в таблице. Таким образом, этот HTTP-запрос на самом деле выполняет операцию выбора (SELECT) из указанной таблицы. С остальными операциями (добавление, изменение, удаление) принцип общения такой же: в URI указывается путь к ресурсу, а в теле запроса — дополнительные параметры, например значения полей для вставки записи в таблицу.
Во втором случае вместо обращения к ресурсу напрямую используется вызов процедуры, которая может делать все то же самое, что и в первом случае (CRUD), а также исполнять любую другую логику, реализующую нужный бизнес-процесс. Вызов процедуры из пакета может быть сделан с помощью HTTP-запроса такого формата:
http://localhost:8080/ords/my_schema/my_pkg/MY_PROC
Параметры для процедуры передаются в теле запроса в виде JSON-структур формата
{"param" : "value"}
. Такой же подход используется и при применении Native Oracle WS, рассмотренном выше, но в случае REST-сервисов нет ограничений, накладываемых протоколом SOAP.Плюсы
- Гибкий механизм, позволяющий реализовать веб-сервисы любой сложности.
- Простота реализации: для превращения PL/SQL-процедуры в веб-сервис нужно выполнить всего несколько типовых команд. А с использованием SQL Developer создание REST API превращается в кликанье мышкой с минимумом написания кода.
- Возможность реализации веб-сервиса без аутентификации.
Минус
- ORDS не входит в стандартную поставку Oracle — его нужно устанавливать отдельно для каждого сервера (дополнительных лицензий, как уже говорилось, не требуется). Это может быть проблемой, если у вас имеются тысячи серверов БД.
Настройка
ORDS можно запустить в двух режимах: через сервер приложений или в автономном (standalone) режиме.
В данной статье рассматривается только вариант запуска в standalone-режиме.
Для работы ORDS нужно:
- Настроить параметры перед установкой.
- Выполнить установку ODRS.
- Запустить ORDS.
- Настроить ACL (для нужной схемы БД).
Настройка параметров
В дистрибутиве ORDS имеется файл с параметрами, который по умолчанию выглядит так:
db.hostname=
db.port=
db.servicename=
db.sid=
db.username=APEX_PUBLIC_USER
migrate.apex.rest=false
rest.services.apex.add=
rest.services.ords.add=true
schema.tablespace.default=SYSAUX
schema.tablespace.temp=TEMP
standalone.http.port=8080
standalone.static.images=
user.tablespace.default=USERS
user.tablespace.temp=TEMP
Описание параметров можно найти в документации
Параметры для standalone-режима:
Параметр | Описание |
db.hostname |
Имя или IP-адрес сервера БД Oracle |
db.port |
Порт БД (например, 1521) |
db.servicename |
Servicename базы данных |
db.username |
Имя пользователя PL/SQL-шлюза. По умолчанию = APEX_PUBLIC_USER, но в примере не планируется использовать APEX, поэтому можно не заполнять этот параметр |
db.password |
Пароль пользователя (тоже не заполняем) |
migrate.apex.rest |
Переход RESTful сервисов APEX на ORDS. Ставим false, если не используется APEX |
rest.services.apex.add |
Конфигурировать ORDS для использования в APEX |
rest.services.ords.add |
Установить схему БД для ORDS (ORDS_PUBLIC_USER и ORDS_METADATA). Значение должно быть true |
schema.tablespace.default |
Табличное пространство по умолчанию для схемы ORDS_METADATA |
schema.tablespace.temp |
Временное табличное пространство для схемы ORDS_METADATA |
standalone.http.port |
Порт, на котором будет работать ORDS. Этот порт будет использоваться в URI для доступа к WS |
user.tablespace.default |
Табличное пространство по умолчанию для схемы ORDS_PUBLIC_USER |
user.tablespace.temp |
Временное табличное пространство для схемы ORDS_PUBLIC_USER |
db.hostname=your_server_host_name
db.port=1521
db.servicename=your_db_servicename
migrate.apex.rest=false
rest.services.apex.add=false
rest.services.ords.add=true
schema.tablespace.default=SYSAUX
schema.tablespace.temp=TEMP
standalone.http.port=8888
standalone.static.images=
user.tablespace.default=USERS
user.tablespace.temp=TEMP
Настройка необходимых параметров завершена, можно приступать к установке.
Установка
Выполняем в ОС команду:
java -jar ords.war
По умолчанию файл конфигурации берется из каталога
params
, расположенного рядом с ords.war
. Можно явно указать путь к этому файлу, используя параметр --parameterFile
:java -jar ords.war --parameterFile /path/to/params/myown_params.properties
В диалоге установки нужно сделать следующее:
- Указываем путь к каталогу для сохранения конфигурации (в примере указан
conf
— в результате в том же каталоге, где находится файлords.war
, будет создан каталогconf
, в котором будут созданы файлы с конфигурацией ORDS). - После появления указания Enter the database password for ORDS_PUBLIC_USER водим пароль для схемы
ORDS_PUBLIC_USER
. Под этим пользователем ORDS будет подключаться к БД. - После появления указания Enter 1 if you want to use PL/SQL Gateway or 2 to skip this step отвечаем «2», так как мы не собираемся использовать APEX и нам не нужно делать миграцию с
mod_plsql
. - После появления указания Enter 1 if you wish to start in standalone mode or 2 to exit [1] отвечаем «1».
- После появления указания Enter 1 if using HTTP or 2 if using HTTPS [1] отвечаем 1.
D:\ords-19.2.0.199.1647>java -jar ords.war install
This Oracle REST Data Services instance has not yet been configured.
Please complete the following prompts
Enter the location to store configuration data: conf
Enter the database password for ORDS_PUBLIC_USER:
Confirm password:
ёхэ 03, 2019 2:47:49 PM oracle.dbtools.rt.config.setup.SchemaSetup getInstallOrUpgrade
WARNING: Failed to connect to user ORDS_PUBLIC_USER jdbc:oracle:thin:@//***YOUR_HOST_NAME.DOMAIN***:1521/***YOUR_SERVICE_NAME.DOMAIN***
Enter 1 if you want to use PL/SQL Gateway or 2 to skip this step.
If using Oracle Application Express or migrating from mod_plsql then you must enter 1 [1]:2
ёхэ 03, 2019 2:48:32 PM
INFO: reloaded pools: []
Enter 1 if you wish to start in standalone mode or 2 to exit [1]:
Enter 1 if using HTTP or 2 if using HTTPS [1]:
2019-09-03 14:48:49.754:INFO::main: Logging initialized @4276887ms to org.eclipse.jetty.util.log.StdErrLog
ёхэ 03, 2019 2:48:49 PM
INFO: HTTP and HTTP/2 cleartext listening on port: 8082
ёхэ 03, 2019 2:48:50 PM
INFO: Disabling document root because the specified folder does not exist: D:\ords-19.2.0.199.1647\conf\ords\standalone\doc_root
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at oracle.dbtools.jarcl.Entrypoint.invoke(Entrypoint.java:66)
at oracle.dbtools.jarcl.Entrypoint.main(Entrypoint.java:89)
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(Unknown Source)
at java.lang.AbstractStringBuilder.append(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at java.util.AbstractMap.toString(Unknown Source)
at java.lang.String.valueOf(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at oracle.dbtools.jarcl.zip.ZipIndex.toString(ZipIndex.java:166)
at java.lang.String.valueOf(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at oracle.dbtools.jarcl.JarClassLoader.toString(JarClassLoader.java:51)
at org.eclipse.jetty.server.ClassLoaderDump.dump(ClassLoaderDump.java:67)
at org.eclipse.jetty.util.component.Dumpable.dumpObjects(Dumpable.java:225)
at org.eclipse.jetty.util.component.ContainerLifeCycle.dumpObjects(ContainerLifeCycle.java:746)
at org.eclipse.jetty.server.handler.ContextHandler.dump(ContextHandler.java:259)
at org.eclipse.jetty.util.component.Dumpable.dumpObjects(Dumpable.java:162)
at org.eclipse.jetty.util.component.ContainerLifeCycle.dumpObjects(ContainerLifeCycle.java:746)
at org.eclipse.jetty.util.component.ContainerLifeCycle.dump(ContainerLifeCycle.java:701)
at org.eclipse.jetty.util.component.Dumpable.dump(Dumpable.java:62)
at org.eclipse.jetty.util.component.ContainerLifeCycle.dump(ContainerLifeCycle.java:684)
at oracle.dbtools.standalone.StandaloneConfiguration.start(StandaloneConfiguration.java:241)
at oracle.dbtools.standalone.Standalone.execute(Standalone.java:508)
at oracle.dbtools.cmdline.DefaultCommand.execute(DefaultCommand.java:137)
at oracle.dbtools.cmdline.Commands.execute(Commands.java:207)
at oracle.dbtools.cmdline.Commands.main(Commands.java:163)
at oracle.dbtools.cmdline.Commands.main(Commands.java:368)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at oracle.dbtools.jarcl.Entrypoint.invoke(Entrypoint.java:66)
В результате будет создан каталог с конфигурацией ORDS, а в БД появятся служебные схемы для ORDS.
Cм. также: Install Oracle REST Data Services 3.0.X in under 5 minutes.
Запуск
Запуск в автономном режиме выполняется командой:
java -jar ords.war standalone
D:\ords-19.2.0.199.1647>java -jar ords.war standalone
2019-09-03 15:52:45.825:INFO::main: Logging initialized @2079ms to org.eclipse.jetty.util.log.StdErrLog
ёхэ 03, 2019 3:52:45 PM
INFO: HTTP and HTTP/2 cleartext listening on port: 8082
ёхэ 03, 2019 3:52:45 PM
INFO: Disabling document root because the specified folder does not exist: D:\ords-19.2.0.199.1647\conf\ords\standalone\doc_root
2019-09-03 15:52:47.124:INFO:oejs.Server:main: jetty-9.4.z-SNAPSHOT; built: 2019-05-02T09:46:34.874Z; git: 14f32d50076f2b706f41a33066eb364d8492e199; jvm 1.8.0_221-b11
2019-09-03 15:52:47.179:INFO:oejs.session:main: DefaultSessionIdManager workerName=node0
2019-09-03 15:52:47.179:INFO:oejs.session:main: No SessionScavenger set, using defaults
2019-09-03 15:52:47.180:INFO:oejs.session:main: node0 Scavenging every 660000ms
ёхэ 03, 2019 3:52:48 PM
INFO: Configuration properties for: |apex|pu|
db.hostname=***YOUR_HOST_NAME.DOMAIN***
db.password=******
db.port=1521
db.servicename=***YOUR_SERVICE_NAME.DOMAIN***
db.username=ORDS_PUBLIC_USER
resource.templates.enabled=true
ёхэ 03, 2019 3:52:48 PM
WARNING: *** jdbc.MaxLimit in configuration |apex|pu| is using a value of 10, this setting may not be sized adequately for a production environment ***
ёхэ 03, 2019 3:52:48 PM
WARNING: *** jdbc.InitialLimit in configuration |apex|pu| is using a value of 3, this setting may not be sized adequately for a production environment ***
ёхэ 03, 2019 3:52:50 PM
INFO: Oracle REST Data Services initialized
Oracle REST Data Services version : 19.2.0.r1991647
Oracle REST Data Services server info: jetty/9.4.z-SNAPSHOT
2019-09-03 15:52:50.484:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@d56d67{/ords,null,AVAILABLE}
2019-09-03 15:52:50.658:INFO:oejs.AbstractConnector:main: Started ServerConnector@12325ad{HTTP/1.1,[http/1.1, h2c]}{0.0.
0.0:8082}
2019-09-03 15:52:50.659:INFO:oejs.Server:main: Started @6914ms
После этого ORDS запущен и работает. Адрес веб-сервиса будет равен http://<имя_хоста>:/ords/…, где <имя_хоста> — имя компьютера, на котором запущен ORDS (оно не обязательно должно совпадать с именем сервера БД, то есть ORDS можно запустить на другом хосте), <pоrt> — порт, указанный в конфигурации ORDS.
При желании можно оформить скрипт для автоматического запуска ORDS при старте ОС.
Настройка доступа
И последний шаг — настройка ACL. Действия аналогичны тем, что и при использовании Native Oracle WS.
Выполнять этот шаг нужно после того, как будет создана схема БД, поэтому в примерах ниже будет отдельно указано, когда нужно выполнить данную настройку. Пока можно считать, что все предварительные действия выполнены, и приступать к примерам.
Пример 1: Обработчик для POST-запроса
В качестве примера рассмотрим вариант реализации RPC-style interaction, то есть сделаем метод веб-сервиса, обработчиком которого будет пакетная процедура.
Собственно, именно в таком варианте работа с ORDS была реализована в нашем проекте для торговой сети, и результат оправдал свои ожидания. Получился быстрый и универсальный способ создания новых методов веб-сервисов для разных бизнес-процессов — от работы с оборудованием до API для сайтов.
Как уже упоминалось выше, поддержка ORDS включена также в SQL Developer (IDE для баз данных, разрабатываемая Oracle). В SQL Developer создание веб-сервисов доступно прямо из пунктов меню или контекстного меню.
В этой статье примеры сделаны с помощью SQL Developer (версия 18), но также прилагается PL/SQL-код, который выполняется в БД в результате действий в IDE.
Версия БД, на которой делались эксперименты:
SQL> SELECT v.BANNER FROM v$version v;
BANNER
--------------------------------------------------------------------------------
Oracle DATABASE 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
PL/SQL Release 12.2.0.1.0 - Production
CORE 12.2.0.1.0 Production
TNS FOR Linux: Version 12.2.0.1.0 - Production
NLSRTL Version 12.2.0.1.0 - Production
Создание тестового пользователя для WS
Для примеров нам снова понадобится новая схема — создаем ее:
CREATE USER WS_TEST IDENTIFIED BY ws_test QUOTA 200M ON USERS;
GRANT CREATE SESSION, RESOURCE TO ws_test;
Настройка схемы БД
После того, как схема создана, необходимо сделать ее доступной через ORDS.
Для этого:
- В SQL Developer создаем подключение для
WS_TEST
. - В списке подключений вызываем контекстное меню для нового подключения. Выбираем REST Services > Enable REST Services...:
- В диалоге ставим галку напротив Enable schema (также здесь можно изменить параметр «Псевдоним схемы» (Schema alias), если нужно, чтобы вместо имени схемы
ws_test
в URI отображался другой текст):
- Нажав «Далее», увидим PL/SQL-код, который в итоге будет выполнен в БД (и который можно написать самостоятельно, не пользуясь IDE):
Листинг:
BEGIN
ORDS.ENABLE_SCHEMA( p_enabled => TRUE
, p_schema => 'WS_TEST'
, p_url_mapping_type => 'BASE_PATH'
, p_url_mapping_pattern => 'ws_test'
, p_auto_rest_auth => FALSE);
COMMIT;
END;
/
Создание модуля и шаблона
Далее нужно создать модуль и шаблон для ресурса.
Модуль — это структурная единица, которая позволяет сгруппировать несколько логически связанных шаблонов ресурсов.
Шаблон — конкретный веб-сервис, обслуживающий определенный набор методов для ресурса.
В источнике:
Resource module: An organizational unit that is used to group related resource templates.
Resource template: An individual RESTful service that is able to service requests for some set of URIs (Universal Resource Identifiers). The set of URIs is defined by the URI Pattern of the Resource Template.
Например, для интернет-магазина модуль можно назвать
shop
— в этом модуле будут объединены все API магазина. Шаблонами могут быть конкретные ресурсы, например, заказ (шаблон order
), каталог товаров (шаблон item
), оплата (шаблон payment
) и т. д. Для создания модуля и шаблона нужно выполнить такие шаги:
- В дереве объектов открываем раздел REST Data Services, вызываем контекстное меню в разделе Modules, выбираем New module...:
- В диалоге создания модуля указываем имя модуля (
shop
), URI prefix (тоже указываемshop
— ниже в примере сразу же видим, какой будет шаблон адреса веб-сервиса), ставим галку напротив Publish, жмем «Следующий >»:
- В следующем диалоге указываем имя шаблона, например
order
, и снова жмем «Следующий >».
В последнем диалоге видим все введенные параметры модуля и шаблона. На вкладке SQL можно увидеть PL/SQL-код, который будет выполнен в БД при нажатии «Готово»:
Для созданного модуля
shop
можно добавить еще шаблонов — также из дерева объектов, вызвав контекстное меню и выбрав пункт Add Template....BEGIN
ORDS.DEFINE_MODULE( p_module_name => 'shop'
, p_base_path => 'shop'
, p_items_per_page => 25
, p_status => 'PUBLISHED'
, p_comments => NULL);
ORDS.DEFINE_TEMPLATE( p_module_name => 'shop'
, p_pattern => 'order'
, p_priority => 0
, p_etag_type => 'HASH'
, p_etag_query => NULL
, p_comments => NULL);
COMMIT;
END;
/
Создание обработчика HTTP-запроса
Теперь нам нужно создать обработчик HTTP-запроса для нашего сервиса.
Сначала нам понадобится создать объекты БД, которые будут содержать логику обработки запроса.
Нам потребуются таблица для логгирования и пакет, в котором будет код обработчика.
CREATE TABLE T_WS_LOG
(
id_log NUMBER GENERATED ALWAYS AS IDENTITY,
message VARCHAR2(2000),
request_header VARCHAR2(2000),
request_body CLOB,
response_header VARCHAR2(2000),
response_body CLOB,
dtz_log TIMESTAMP(6) WITH TIME ZONE DEFAULT SYSTIMESTAMP
);
COMMENT ON TABLE T_WS_LOG IS 'Лог обработки HTTP-запросов (ORDS)';
COMMENT ON COLUMN T_WS_LOG.id_log IS 'ПК';
COMMENT ON COLUMN T_WS_LOG.message IS 'Сообщение лога';
COMMENT ON COLUMN T_WS_LOG.request_header IS 'Заголовок запроса';
COMMENT ON COLUMN T_WS_LOG.request_body IS 'Тело запроса';
COMMENT ON COLUMN T_WS_LOG.response_header IS 'Заголовок ответа';
COMMENT ON COLUMN T_WS_LOG.response_body IS 'Тело ответа';
COMMENT ON COLUMN T_WS_LOG.dtz_log IS 'Дата/время записи лога';
ALTER TABLE T_WS_LOG ADD CONSTRAINT PK_T_WS_LOG PRIMARY KEY (ID_LOG) USING INDEX;
CREATE OR REPLACE PACKAGE PK_ORDS_API IS
FUNCTION blob2clob
( a_blob BLOB
, a_from_charset VARCHAR2 := 'AMERICAN_AMERICA.AL32UTF8'
, a_to_charset VARCHAR2 := 'AMERICAN_AMERICA.AL32UTF8'
) RETURN CLOB;
PROCEDURE process_request
( a_request CLOB
);
END PK_ORDS_API;
/
CREATE OR REPLACE PACKAGE BODY PK_ORDS_API IS
FUNCTION blob2clob
( a_blob BLOB
, a_from_charset VARCHAR2 := 'AMERICAN_AMERICA.AL32UTF8'
, a_to_charset VARCHAR2 := 'AMERICAN_AMERICA.AL32UTF8'
) RETURN CLOB
AS
l_clob CLOB;
l_amount NUMBER := 2000;
l_offset NUMBER := 1;
l_buffer VARCHAR2(32767);
l_length PLS_INTEGER := dbms_lob.getlength(a_blob);
BEGIN
dbms_lob.createtemporary(l_clob, TRUE);
dbms_lob.OPEN(l_clob, dbms_lob.lob_readwrite);
WHILE l_offset <= l_length LOOP
l_buffer := UTL_RAW.cast_to_varchar2(UTL_RAW.convert( r => dbms_lob.substr(a_blob, l_amount, l_offset)
, from_charset => a_from_charset
, to_charset => a_to_charset));
IF LENGTH(l_buffer) > 0 THEN
dbms_lob.writeappend(l_clob, LENGTH(l_buffer), l_buffer);
END IF;
l_offset := l_offset + l_amount;
EXIT WHEN l_offset > l_length;
END LOOP;
RETURN l_clob;
END blob2clob;
PROCEDURE process_request
( a_request CLOB
)
AS
TYPE TStringHash IS TABLE OF VARCHAR2(256) INDEX BY VARCHAR2(256);
lh_hdr TStringHash;
l_hdr VARCHAR2(256);
l_resp CLOB;
l_response_status INTEGER := 200;
l_ccontent_type VARCHAR2(64) := 'application/json';
l_in_headers VARCHAR2(32767);
BEGIN
-- Заголовки и прочие параметры входящего запроса
lh_hdr('SERVER_SOFTWARE') := OWA_UTIL.get_cgi_env('SERVER_SOFTWARE');
lh_hdr('SERVER_NAME') := OWA_UTIL.get_cgi_env('SERVER_NAME');
lh_hdr('GATEWAY_INTERFACE') := OWA_UTIL.get_cgi_env('GATEWAY_INTERFACE');
lh_hdr('SERVER_PROTOCOL') := OWA_UTIL.get_cgi_env('SERVER_PROTOCOL');
lh_hdr('SERVER_PORT') := OWA_UTIL.get_cgi_env('SERVER_PORT');
lh_hdr('REQUEST_METHOD') := OWA_UTIL.get_cgi_env('REQUEST_METHOD');
lh_hdr('PATH_INFO') := OWA_UTIL.get_cgi_env('PATH_INFO');
lh_hdr('PATH_TRANSLATED') := OWA_UTIL.get_cgi_env('PATH_TRANSLATED');
lh_hdr('SCRIPT_NAME') := OWA_UTIL.get_cgi_env('SCRIPT_NAME');
lh_hdr('QUERY_STRING') := OWA_UTIL.get_cgi_env('QUERY_STRING');
lh_hdr('REMOTE_HOST') := OWA_UTIL.get_cgi_env('REMOTE_HOST');
lh_hdr('REMOTE_ADDR') := OWA_UTIL.get_cgi_env('REMOTE_ADDR');
lh_hdr('AUTH_TYPE') := OWA_UTIL.get_cgi_env('AUTH_TYPE');
lh_hdr('REMOTE_USER') := OWA_UTIL.get_cgi_env('REMOTE_USER');
lh_hdr('REMOTE_IDENT') := OWA_UTIL.get_cgi_env('REMOTE_IDENT');
lh_hdr('CONTENT-TYPE') := OWA_UTIL.get_cgi_env('CONTENT-TYPE');
lh_hdr('CONTENT-LENGTH') := OWA_UTIL.get_cgi_env('CONTENT-LENGTH');
lh_hdr('HTTP_ACCEPT') := OWA_UTIL.get_cgi_env('HTTP_ACCEPT');
lh_hdr('HTTP_ACCEPT_LANGUAGE') := OWA_UTIL.get_cgi_env('HTTP_ACCEPT_LANGUAGE');
lh_hdr('HTTP_USER_AGENT') := OWA_UTIL.get_cgi_env('HTTP_USER_AGENT');
lh_hdr('HTTP_COOKIE') := OWA_UTIL.get_cgi_env('HTTP_COOKIE');
l_hdr := lh_hdr.FIRST;
WHILE l_hdr IS NOT NULL LOOP
IF lh_hdr(l_hdr) IS NOT NULL THEN
l_in_headers := l_in_headers||CHR(10)||l_hdr||': '||lh_hdr(l_hdr);
END IF;
l_hdr := lh_hdr.NEXT(l_hdr);
END LOOP;
l_resp := '{ "result" : "success" }';
INSERT INTO t_ws_log
( message
, request_header
, request_body
, response_header
, response_body)
VALUES
( NULL
, l_in_headers
, a_request
, 'Content-Type: '||l_ccontent_type
, l_resp
);
OWA_UTIL.STATUS_LINE(nstatus => l_response_status, bclose_header => FALSE);
OWA_UTIL.MIME_HEADER(ccontent_type => l_ccontent_type, bclose_header => FALSE);
OWA_UTIL.HTTP_HEADER_CLOSE();
htp.p(l_resp);
END process_request;
END PK_ORDS_API;
/
Теперь для созданного шаблона нужно добавить обработчик. Добавляем обработчик для HTTP-метода
POST
.Для этого выполняем следующие шаги:
- Вызываем контекстное меню для шаблона, выбираем Add Handler и тут же выбираем HTTP-метод:
- Откроется окно для ввода параметров обработчика. На вкладке SQL Worksheet пишем PL/SQL-код, который будет вызываться для нашего шаблона при получении HTTP-запроса с методом
POST
. В качестве обработчика указываем скрипт, вызывающий процедуруprocess_request
из созданного нами пакета. Обратите внимание, что в скрипте используется bind-переменная:body
— это специальная переменная ORDS, которая будет содержать целиком тело запроса в бинарном виде (так как имеет тип BLOB). Полный список служебных переменных можно посмотреть здесь. В списке есть также переменная:body_text
, которая сразу возвращает тело запроса в виде CLOB. Однако при боевой отладке этой технологии было выяснено, что некоторые запросы, возвращающие текстовые данные, тем не менее не воспринимаются в ORDS как текст. В таком случае переменная:body_text
просто оказывается пустой, а запрос передается в БД в таком «бестелесном» виде. Надежнее использовать исходные данные из переменной:body
, которую нужно преобразовать к типу CLOB самостоятельно в обработчике запроса. Итак, код нашего обработчика выглядит так:
- Нажимаем кнопку Save REST Handler — после этого метод готов к использованию.
Листинг кода обработчика POST-запроса:
DECLARE l_blob BLOB := :body; BEGIN PK_ORDS_API.process_request(a_request => PK_ORDS_API.blob2clob(l_blob)); EXCEPTION WHEN OTHERS THEN OWA_UTIL.STATUS_LINE(nstatus => 500, bclose_header => FALSE); OWA_UTIL.MIME_HEADER(ccontent_type => 'application/json'); htp.p('{ "result" : "error", "message" : "'||SQLERRM||'" }'); END;
При необходимости можно получить PL/SQL-код для создания обработчика. Для этого в контекстном меню ORDS-модуля нужно выбрать REST Definition и далее указать, куда выдать скрипт создания модуля, например в буфер обмена:
-- Generated by Oracle SQL Developer REST Data Services 18.1.0.095.1630
-- Exported REST Definitions from ORDS Schema Version 18.4.0.r3541002
-- Schema: WS_TEST Date: Wed Oct 23 20:19:54 MSK 2019
--
BEGIN
ORDS.ENABLE_SCHEMA(
p_enabled => TRUE,
p_schema => 'WS_TEST',
p_url_mapping_type => 'BASE_PATH',
p_url_mapping_pattern => 'ws_test',
p_auto_rest_auth => TRUE);
ORDS.DEFINE_MODULE(
p_module_name => 'shop',
p_base_path => '/shop/',
p_items_per_page => 25,
p_status => 'PUBLISHED',
p_comments => NULL);
ORDS.DEFINE_TEMPLATE(
p_module_name => 'shop',
p_pattern => 'order',
p_priority => 0,
p_etag_type => 'HASH',
p_etag_query => NULL,
p_comments => NULL);
ORDS.DEFINE_HANDLER(
p_module_name => 'shop',
p_pattern => 'order',
p_method => 'POST',
p_source_type => 'plsql/block',
p_items_per_page => 0,
p_mimes_allowed => '',
p_comments => NULL,
p_source =>
'DECLARE
l_blob BLOB := :body;
BEGIN
PK_ORDS_API.process_request(a_request => PK_ORDS_API.blob2clob(l_blob));
EXCEPTION
WHEN OTHERS THEN
OWA_UTIL.STATUS_LINE(nstatus => 500, bclose_header => FALSE);
OWA_UTIL.MIME_HEADER(ccontent_type => ''application/json'');
htp.p(''{ "result" : "error", "message" : "''||SQLERRM||''" }'');
END;'
);
COMMIT;
END;
Замечание: если выполнить процедуру создания модуля
ORDS.DEFINE_MODULE
еще раз, то автоматически будут удалены все шаблоны этого модуля, при этом никакого предупреждения об этом не будет!Пример вызова
На этом наш веб-сервис готов. Осталось проверить его работу.
Для проверки выполняем запрос:
POST http://****:8888/ords/ws_test/shop/order HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 22
Host: ****:8888
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
{ "message" : "test" }
В ответ получаем:
HTTP/1.1 200 OK
Date: Wed, 23 Oct 2019 16:54:53 GMT
Content-Type: application/json
Transfer-Encoding: chunked
{ "result" : "success" }
Результат выполнения метода — запись с телом запроса и ответа в таблице логов:
Как видим, отправленный HTTP-запрос успешно был обработан процедурой из пакета.
Параметры из запроса
Выше мы уже увидели результат работы простейшего веб-сервиса. Теперь немного усложним задачу, добавив дополнительные параметры в запрос.
Есть два способа передачи параметров:
Через URL. Параметры задаются в URL в стандартном виде:
http://...URI...?p1=val1&p2=val2
Через HEADER. Параметры задаются в заголовке запроса в виде
p1: val1
В ORDS в обработчике запроса параметры определяются в виде bind-переменных.
Добавим в предыдущий пример два параметра:
prm1
— параметр в URI, prm2
— параметр в заголовке запроса.Для обработки этих параметров допишем процедуру
PK_ORDS_API.process_request:
добавим параметры a_prm_uri
и a_prm_hdr
, в которые будут приходить значения наших параметров из запроса.PROCEDURE process_request
( a_request CLOB
, a_prm_uri VARCHAR2 := NULL
, a_prm_hdr VARCHAR2 := NULL
)
AS
TYPE TStringHash IS TABLE OF VARCHAR2(256) INDEX BY VARCHAR2(256);
lh_hdr TStringHash;
l_hdr VARCHAR2(256);
l_resp CLOB;
l_response_status INTEGER := 200;
l_ccontent_type VARCHAR2(64) := 'application/json';
l_in_headers VARCHAR2(32767);
BEGIN
-- Заголовки и прочие параметры входящего запроса
lh_hdr('SERVER_SOFTWARE') := OWA_UTIL.get_cgi_env('SERVER_SOFTWARE');
lh_hdr('SERVER_NAME') := OWA_UTIL.get_cgi_env('SERVER_NAME');
lh_hdr('GATEWAY_INTERFACE') := OWA_UTIL.get_cgi_env('GATEWAY_INTERFACE');
lh_hdr('SERVER_PROTOCOL') := OWA_UTIL.get_cgi_env('SERVER_PROTOCOL');
lh_hdr('SERVER_PORT') := OWA_UTIL.get_cgi_env('SERVER_PORT');
lh_hdr('REQUEST_METHOD') := OWA_UTIL.get_cgi_env('REQUEST_METHOD');
lh_hdr('PATH_INFO') := OWA_UTIL.get_cgi_env('PATH_INFO');
lh_hdr('PATH_TRANSLATED') := OWA_UTIL.get_cgi_env('PATH_TRANSLATED');
lh_hdr('SCRIPT_NAME') := OWA_UTIL.get_cgi_env('SCRIPT_NAME');
lh_hdr('QUERY_STRING') := OWA_UTIL.get_cgi_env('QUERY_STRING');
lh_hdr('REMOTE_HOST') := OWA_UTIL.get_cgi_env('REMOTE_HOST');
lh_hdr('REMOTE_ADDR') := OWA_UTIL.get_cgi_env('REMOTE_ADDR');
lh_hdr('AUTH_TYPE') := OWA_UTIL.get_cgi_env('AUTH_TYPE');
lh_hdr('REMOTE_USER') := OWA_UTIL.get_cgi_env('REMOTE_USER');
lh_hdr('REMOTE_IDENT') := OWA_UTIL.get_cgi_env('REMOTE_IDENT');
lh_hdr('CONTENT-TYPE') := OWA_UTIL.get_cgi_env('CONTENT-TYPE');
lh_hdr('CONTENT-LENGTH') := OWA_UTIL.get_cgi_env('CONTENT-LENGTH');
lh_hdr('HTTP_ACCEPT') := OWA_UTIL.get_cgi_env('HTTP_ACCEPT');
lh_hdr('HTTP_ACCEPT_LANGUAGE') := OWA_UTIL.get_cgi_env('HTTP_ACCEPT_LANGUAGE');
lh_hdr('HTTP_USER_AGENT') := OWA_UTIL.get_cgi_env('HTTP_USER_AGENT');
lh_hdr('HTTP_COOKIE') := OWA_UTIL.get_cgi_env('HTTP_COOKIE');
lh_hdr('a_prm_uri') := a_prm_uri;
lh_hdr('a_prm_hdr') := a_prm_hdr;
l_hdr := lh_hdr.FIRST;
WHILE l_hdr IS NOT NULL LOOP
IF lh_hdr(l_hdr) IS NOT NULL THEN
l_in_headers := l_in_headers||CHR(10)||l_hdr||': '||lh_hdr(l_hdr);
END IF;
l_hdr := lh_hdr.NEXT(l_hdr);
END LOOP;
l_resp := '{ "result" : "success" }';
INSERT INTO t_ws_log
( message
, request_header
, request_body
, response_header
, response_body)
VALUES
( NULL
, l_in_headers
, a_request
, 'Content-Type: '||l_ccontent_type
, l_resp);
OWA_UTIL.STATUS_LINE(nstatus => l_response_status, bclose_header => FALSE);
OWA_UTIL.MIME_HEADER(ccontent_type => l_ccontent_type, bclose_header => FALSE);
OWA_UTIL.HTTP_HEADER_CLOSE();
htp.p(l_resp);
END process_request;
Внутри процедуры просто сделаем запись в лог значений новых параметров.
Добавляем новые параметры в обработчик POST-запроса — в виде bind-переменных
:prm_uri
и :prm_hdr
.Обработчик POST-запроса запроса с новыми параметрами:
DECLARE
l_blob BLOB := :body;
BEGIN
PK_ORDS_API.process_request(a_request => PK_ORDS_API.blob2clob(l_blob), a_prm_uri => :prm_uri, a_prm_hdr => :prm_hdr);
EXCEPTION
WHEN OTHERS THEN
OWA_UTIL.STATUS_LINE(nstatus => 500, bclose_header => FALSE);
OWA_UTIL.MIME_HEADER(ccontent_type => 'application/json');
htp.p('{ "result" : "error", "message" : "'||SQLERRM||'" }');
END;
В обработчике на вкладке Parameters объявляем переменные:
В этой форме первое поле (Name) содержит имя параметра, которое ожидается в запросе, второе поле (Bind Parameter) — имя bind-переменной, которое будет указано в обработчике этого запроса.
Выполним запрос с новыми параметрами:
Результат — параметры из запроса сохранились в логе:
Обратите внимание, что параметры из URI можно также достать из CGI-переменной
QUERY_STRING
, то есть для получения параметров не обязательно заводить bind-переменные — можно парсить их в самой процедуре-обработчике запроса.CGI-переменные
При работе с HTTP в Oracle есть возможность получения значений переменных окружения, отражающих контекст HTTP-запроса. Получить значения переменных можно с помощью процедуры
OWA_UTIL.get_cgi_env
.APEX_LISTENER_VERSION
GATEWAY_INTERFACE
GATEWAY_IVERSION
HTTP_ACCEPT_ENCODING
HTTP_HOST
HTTP_PORT
HTTP_USER_AGENT
PATH_ALIAS
PATH_INFO
PLSQL_GATEWAY
QUERY_STRING
REMOTE_ADDR
REMOTE_USER
REQUEST_CHARSET
REQUEST_IANA_CHARSET
REQUEST_METHOD
REQUEST_PROTOCOL
REQUEST_SCHEME
SCRIPT_NAME
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
WEB_AUTHENT_PREFIX
host
user-agent
CONTENT-LENGTH
CONTENT-TYPE
См. также: HTTP Headers (OWA_UTIL) and ORDS-Specific Bind Variables
Пример 2: Доступ к таблице через ORDS
В этом примере рассмотрим организацию доступа к объекту БД (к таблице) через ORDS.
Как и в предыдущем примере, делаем доступ без авторизации. О том, как делать защищенный доступ к ресурсам, можно посмотреть в документации.
Чтобы сделать объект БД доступным через ORDS, нужно выполнить всего один шаг — команду
ORDS.ENABLE_OBJECT
. После этого к объекту можно обратиться по URI вида:http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>
.Создание тестовой таблицы
Для примера создадим таблицу «Заказы».
Скрипт создания таблицы:
CREATE TABLE T_ORDER
(
id_order NUMBER NOT NULL,
NUM VARCHAR2(32),
buyer_name VARCHAR2(256),
dt_order DATE,
memo VARCHAR2(2000)
);
COMMENT ON TABLE T_ORDER IS 'Заказ';
COMMENT ON COLUMN T_ORDER.id_order IS 'Первичный ключ';
COMMENT ON COLUMN T_ORDER.num IS 'Номер заказа';
COMMENT ON COLUMN T_ORDER.buyer_name IS 'ФИО покупателя';
COMMENT ON COLUMN T_ORDER.dt_order IS 'Дата создания заказа';
COMMENT ON COLUMN T_ORDER.memo IS 'Примечание';
ALTER TABLE T_ORDER ADD CONSTRAINT PK_T_ORDER PRIMARY KEY (ID_ORDER) USING INDEX;
Открытие доступа к таблице через ORDS
- В SQL Developer вызываем контекстное меню для требуемой таблицы, выбираем Enable REST Service....
- В окне настройки доступа ставим галку напротив Enable object, снимаем галку напротив Authorization required, нажимаем «Готово» (или «Следующий», чтобы посмотреть полученный PL/SQL-код):
- После выполнения этих действий таблица
T_ORDER
становится доступна через HTTP, базовый URI для обращения к ресурсу:
http://<server>:<port>/ords/ws_test/t_order
Листинг включения таблицы:
DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN ORDS.ENABLE_OBJECT(p_enabled => TRUE, p_schema => 'WS_TEST', p_object => 'T_ORDER', p_object_type => 'TABLE', p_object_alias => 't_order', p_auto_rest_auth => FALSE); commit; END; /
Создание или изменение записи
Доступ к таблице открыт — проверяем, как можно создавать и редактировать записи в таблице через ORDS.
Для создания записи выполняем запрос
PUT
.В документации ORDS в описании метода
PUT
указан такой шаблон:PUT http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>/<KeyValues>
То есть поле
KeyValues
(ключ записи) должно быть заполнено даже для новой создаваемой записи. В самом запросе должны быть перечислены все поля таблицы (но ключевое поле можно не добавлять).PUT http://<server>:<port>/ords/ws_test/t_order/25 HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json;charset=UTF-8
Content-Length: 157
Host: <server>:<port>
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
{
"num" : "ords-3472634",
"buyer_name" : "Buyer Name",
"dt_order" : "2019-10-25T12:00:00Z",
"memo" : "Тестовый заказ 1"
}
HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: http://<server>:<port>/ords/ws_test/t_order/25
ETag: "..."
Transfer-Encoding: chunked
{
"id_order": 25,
"num": "ords-3472634",
"buyer_name": "Buyer Name",
"dt_order": "2019-10-25T12:00:00Z",
"memo": "Тестовый заказ 1",
"links": [
{
"rel": "self",
"href": "http://<server>:<port>/ords/ws_test/t_order/25"
},
{
"rel": "edit",
"href": "http://<server>:<port>/ords/ws_test/t_order/25"
},
{
"rel": "describedby",
"href": "http://<server>:<port>/ords/ws_test/metadata-catalog/t_order/item"
},
{
"rel": "collection",
"href": "http://<server>:<port>/ords/ws_test/t_order/"
}
]
}
Смотрим содержимое таблицы — появилась наша новая запись:
Для изменения записи вызываем тот же метод PUT. Изменим примечание в нашем заказе:
PUT http://<server>:<port>/ords/ws_test/t_order/25 HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json;charset=UTF-8
Content-Length: 178
Host: <server>:<port>
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
{
"num" : "ords-3472634",
"buyer_name" : "Buyer Name",
"dt_order" : "2019-10-25T12:00:00Z",
"memo" : "Тестовый заказ 1. Изменить примечание"
}
В ответе получим тот же JSON с параметрами измененной записи. В таблице видим, что примечание обновилось:
Получение записей из таблицы
Есть три режима запроса данных из таблицы:
- Постраничный запрос:
GET http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>/?offset=<Offset>&limit=<Limit>
- Запрос по условиям:
GET http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>/?q=<FilterClause>
- Запрос по первичному ключу:
GET http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>/<KeyValues>
Для получения всех записей можно выполнить запрос без указания каких-либо параметров:
GET http://<server>:<port>/ords/ws_test/t_order/
HTTP/1.1 200 OK
Date: Fri, 25 Oct 2019 15:39:58 GMT
Content-Type: application/json
ETag: "..."
Transfer-Encoding: chunked
{
"items": [ {
"id_order": 25,
"num": "ords-3472634",
"buyer_name": "Buyer Name",
"dt_order": "2019-10-25T12:00:00Z",
"memo": "Тестовый заказ 1. Р?зменить примечание",
"links": [ {
"rel": "self",
"href": "http://<server>:<port>/ords/ws_test/t_order/25"
}]
}],
"hasMore": false,
"limit": 25,
"offset": 0,
"count": 1,
"links": [
{
"rel": "self",
"href": "http://<server>:<port>/ords/ws_test/t_order/"
},
{
"rel": "edit",
"href": "http://<server>:<port>/ords/ws_test/t_order/"
},
{
"rel": "describedby",
"href": "http://<server>:<port>/ords/ws_test/metadata-catalog/t_order/"
},
{
"rel": "first",
"href": "http://<server>:<port>/ords/ws_test/t_order/"
}
]
}
Как заставить Oracle добавлять кодировку в заголовок Content-Type — вопрос открытый.
GET http://<server>:<port>/ords/ws_test/t_order/25
HTTP/1.1 200 OK
Date: Fri, 25 Oct 2019 15:44:35 GMT
Content-Type: application/json
ETag: "..."
Transfer-Encoding: chunked
{
"id_order": 25,
"num": "ords-3472634",
"buyer_name": "Buyer Name",
"dt_order": "2019-10-25T12:00:00Z",
"memo": "Тестовый заказ 1. Р?зменить примечание",
"links": [
{
"rel": "self",
"href": "http://<server>:<port>/ords/ws_test/t_order/25"
},
{
"rel": "edit",
"href": "http://<server>:<port>/ords/ws_test/t_order/25"
},
{
"rel": "describedby",
"href": "http://<server>:<port>/ords/ws_test/metadata-catalog/t_order/item"
},
{
"rel": "collection",
"href": "http://<server>:<port>/ords/ws_test/t_order/"
}
]
}
Удаление записи
Для удаления используем HTTP-метод
DELETE
.Запрос:
DELETE http://<server>:<port>/ords/ws_test/t_order/?q={"id_order":25}
Запрос в исходном виде:
DELETE http://<server>:<port>/ords/ws_test/t_order/?q=%7B%22id_order%22%3A25%7D
Ответ:
HTTP/1.1 200 OK
Date=Fri, 25 Oct 2019 16:23:39 GMT
Content-Type=application/json
Transfer-Encoding=chunked
{"rowsDeleted":1}
Выводы
ORDS — достаточно гибкий и универсальный механизм для работы с веб-сервисами, позволяющий реализовать полноценный REST. В плане производительности сильно превосходит Native Oracle WS с его тяжелым внутренним парсингом XML. Для реализации высоконагруженной системы этот подход не подойдет: в этом случае нужен другой стек технологий — отдельный сервер приложений, проксирование запросов, использование кластерных БД и так далее. Однако для реализации систем с относительно небольшим количеством HTTP-запросов (до 10–20 в секунду) ORDS — оптимальный подход как в производительности, так и в гибкости. ORDS уступает Native Oracle WS только в плане генерации спецификации веб-сервиса: последний выдает уже полностью готовую спецификацию (WSDL), которую «как есть» можно отдавать потребителям сервиса. В ORDS также есть возможность генерации описания, но для рассмотренного в этой статье подхода с полностью универсальным сервисом (когда есть общая процедура обработки для всех сервисов) автоматическая генерация спецификации становится невозможной. Oracle сгенерирует только спецификацию верхнего уровня, а детали (модели данных) придется описывать вручную.
Альтернативные подходы
Java Servlet
Этот вариант создания веб-сервисов по способу настройки похож на Native Oracle WS: тоже требует использования встроенного HTTP-сервера Oracle, а также настройки ACL (как, впрочем, и все другие способы). Но, в отличие от Native Oracle WS, этот вариант работает с чистым HTTP. Обработчики запросов в этом случае пишутся на Java и смотрят только на тип HTTP-запроса (
PUT
, GET
, POST
и так далее, хотя можно сделать и один обработчик на все типы), а логика обработки полностью остается на усмотрение разработчика. Можно передавать тело запроса «как есть» в логику БД, и там разбирать его и обрабатывать, можно часть логики оставить на стороне Java-обработчика, а из БД вызывать уже нужную процедуру в зависимости от пришедших в запросе данных.Данный подход к реализации веб-сервисов вполне универсальный и к тому же не требует установки никаких дополнительных компонентов. У нас не получилось его применить только из-за жестких требований к сервису: нужен был веб-сервис, не требующий аутентификации. При реализации данного подхода аутентификация обязательна, и это требование не получилось обойти.
Более подробно про этот способ можно узнать в документации Oracle.
Database Access Descriptor (PL/SQL Servlet)
Этот вариант полностью аналогичен предыдущему, только обработчиком запроса выступает хранимая процедура PL/SQL.
Пример URL — указывается имя пакета, имя процедуры, параметры (GET-запрос):
GET http://<server>:<port>/servlet_plsql/pi_test.test_serv?p_path=ppp
В случае POST-запроса имена параметров нужно прописывать прямо в теле запроса через знак «=», что довольно неудобно, так как тип содержимого запроса (ContentType) в этом случае может быть только текстовым. Передать xml- или json-структуру можно только в таком виде:
p_proc_param_name=<xml_data>…</xml_data>
Такой вариант веб-сервиса применим только в случаях, когда имеем дело с совсем простыми запросами — вызовами процедуры с простыми типами данных. Передать какую-либо сложную многоуровневую структуру в этом варианте не получится.
Этот подход подробно описан на сайте ORACLE-BASE.
Заключение
Создание веб-сервиса в Oracle — задача довольно простая, не требующая написания какого-то сверхсложного кода. При этом разработчики Oracle получают в свой арсенал довольно мощный механизм, позволяющий интегрировать разнородные системы или части систем через HTTP.
В данной статье мы рассмотрели четыре подхода к созданию веб-сервисов.
Native Oracle WS — устаревшая технология, которая тем не менее имеет свои плюсы: автоматически генерируемый WSDL, автоматический парсинг XML, отсутствие необходимости установки дополнительного ПО. Главный недостаток — небольшая производительность и ограничение поддерживаемых типов данных.
ORDS — на мой взгляд, предпочтительный способ создания веб-сервисов. Достаточно гибкий и универсальный. Из неудобств этого способа можно выделить только то, что в стандартную поставку Oracle он не входит, то есть требует отдельной установки.
Java Servlet — вполне универсальный способ, не требующий установки дополнительного ПО. Однако все нужно делать полностью вручную, поскольку нет возможности автогенерации сервисов.
PL/SQL Servlet — наименее удачный подход. Из плюсов можно выделить то, что в этом варианте мы получаем возможность вызова хранимых процедур через HTTP без необходимости установки дополнительного ПО, а также без написания дополнительного кода на других языках: весь код пишется только на PL/SQL.
Всем спасибо за внимание! Надеюсь материал статьи окажется полезным тем, кто так или иначе связан с продуктами Oracle и озадачен проблемами внутрисистемных и межсистемных интеграций.
Using Native Oracle XML DB Web Services
Native Oracle XML DB Web Services in Oracle 11g Release 1
ORDS
Описание: Installation, Configuration, and Development Guide
Install Oracle REST Data Services 3.0.X in under 5 minutes
CGI-переменные: HTTP Headers (OWA_UTIL) and ORDS-Specific Bind Variables
Аутентификация в ORDS: Oracle REST Data Services (ORDS) : Authentication
ORDS и Open API (Swagger): Oracle REST Data Services (ORDS) : Open API 2.0 (Swagger) Support
ORDS, возвращающий BLOB: ORDS web services returning BLOBs
Java Servlet
Writing Oracle XML DB HTTP Servlets in Java
Database Access Descriptor (PL/SQL Servlet)
DBMS_EPG — The Embedded PL/SQL Gateway in Oracle 10g Database Release 2
imanushin
Крутое решение!
А Вы оценивали, примерно, насколько изменились расходы на приложение? Сейчас у вас больше стал нагружаться сервер с базой данных, а значит, условно, потребуется болше денег на лицензии Оракла (они же, по сути, на ядра выдаются, а нагрузка возросла, как кажется с первого взгляда).
С другой стороны, не требуется дополнительный сервер для http сервисов, что может помочь экономии.
awswaltz Автор
Спасибо за комментарий!
Именно расходы на приложение не оценивали, замеряли только пропускную способность веб-сервиса до (Native XML DB Web Service) и после (ORDS). С ORDS пропускная способность оказалась на порядок выше. Вообще Native XML DB Web Service часто давал большую нагрузку на CPU; тут, конечно многое зависит от внутренней логики обработки запроса, но в случае Native WS ещё получали дополнительную нагрузку на парсинг XML. После перехода на ORDS ситуация улучшилась за счёт более оптимального парсера — часть запросов была переведена на json-формат, но даже те, что остались на xml, стали работать быстрее. БД стала нагружаться больше только за счёт того, что количество запросов увеличилось (за счёт роста числа заказов, появление большего числа интеграций и т.д.), т.е. в таких же условиях Native WS вообще переставал справляться, а переход на ORDS в целом решил проблему.
imanushin
А, разумно, вы же еще сэкономили в реальности на трафике запросов — теперь они не уходят из Оракла.
Класс, спасибо!
maxzh83
Экономия и oracle в разных углах ринга. Кто хочет экономить, не берет oracle в принципе.
Siemargl
Ценник конечно бывает просто заоблачным.
Но иногда нет ничего дороже простоя, даже кратковременного.
Хотя экономия на http сервере, это на спичках.