Oracle中的Web服务



网页服务广泛用于一个系统的组件之间或不同系统之间的集成。 Web服务之所以流行,是因为它们的多功能性以及易于实施和调试的优点。多功能性与使用Internet和HTTP协议进行的数据传输相关。 Web服务使在运行于不同操作系统和平台上以不同语言编写的组件之间的集成相对容易。 Web服务实现的简单性是通过许多IDE中的内置工具和组件实现的,这些工具和组件使您可以快速开发Web服务(提供方)和必要的代码以在客户端(客户端)调用该服务。通过使用人类可读的数据交换格式-XML和JSON简化了调试服务。除了,有许多实用程序可用于调试和测试服务,包括负载。

在本文中,我们将考虑几种直接从Oracle DBMS创建Web服务的方法,即不使用第三方工具的方法。

内容

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)从主服务器收集数据,并返回销售结果(文件交换)。

进步并没有停滞不前,新设备出现在商店中。来自该设备的数据应定期发送到商店的主服务器(周期-每隔几分钟),集成必须通过Web服务进行,消息必须具有某种格式,不需要身份验证。我们查看了数据交换合同,发现使用的Web服务技术将不允许与此类设备进行建筑物集成。于是寻找解决方案开始了...

结果,考虑了几种实现所需Web服务的选项,直到编写一个单独的组件才能为HTTP数据库打开Oracle数据库的窗口:一方面,它将提供Web服务,另一方面,它将通过JDBC与数据库交互。困难在于,新组件首先必须在数千家商店中安装,其次,将出现另一个必须随附的链接。因此,使用Oracle内置工具实施Web服务仍然是优先选择。

通过搜索,我们发现了本文将要考虑的四种方法:

  1. 本机Oracle XML DB Web服务
  2. Oracle REST数据服务
  3. Java Servlet
  4. 数据库访问描述符(PL / SQL servlet)

前两个选项将被更详细地考虑。最初,在我们的系统中使用了本机Oracle XML DB Web服务,也就是说,它是继承的。ORDS替代了这种过时的技术(尽管我仍然必须努力工作,并在数千家商店中安装ORDS)。

在寻找本机Oracle WS的替代方案时,我们与ORDS一起考虑了另外两种方法(Java Servlet和PL / SQL Servlet),但我们并未在项目中使用它。因此,我们将不对这些方法进行详细的考虑,而只限于简要参考。

本文将提供一些实施Web服务的实际示例,并附有有助于创建有效服务的说明。

本机Oracle XML DB Web服务


能力


允许您使用WS SOAP(版本1.1)通过HTTP组织对Oracle数据库的访问:

  • SQL- XQuery- SOAP.
  • ( ).



  • - : , HTTP-.
  • - (WSDL), , -.
  • -. , — , — .
  • XML-. , , XML, - — Oracle . , WS- Oracle — , - .



  • WSDL , , - . — WSDL, WSDL , .
  • ( 50 ). , — , . , - — , , .

值得注意的是,对Web服务的所有请求都需要进行身份验证(来自文档:基本身份验证:Oracle XML DB支持基本身份验证,在此,客户端在授权标头中以明文形式发送用户名和密码)。在Oracle中,您可以使用Oracle XML DB Protocol Server配置来配置通过HTTP对服务器资源的匿名访问,但是实际上,它仅适用于GET请求,而POST请求则需要身份验证。由于本机Oracle WS仅通过POST请求工作,因此无法为该技术配置匿名访问。

客制化


要使用本机Oracle WS,您需要:

  1. 在Oracle中配置内置的HTTP服务器。
  2. 在Oracle数据库内部配置访问权限(打开HTTP端口)。
  3. 创建一个servlet。
  4. 配置ACL(访问列表)。

配置Oracle XML DB HTTP服务器


默认情况下,HTTP服务器应该已经可以运行,但是在某些情况下,可能需要其他侦听器配置-将以下块添加到DESCRIPTION_LIST:

(DESCRIPTION=
  (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=8080))(Presentation=HTTP)(Session=RAW)
)

之后,您需要重新启动监听器。

配置HTTP访问


  1. 检查当前端口的HTTP。

    SELECT dbms_xdb.gethttpport() AS http_port FROM dual;

    值“ 0”表示禁用HTTP访问。
  2. 端口设置。

    BEGIN
        --    web-services
        dbms_xdb.setHttpPort(8080);
        COMMIT;
    END;
    /

为Web服务创建Servlet


为了使Web服务正常工作,数据库配置中需要servlet注册。

创建servlet的脚本
--   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;
/

配置访问列表


要通过HTTP访问Oracle,您需要在DBMS配置中添加规则。这是使用内置的DBMS实用程序完成的。

要配置ACL,您需要:

  • 脚本编辑数据库配置(如下);
  • 完成配置的数据库模式。

即,必须已经为其配置了ACL的数据库模式已创建。在下面的示例中,将在需要创建新数据库模式的那些地方引用此部分,对于它们,您将需要配置ACL。

在SYS方案中,我们执行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;
/

应当立即指出,不仅使用本机Oracle WS,而且使用本文讨论的所有其他创建Web服务的方法时,都需要ACL配置。

使用批处理过程的Web服务示例


我们会需要:

  • 数据库模式,用于处理Web服务请求的对象将位于该数据库模式中。
  • 记录表。
  • 包含过程/功能的软件包。
  • 任何允许您发送Web请求的工具。在这种情况下,使用了SOAP UI应用程序,但是您可以选择其他任何工具,直到命令行为止。

特征:

  • 过程参数必须具有简单或对象类型。否则,该过程将不会被视为Web服务方法,也不会包含在服务方法列表中。
  • 要实现输入或输出数据的复杂结构,您需要使用对象类型(不能使用批处理类型)。

我们创建必要的数据库对象:

  1. 方案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添加新方案。上一节中的脚本。
  2. 在新方案中,创建用于记录的表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 '/ ';
  3. 打包过程很简单:

    
    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;
    /

最简单的Web服务已创建并可以使用。

要访问该服务,您需要使用以下格式的URL:

http://[server]:[port]/[servlet_name]/[DB_SCHEMA]/[WS_OBJ]?wsdl

其中:

[服务器] -Oracle数据库服务器的域名或IP地址
[端口] -在“配置通过HTTP的访问”部分中指定的用于通过HTTP访问的端口
[servlet_name] -在“为Web创建Servlet的部分”中指定的servlet的名称服务”
[DB_SCHEMA] -数据库模式的名称(大写)
[WS_OBJ] -服务的名称(大写),与数据库对象的名称相等,在我们的情况下-包的名称

请注意,URL很重要!

链接示例:

http://my.server:8080/orawsv/WS_SOAP_TEST/PK_NATIVE_WS_TEST?wsdl

如果在浏览器中单击此链接,将基于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 UI中创建一个新的SOAP项目。作为URL,从上面的示例中指定地址。

打开后,Request 1我们看到请求模板已经由指定的WSDL自动生成。

立即为请求添加基本授权-登录名和密码与连接到数据库时的登录名​​和密码相同。



请求和响应示例。请注意,一行已添加到自动生成的请求中<pk:O_RESULT-VARCHAR2-OUT/>事实是,Oracle 12c中存在一个错误:OUT变量未添加到用于本机Oracle WS的WSDL中。



调用结果在日志表中可见。



发现


如果此Web服务上没有繁重的负载(几秒钟内一个请求),则可以将本机Oracle XML DB Web服务技术用作工业解决方案。当消费者对Web服务没有严格的要求(其属性,数据结构,处理逻辑)并且没有严格的预定合同时,此选项适用。如果您需要根据预定义的WSDL(如在我们的新设备中)创建Web服务,则本机Oracle WS技术将不适合。

Oracle REST数据服务


从版本11.1开始,Oracle以称为Oracle REST数据服务(ORDS)的单独模块的形式引入了完整的RESTful支持。

ORDS是一个Java应用程序,允许您使用SQL和PL / SQL为Oracle数据库创建RESTful API。它是使用Oracle HTTP Server和mod_plsql的替代方法。本质上,ORDS是外部世界和Oracle数据库之间的HTTP接口。该接口允许您关闭对任何数据库对象(表或PL / SQL过程)的传入HTTP请求。

所有需要做的就是为所需的数据库安装,配置和运行ORDS。创建REST服务的进一步过程归结为用PL / SQL编写代码(或者,如果代码太懒而无法编写,甚至可以通过单击IDE)。

安装ORDS不需要其他许可证。

要求:

  • Java JDK 8或更高版本;
  • Oracle 11.1或更高版本(也支持Oracle 11 XE发行版2)。

支持几种ORDS部署选项:

  • 独立模式;
  • 在应用程序服务器(Oracle WebLogic Server,Apache Tomcat)上。

能力


ORDS允许您:

  • 以RESTful风格组织对资源的访问。
  • 以“远程过程调用”样式构建交互(RPC样式交互)。

简而言之,借助ORDS,您可以通过HTTP打开对某些数据库对象(表,过程,函数,包)的访问。

在第一种情况下,我们以RESTful架构风格来理解资源。每个资源均由唯一的URI定义,对资源的操作(CRUD-创建,读取,修改,删除)由HTTP请求中的操作(PUT,POST,GET,DELETE)决定。例如,如果资源是employee表中的一个条目,那么此条目将通过以下形式的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"}如上所述,在使用本机Oracle WS时使用相同的方法,但是在REST服务的情况下,SOAP协议没有任何限制。

优点

  • 一种灵活的机制,可让您实施任何复杂的Web服务。
  • 实现的简便性:要将PL / SQL过程转换为Web服务,只需执行一些典型命令。使用SQL Developer,创建REST API只需单击最少的代码,就可以单击鼠标。
  • 无需身份验证即可实现Web服务的能力。

减去

  • ORDS不包含在标准Oracle软件包中-必须为每台服务器分别安装ORDS(如上所述,不需要其他许可证)。如果您有数千个数据库服务器,这可能是一个问题。

客制化


可以以两种模式启动ORDS:通过应用程序服务器或独立模式。

本文仅讨论在独立模式下运行的选项。

为了使ORDS正常工作,您需要:

  1. 在安装之前配置设置。
  2. 完成ODRS安装。
  3. 启动ORDS。
  4. 配置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

有关参数的描述,请参见文档

“独立模式的参数”:
参数描述
db.hostnameOracle数据库服务器的名称或IP地址
db.portDB端口(例如1521)
db.servicename服务名称数据库
db.usernamePL / SQL网关的用户名。默认值= APEX_PUBLIC_USER,但是该示例不打算使用APEX,因此可以将此参数保留为空白
db.password用户密码(也不要填写)
migrate.apex.restRESTful APEX服务过渡到ORDS。如果不使用APEX,则设置为false。
rest.services.apex.add配置ORDS以在APEX中使用
rest.services.ords.add设置ORDS的数据库架构(ORDS_PUBLIC_USER和ORDS_METADATA)。值必须为真
schema.tablespace.defaultORDS_METADATA架构的默认表空间
schema.tablespace.tempORDS_METADATA架构的临时表空间
standalone.http.portORDS将在其上运行的端口。此端口将在URI中用于访问WS。
user.tablespace.defaultORDS_PUBLIC_USER架构的默认表空间
user.tablespace.tempORDS_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

必要的设置已完成,您可以继续进行安装。

安装


我们在OS中执行命令:

java -jar ords.war

默认情况下,配置文件来自params旁边的目录ords.war您可以使用参数明确指定此文件的路径--parameterFile

java -jar ords.war --parameterFile /path/to/params/myown_params.properties

在安装对话框中,执行以下操作:

  1. 我们指出了用于保存配置的目录的路径(在示例中,这表明conf -结果是,在文件所在的同一目录中ords.war,将创建一个目录conf,其中将创建具有ORDS配置的文件)。
  2. 在出现输入用于ORDS_PUBLIC_USER的数据库密码命令之后,我们输入该方案的密码ORDS_PUBLIC_USER在该用户下,ORDS将连接到数据库。
  3. 在出现提示输入1(如果您想使用PL / SQL Gateway)或2(跳过此步骤)之后,我们回答“ 2”,因为我们将不使用APEX,也不需要从迁移mod_plsql
  4. 在输入1(如果希望以独立模式启动)或输入2(退出)[1]的指示后,我们回答“ 1”。
  5. 在出现输入1(如果使用HTTP)或2(如果使用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的实用程序方案将出现在数据库中。

看到 还:在5分钟内安装Oracle REST Data Services3.0.X

发射


通过以下命令以独立模式启动:

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已启动并正在运行。Web服务地址为http:// <主机名>:/ ords / ...,其中<主机名>是运行ORDS的计算机的名称(它不必与数据库服务器的名称匹配,即ORDS可以在另一台主机上运行),<port>-ORDS配置中指定的端口。

如果需要,您可以创建一个脚本以在操作系统启动时自动运行ORDS。

存取设定


最后一步是配置ACL。这些步骤使用本机Oracle WS 相同

创建数据库模式后,必须完成此步骤,因此以下示例将分别指示何时需要执行此配置。到目前为止,我们可以假定所有初步步骤均已完成,并继续进行示例。

示例1:POST请求的处理程序


例如,考虑RPC样式的交互实现选项,即,我们将创建一个Web服务方法,该方法的处理程序将是一个批处理过程。

实际上,正是在此版本中,在我们的分销网络项目中实施了与ORDS的合作,结果达到了预期。事实证明,这是一种快速通用的方法,可以为不同的业务流程创建新的Web服务方法-从使用设备到站点的API。

如上所述,ORDS支持也包含在SQL Developer(Oracle开发的数据库IDE)中。在SQL Developer中,可以直接从菜单项或上下文菜单中创建Web服务。

在本文中,使用SQL Developer(版本18)制作了示例,但是还包括由于IDE中的操作而在数据库中执行的PL / SQL代码。

实验所依据的数据库版本:

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访问。

为了这:

  1. 在SQL Developer中,为创建连接WS_TEST
  2. 在连接列表中,调用新连接的上下文菜单。选择REST服务→启用REST服务...


  3. 在对话框中,选中“ 启用模式 ”旁边的框如果您希望在URI中显示其他文本而不是模式名称,也可以在此处更改“ Schema alias ”参数ws_test):


  4. 通过单击“下一步”,我们将看到最终将在数据库中执行的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;
/

创建模块和模板


接下来,您需要为资源创建一个模块和模板。

模块是一个结构单元,可让您对几个逻辑相关的资源模板进行分组。

模板是一种特定的Web服务,为资源提供一组特定的方法。



资源模块:一个用于组相关的资源的模板的组织单元。

资源模板:一种单独的RESTful服务,能够为某些URI(通用资源标识符)集的请求提供服务。 URI的集合由资源模板的URI模式定义。


例如,对于在线商店,可以调用一个模块shop -在此模块中,将合并所有商店的API。模板可以是特定的资源,例如订单(模板order),产品目录(模板item),付款(模板payment)等。

要创建模块和模板,需要执行以下步骤:

  1. 在对象树中,打开“ REST数据服务”部分,在“ 模块”部分中调用上下文菜单,选择“ 新建模块...”


  2. 在模块创建对话框中,指定模块名称(shop),前缀URI(还指示shop -在下面的示例中,我们将立即看到Web服务地址模板是什么),在Publish前面放置一个daw ,单击“ Next>”


  3. 在下一个对话框中,指定模板的名称,例如order,然后再次单击“ Next>”

    在最后一个对话框中,我们看到所有输入的模块和模板参数。在“ SQL”选项卡上,您可以单击“ 完成”来查看将在数据库中执行的PL / SQL代码



要创建模块shop ,您可以添加更多的模式-也在对象树,上下文菜单中,然后选择“添加模板...”

代码清单以创建模块和模板
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

为此,请执行以下步骤:

  1. 我们调用模板的上下文菜单,选择添加处理程序,然后选择HTTP方法:

  2. . SQL Worksheet PL/SQL-, HTTP- POST. , process_request . , bind- :body — ORDS, ( BLOB). . :body_text, CLOB. , , , ORDS . :body_text , «» . :body, CLOB . , :


  3. 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定义”,然后指定将模块创建脚本输出到例如剪贴板的位置:


生成的代码创建模块
-- 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再次执行模块创建过程,则该模块的所有模板将被自动删除,并且不会对此发出警告!

通话范例


这是我们的Web服务准备就绪的地方。它仍然需要检查其工作。

要检查,我们执行以下请求:

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请求已被包中的过程成功处理。

请求中的参数


上面,我们已经看到了简单的Web服务的结果。现在,通过向请求中添加其他参数,使任务复杂一些。

有两种方法来传递参数:

通过URL。参数以标准形式在URL中设置:

http://...URI...?p1=val1&p2=val2

通过HEADER。参数在请求标头中设置为p1: val1

在请求处理程序的ORDS中,参数定义为绑定变量。

我们在前面的示例中添加了两个参数:prm1-URI中的prm2参数-请求标头中的参数。

为了处理这些参数,PK_ORDS_API.process_request:我们添加了过程,添加了参数a_prm_uria_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请求处理程序中-以绑定变量: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;

在处理程序中的`` 参数''选项卡上,声明变量:


在这种形式下,第一个字段(Name)包含请求中期望的参数名称,第二个字段(Bind Parameter)-将在此请求的处理程序中指定的绑定变量的名称。

让我们使用新参数执行请求:



结果-请求中的参数保存在日志中:


请注意,也可以从CGI变量中检索URI中的参数QUERY_STRING,即,获取参数不需要启动绑定变量-您可以在处理程序过程本身中解析它们请求。

CGI变量


在Oracle中使用HTTP时,可以获取反映HTTP请求上下文的环境变量的值。您可以使用该过程获取变量的值OWA_UTIL.get_cgi_env

PL / SQL中可用的CGI变量列表
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标头(OWA_UTIL)和ORDS特定的绑定变量

示例2:通过ORDS访问表


在此示例中,我们考虑通过ORDS访问数据库对象(对表)的组织方式。

与前面的示例一样,我们未经授权即可进行访问。请参阅文档中的如何使对资源的安全访问

要使数据库对象可通过ORDS访问,只需执行一个步骤-命令ORDS.ENABLE_OBJECT之后,可以通过以下形式的URI访问对象:

http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>

创建测试图案


例如,我们将创建表“ Orders”。

表创建脚本:

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打开表访问


  1. 在SQL Developer调用所需表的上下文菜单中,选择“启用REST服务...”



  2. 在访问设置窗口中,选中“ 启用对象”复选框,取消选中“ 需要授权”复选框,单击“完成”(或单击“下一步”以查看收到的PL / SQL代码):



  3. 执行完这些步骤之后,该表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创建和编辑表中的记录。

要创建记录,我们执行request 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。在表中,我们看到该注释已更新:



从表中检索记录


从表中查询数据的方式有以下三种:

  1. 页面请求:

    GET http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>/?offset=<Offset>&limit=<Limit>
  2. 要求条件:

     GET http://<HOST>:<PORT>/ords/<SchemaAlias>/<ObjectAlias>/?q=<FilterClause>
  3. 主键请求:

    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是用于Web服务的一种相当灵活且通用的机制,它使您可以实现成熟的REST。在性能方面,它具有强大的内部XML解析能力,远远优于Native Oracle WS。要实现高负载的系统,此方法不适合:在这种情况下,需要使用不同的技术堆栈-单独的应用程序服务器,代理请求,使用集群数据库等。但是,对于实施HTTP请求数量相对较少(每秒高达10-20个)的系统而言,ORDS是性能和灵活性方面的最佳方法。 ORDS仅在生成Web服务规范方面劣于本机Oracle WS:后者提供了完整的规范(WSDL),可以按原样将其提供给服务的使用者。 ORDS也有能力生成描述,但是对于本文考虑的具有完全通用服务的方法(当所有服务都有通用的处理过程时),无法自动生成规范。Oracle仅会生成一个顶层规范,而零件(数据模型)则必须手动进行描述。

替代方法


Java Servlet


使用配置方法创建Web服务的此选项类似于本机Oracle WS:它还需要使用Oracle的内置HTTP服务器以及ACL设置(实际上,是所有其他方法)。但是,与本机Oracle WS不同,此选项可用于纯HTTP。在这种情况下,请求处理程序是用Java编写,并仅在HTTP请求类型看(PUTGETPOST,等等,但它可以做到和所有类型的一个处理器),以及处理逻辑完全保留在开发人员的自由裁量权。您可以将请求主体“按原样”传输到数据库逻辑,然后对其进行分解和处理,可以将逻辑的一部分留在Java处理程序一侧,然后根据请求中的数据从数据库中调用所需的过程。

这种实现Web服务的方法非常普遍,并且不需要安装任何其他组件。我们仅由于对服务的严格要求而无法应用它:我们需要一个不需要身份验证的Web服务。在实施此方法时,需要进行身份验证,并且不能回避此要求。

有关此方法的更多详细信息,请参见Oracle文档

数据库访问描述符(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>

此版本的Web服务仅在我们处理非常简单的请求(具有简单数据类型的过程调用)的情况下适用。在此选项中传输任何复杂的多层结构将不起作用。在ORACLE-BASE网站上

详细描述这种方法

结论


在Oracle中创建Web服务是一项相当简单的任务,不需要编写任何类型的超复杂代码。同时,Oracle开发人员将相当强大的机制引入他们的工具库,该机制使您可以通过HTTP集成异构系统或部分系统。

在本文中,我们研究了创建Web服务的四种方法。

本机Oracle WS是一种过时的技术,但是它仍然具有优势:自动生成WSDL,自动解析XML,无需安装其他软件。主要缺点是性能低下以及受支持的数据类型的限制。

订单-在我看来,创建Web服务的首选方式。足够灵活和通用。在此方法的不便之处,我们只能区分它没有包含在标准Oracle软件包中,也就是说,它需要单独安装。

Java Servlet是一种完全通用的方法,不需要安装其他软件。但是,所有操作都需要完全手动完成,因为不可能自动生成服务。

PL / SQL Servlet是最不成功的方法。这些优点之间的区别是,在此选项下,我们可以通过HTTP调用存储过程,而无需安装其他软件,也无需编写其他语言的代码:所有代码仅以PL / SQL编写。

谢谢大家的关注!我希望本文对那些以某种方式与Oracle产品相关联并且对系统内和系统间集成的问题感到困惑的人有用。


All Articles