#新手

0 关注者 · 74 帖子

初学者标签汇集了面向 InterSystems 数据平台初学者的文章和问题

文章 Hao Ma · 五月 31, 2021 6m read

HealthConnect中创建HTTP服务端

这里我说说怎么在HealthConnect上开发HTTP服务。

作为消息引擎,HealthConnect会需要从一个接口接收HTTP请求发送到另一个接口,中间做消息转换,路由等等,目的的接口可能是HTTP,或者SOAP,REST等等。这里只介绍HTTP服务的内容,也就是最简单的两种实现:

第一种:实现客户定制的HTTP服务业务服务组件(Business Servie)

创建Business Service类,继承EnsLib.HTTP.Service, 如下面的示例:

Class SEDemo.IO.HTTP.ServiceExample1 Extends EnsLib.HTTP.Service
{
    Parameter ADAPTER;
    Method OnProcessInput(pInput As %Stream.Object, Output pOutput As %Stream.Object) As %Status
    {
        //创建Ensemble消息发送给其他组件
        set pRequest=##class(Ens.StreamContainer).%New()
        Set tSC=pRequest.StreamSet(pInput)
        set tSC= ..SendRequestAsync("Dummy1",pRequest,.pResponse)
        //创建返回Stream,发送给调用方
        set pOutput=##class(%Stream.GlobalCharacter).%New()
        do pOutput.Write("yes, I recieved request")
        Quit tSC
    }
}

详细说明:

使用CSP机制接收请求,不要用HTTP的Inbound Adpater,这样能得到

如果您学习过在Ensemble上开发SOAP接口,一定对代码里的***"Parameter ADAPTER;"***不陌生,它的作用是确定不要使用父类里的Adapter。

EnsLib.HTTP.Service有两个工种模式,一个是使用内置适配器是Ens.HTTP.InboundAdapter,还有一个是使用CSP机制,从CSP Gateway接收请求。下面的图中黄色的箭头是用适配器接收消息,这时业务服务可以定义工种的URL,端口,SSL等等;下面红色的箭头是所谓的”CSP请求“,也就是HTTP请求经过Web服务器,CSP Gateway, 到Web Application, 再被EnsLib.HTTP.Service收到。

使用CSP机制有更好的安全性和性能,所以在HealthConnect中任何HTTP的服务端接口我们都推荐CSP机制,包括HTTP接口,SOAP接口, REST接口。这些接口的开发都不要使用对应的InboundAdapter。

有关CSP Gateway的原理,还可以参见在线文档或者我的另一技术文章:Web Gateway介绍

注意的是:当不使用Adapter时,Production页面的组件配置中很多项目会消失,这些是Adapter的属性,比如允许的IP, 端口,包括编码等等。因为不用Adapter,您也不用定义IP,端口号;只有编码,可以在BS的代码里实现。实现的操作可以参考Adapter的设置: https://docs.intersystems.com/healthconnectlatest/csp/docbook/Doc.View.cls?KEY=EHTTP_settings_inbound

OnProcessInput()的入参pInput

业务组件收到的HTTP请求由pInput传入,真正的类型是%CSP.GlobalBinaryStream,它的父类是已经不推荐使用的流类型%GlobalBinaryStream。用%Stream.Object作为pInput的对象类型是合适的,这是一个新版本的Stream对象的超类,可以是任意类型的流。上面代码里业务服务组件发出的Ensemble消息的类型是StreamContainer,如果你看看消息跟踪的类型,你会发现里面流的类型是”GB",也就是一个%GlobalBinaryStream类型的流。

pInput对象的属性Stream里存放的是HTTP Body,而HTTP头放在Attributes属性里,如下图所示:

如何获得请求里的消息头

以下是用pInput.GetAttributeList()得到的Attributes的内容:

 <![CDATA[*CSPApplication/csp/healthshare/demo/CharEncoding1EnsConfigName SEDemo.IO.HTTP.ServiceExample1
        HTTPVersion1.1
        HttpRequestGET
            IParamsParamsRawParamsTranslationTableRAWAURL:/csp/healthshare/demo/SEDemo.IO.HTTP.ServiceExample1.clsaccept*/*"accept-encodinggzip, deflatecache-control
        no-cacheconnectionkeep-alivecontent-length0content-type£cookieCSPSESSIONID-SP-80-UP-csp-healthshare-=0000010100002ROlNxwgxwUQuPHKQogHGNWfADLfF2xiPwce2s; CacheBrowserId=ui$4lIZ_rJsPD_xUTPG$Rw; CSPWSERVERID=B33PnJAehost172.16.58.200mykeyimess7postman-token&83e942ce-ae73-4d98-b7ab-1e2803c95794%user-agentPostmanRuntime/7.18.0]]>

而获取消息头的某些值, 可以用 pInput.Attributes(http_header_name)得到, 比如下面这些:

    set MethodTypeName=pInput.Attributes("HttpRequest")
	set HTTPVersion = pInput.Attributes("HTTPVersion")
    set ContentType = pInput.Attributes("content-type")
    set ContentLength = pInput.Attributes("content-length")
    set URL = pInput.Attributes("URL")
    //获得GET请求,得到URL中的form参数(注意,POST请求的表单数据在消息体里面)
    set pList = Attributes("Params",form_variable_name,n), 
    //得到自定义的消息头值,比如上面的消息里面有个自定义的头字段“mykey"
    set Mykey = pInput.Attributes("mykey")

更多的细节,请参考在线文档: Using the HTTP Inbound Adap: About the Attributes Array

中文编码转码

如果请求的流里面有中文字符,需要在代码里执行转码,比如下面这个例子,把pInput的流,转码成UTF8,放到Ens.StringRequest里传输。

    set pRequest=##class(Ens.StringRequest).%New()
    set pRequest.StringValue=$ZCVT(pInput.Read(),"I","UTF8")
    set sc= ..SendRequestAsync("Dummy1",pRequest,.pResponse)

有关组件名称和访问的URL

请求的URL有两个通常的选择:

  1. Production里面的业务组件名字和类名称一样

比如上面代码的类是"SEDemo.IO.HTTP.ServiceExample1",如果production的业务服务也是同样的名字,那么调用它的URL就是

    http://hostip:port/csp/healthshare/demo/SEDemo.IO.HTTP.ServiceExample1.cls

其中"/csp/healthshare/demo/"是Web Application的名字。

  • 如果上一条不成立。比如写了一个类,用于多个业务服务组件,那么需要组件的名字可以用自己的名字,调用的URL要包含?CfgItem=xxx表示寻找不同的业务组件服务。举例说,还是上面的类,用于添加了两个业务服务组件"httpservice2"和"httpservice3",访问它们的URL就是:
    http://hostip:port/csp/healthshare/demo/SEDemo.IO.HTTP.ServiceExample1.cls?CfgItem=httpservice2
    http://hostip:port/csp/healthshare/demo/SEDemo.IO.HTTP.ServiceExample1.cls?CfgItem=httpservice3

还有这么个操作,就是单独创建一个Web Application, 配置DispatchClass,来接入一个Web服务。我觉得完全没有必要,而且新版本中Web Application的菜单里只剩下了为REST分配分派类的选择,因此这里就不说这个了。

第二种: 使用EnsLib.HTTP.GenericService预置业务服务组件

使用预置的,开发好的组件意味着不用写代码,配置一下就可以使用。相应的,灵活性上就差了,大概只适合简单的透传,转发,路由。如果要修改数据包内容,http头内容,编码转换等等,需要要在其他的组件上,比如production中消息经过的BP, BO中实现。

EnsLib.HTTP.GenericService的父类是EnsLib.HTTP.Service,因此它也是可以使用Adapter机制或者CSP标准机制。如前面所述,我们需要用CSP机制,调用的URL必须包含**?CfgItem=组件配置名**,比如我在Production里面配置了组件”GenericService1", 那么我访问的请求就应该是:

    http://localhost/csp/healthshare/demo/EnsLib.HTTP.GenericService.cls?CfgItem=GenericHTTPService1

还有,因为我们不修改代码,所以组件配置项中会有Adapter的配置,比如端口号,SSL配置,直接忽略它们,不用理睬。 。如果您看到配置项上有”字符集",显示的是UTF-8,可是没起作用,请不要奇怪,这个是Adapter的配置,您用CSP机制时它是不起作用的。

EnsLib.HTTP.GenericService向其他业务组件发出的Ensemble消息的类型是EnsLib.HTTP.GenericMessage。以下是一个POST请求被业务服务组件发送给BO的消息样例。

        <!-- type: EnsLib.HTTP.GenericMessage  id: 531 -->
        <HTTPMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="http://www.w3.org/2001/XMLSchema">
        <Stream>{
        âserial_idâ: âC20114062017025â,
        âtake_timeâ: â2019-01-02 15:04:05â,
        âpos_timeâ: â2019-01-02 15:04:05â,
        âtypeâ: â1â,
        âwarnâ: â0â,
        âfile_idâ: â9cd586eef356c71f64b82a190b469e69â,
        âfile_nameâ: âA1012014400596520160714190338.hlyâ,
        âfile_pathâ: â/service/data/TE9100Yâ,
        âbegin_timeâ: â2016-07-04 20:17:21â,
        âend_timeâ: â2016-07-04 20:18:21â,
        âlengthâ: â60â,
        âsizeâ: â9650â,
        âresultâ: â窦æ§å¿ç, æ¬æ¬¡å¿çµçæµæªè§å¼å¸¸â
        }</Stream>
        <Type>GB</Type>
        <HTTPHeaders>
        <HTTPHeadersItem HTTPHeadersKey="CSPApplication">/csp/healthshare/demo/</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="CharEncoding" xsi:nil="true"></HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="EnsConfigName">GenericHTTPService1</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="HTTPVersion">1.1</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="HttpRequest">POST</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="IParams">1</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="IParams_1">CfgItem=GenericHTTPService1</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="RawParams">CfgItem=GenericHTTPService1</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="TranslationTable">RAW</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="URL">/csp/healthshare/demo/EnsLib.HTTP.GenericService.cls</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="accept">*/*</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="accept-encoding">gzip, deflate, br</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="authorization">Basic X3N5c3RlbTpTWVM=</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="cache-control">no-cache</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="connection">keep-alive</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="content-length">532</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="content-type">text/plain</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="cookie">CSPSESSIONID-SP-52773-UP-csp-healthshare-=0000000100006fSAbXykPFTd0NFNEoaUH94y07Phmq0V92aeDg; CSPBrowserId=F4nsRY6yDGVipvQ84sDN3w; CSPWSERVERID=I33xYkI3</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="host">172.16.58.200:52773</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="postman-token">0e0bf4a7-868b-4e69-bbac-bfd89909fe99</HTTPHeadersItem>
        <HTTPHeadersItem HTTPHeadersKey="user-agent">PostmanRuntime/7.28.0</HTTPHeadersItem>
        </HTTPHeaders>
        </HTTPMessage>

使用这个组件要注意的几点:

  • 上面的数据包里有中文乱码,我是故意这么做的,就是提醒您:强调一下选用这个组件时,如果要修改数据,可以在其他的组件处理。如果我做一个简单的HTTP转发,我可以选用EnsLib.HTTP.GenericService和EnsLib.HTTP.GenericOperation这一对预置组件,这样BS,BO就免开发了。而如果中间要做中文编码的转换,我会插入一个BP, 专门处理EnsLib.HTTP.GenericMessage里的中文转码。

  • 组件的"启动标准请求"配置项:要使用CSP Gateway请求机制时应该勾选。

  • "持久消息已发送INPROC"配置项( PersistInProcData) 这个选项是专用于以InProc模式同步调用时是否要持久化Ensemble消息的选项,默认是保存。如果设置成off,那么HealthConnect即不保留消息头,也不保留消息体,在消息查看器里无法查看,也不能重传。

  • “保持标准请求分区”配置项 也是用于InProc模式调用,是否保留到BO定义的外部系统的连接。和上面的"持久消息已发送INPROC"配置项一样,很少被用到,保留默认的选中就可以。

  • "没有字符集转换”配置项 控制CSP Gateway是否对%reponse消息里的文本按content-type的类型转码,默认是不勾选,也就是保留CSP Gateway的转码功能。

  • ”One-Way"配置项 如果客户端不期待响应消息,那么选中这个选项后,EnsLib.HTTP.GenericService收到请求转发的同时会送一个Http状态码202,意思是”服务器已接受请求,但尚未处理“. 默认是不选中这个配置。

0
1 417
文章 Hao Ma · 四月 29, 2021 3m read

经常被问到有关IRIS如何支持SSL,HTTPS的问题,有必要写个东西介绍一下。

##HTTPS的原理 简单的说,https实现两个目的:一是访问网站加密,2是确认被访问的网站是真的。

首先,被访问的网站要申请一个证书,这个证书必须是权威机构发放的,比如google, VeriSign等等,所有的浏览器里有预装了这些组织的公钥(Public Key),因此能确认你提供的证书真是这些组织给出的,而这个证书可以证明你的网站的身份。注意证书证明的是提供服务的组织和服务的真实性,和用什么设备没关系,也就是说,IRIS不管证书的事儿。

接下去,被访问的服务器可以生成公钥和私钥,和客户端交换key,生成整个世界只有两者知道的security code,用来两者之间数据的交换。详细的过程和消息交互可以在网上找到很多很好的文章和视频,比如这个: How does HTTPS work? What's a CA? What's a self-signed Certificate?

如果是测试环境或者使用者可以控制的内部网络,self-signed证书非常常用。self-signed证书就是不去花钱找人认证,而是告诉客户端,我这个证书是自己认证的,你知道我这台机器试内网的一个机器,不用权威机构证明我服务器的身份,咱们交换一下钥匙把通信加密了吧。操作系统,各种Web服务器都提供这样的假证书,可以用于测试。浏览器访问这样的网站时会提醒用户这个网站不安全,客户需要确认继续访问。

IRIS的https访问

如果要访问的是IRIS上的http服务或者页面,需要做的是在连接IRIS的Web服务器配置SSL/TLS。有关IRIS和IRIS WebGateway的介绍,请查看这个系列前面的文章

不需要在IRIS或者IRIS Gateway做任何配置。在IRIS文档里有各种有关SSL/TLS的内容,除非你要开发一个TCP层的使用SSL/TLS的应用或者IRIS作为客户端访问其他HTTPS的服务,你根本不用阅读。

下面简单介绍配置Apache Web服务器简单实现IRIS管理页面的HTTPS访问的步骤。

1.apache Web服务器安装SSL.

如果你的Apache没有安装过SSL组件,运行下面命令安装

yum -y install mod_ssl 

命令执行结束安装完成后,在/etc/httpd/modules目录会添加了mod_ssl.so,并且在/etc/httpd/conf.d 目录下会出现一个ssl.conf文件。

如果是Windows, 您需要下载使用Windows的Apache服务器,比如从这个页面:Apach2.4.46。 按照说明,您需要将软件解压缩到一个目录,比如c:/Apache24,然后执行 "httpd -k install"安装。

并且, 你要确保httpd.conf文件中下面两行没有被注释

LoadModule ssl_module modules/mod_ssl.so
Include conf/extra/httpd-ssl.conf

访问https://WebServerIP,你会被浏览器提醒这不是个可信任的网站,是不是还要继续访问,确认后会看到Apache的测试页,访问是成功的。

2.到IRIS的WebGateway的连接。

我一般放在一个单独的配置文件里,在linux下是在./conf.d/isc.conf, 在Windows系统是在./extra/httpd-isc.conf。这个配置文件是要被include在httpd.conf里面。配置https并不需要修改这个配置文件。下面是在Windows下的httpd-isc.conf的配置示意。

  LoadModule csp_module_sa C:/InterSystems/WebGateway/CSPa24.dll 
    <Location "/csp/bin/Systems/"> 
    SetHandler csp-handler-sa 
    </Location> 
    <Location "/csp/bin/RunTime/"> 
    SetHandler csp-handler-sa 
    </Location> 
    CSPFileTypes * 
    Alias /csp/ c:/InterSystems/WebGateway/csp/ 
    <Directory "c:/InterSystems/WebGateway/csp"> 
        AllowOverride None 
        Options MultiViews FollowSymLinks ExecCGI 
        Require all granted
        <FilesMatch "\.(log|ini|pid|exe)$"> 
        Require all denied 
        </FilesMatch>
    </Directory> 

这时您应该可以测试到IRIS管理页面的HTTPS访问了。

3. 获得证书并添加到Web服务器。

这步是可选的。面向公众服务的Web服务通常会购物证书, 而内部服务个个客户的网络中会有相关的CA的处理方式,相应的如何修改Apache服务器的配置请自行查看文档。

五一节快乐

0
0 424
问题 Michael Lei · 四月 21, 2021

大家好,

我正在创建WS做服务器用,但是当我要求WSDL时,提示错误因为找不到类。
我添加了以下说明:

set ^SYS("Security","CSP","AllowClass","MiProyecto.MiClaseWS","%SOAP.WebServiceInfo")=1 
set ^SYS("Security","CSP","AllowClass","MiProyecto.MiClaseWS","%SOAP.WebServiceInvoke")=1

我已经在WS安全配置中创建了一个入口

在“应用程序角色”选项卡中,我配置了%All权限

(图像中的命名空间“Samples”是出于安全原因)

如果我以以下方式调用服务,则http:// localhost:57772 / myproject / ws / MyProject.MiClassWS.cls? WSDL提示以下错误:

Not Found
The requested URL /miproyecto/ws/MiProyecto.MiClaseWS.cls was not found on this server.

但是,另一方面,如果我执行以下命令,它可以正常工作: https:// localhost:57772 / csp / sample / MiProyecto.MiClaseWS.cls?wsdl

1
0 286
文章 Hao Ma · 四月 18, 2021 5m read

IRIS相比Caché在部署上的一个进步是支持docker。即便不是云部署, 使用docker也带来非常多的便利。 尤其是在开发测试环节,由于docker的使用更便捷,除非要模拟客户的环境或者做规定的性能测试,我在测试中基本已经不再使用本机的实例或者虚机。IRIS的联机文档有详细的IRIS docker安装使用指导,本文只是一个简单的,快速上手的在测试环境安装IRIS docker的简单步骤,尤其适合初学者。

注意Windows上docker可能会遇到这样那样的问题,因此通常还是推荐在Linux或者Mac OS上使用。正式的生产环境的IRIS docker container也是不支持Windows系统的。

Referrence

##1. 在操作系统上安装Docker环境

Docker官方文档的安装步骤非常清晰,我按照上面的步骤在MAC和CentOS上安装docker从来没有出现过问题。恰恰是这个文档中没有的Redhat上的安装,过程中曾碰到过几个问题,在网上搜索了答案,也不算困难。

简单的复述一下步骤:root用户的最简单安装,没有权限问题, 没有docker网络修改等等:

  1. 安装yum-util, 用于添加yum源,如果您的系统中已经有了yum-utils包这步可以省略

     sudo yum install -y yum-utils
    
  2. 添加docker的Repo到yum源并确认

     sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
     sudo yum repolist
    
  3. 安装3个docker组件

     sudo yum install docker-ce docker-ce-cli containerd.io
    
  4. 启动Docker

     sudo systemctl start docker
    
  5. 确认安装成功。'docker run'命令会去本地的库里找'hello-world',没有找到就去网上下一个"images",然后创建一个docker容器给你使用。

     sudo docker run hello-world
    

安装结束您想要到https://hub.docker.com/注册一个账户,用来下载上传docker image。 下载命令使用'docker pull', 比如

docker pull nginx

2. 下载IRIS Docker image

在线文档Container Images Available from InterSystems中介绍了https://containers.intersystems.com网站上可以下载的IRIS images的列表,其中community版本不需要license,其他的版本需要从InterSystems处获得IRIS docker版的专用license.

下面是登录并下载iris docker image的记录。从网页登录https://containers.intersystems.com,您会得到docker的登录密码"使用的密码"jaRWSBJjcUcNprCKTuMX10PYHNq2IYPrAQoYdp6Siokb"。

[root@centos7 ~]# docker login -u="hma" -p="jaRWSBJjcUcNprCKTuMX10PYHNq2IYPrAQoYdp6Siokb" containers.intersystems.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
[root@centos7 ~]# docker pull containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0
2020.3.0.304.0: Pulling from intersystems/iris-ml
5c939e3a4d10: Pull complete
c63719cdbe7a: Pull complete
19a861ea6baf: Pull complete
651c9d2d6c4f: Pull complete
d21839215a64: Pull complete
7995f836674b: Pull complete
841ee3aaa7aa: Pull complete
739c318c2223: Pull complete
d76886412dda: Pull complete
Digest: sha256:4c62690f4d0391d801d3ac157dc4abbf47ab3d8f6b46144288e0234e68f8f10e
Status: Downloaded newer image for containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0
containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0
[root@centos7 ~]#

另外,您也可以从InterSystems的技术支持网站下载iris docker的压缩包"iris*.tar.gz",然后使用"docker load"安装, 比如:

CNMBPHMA:~ hma$ docker load -i iris-2020.4.0.524.0-docker.tar.gz

3. 创建并运行IRIS Container

###简单的办法

如果是只是简单的测试,可以直接运行,但如果不是community版本的docker, 您还需要把license拷贝到container内部。 比如下面的步骤

CNMBPHMA:~ hma$ docker run -d -i -p 52773:52773 -p 51773:51773 --name iris20204 intersystems/irishealth:2020.2.0.211.0
CNMBPHMA:~ hma$ docker cp iris.key iris20204:/usr/irissys/mgr/iris.key

需要注意的是,不同版本的超级端口可能不同,有些是1972,有些是51773。为了省事,您也许可以使用"-P"把所有container内部的端口都映射出来。

除了要拷贝license到container内部,在做测试的时候,您可能还要经常的使用"docker cp"把各种测试文件拷入container。而且,当查看和修改container内部文件的使用,还需要使用下面的命令进入container内部:

CNMBPHMA:~ hma$ docker exec -it iris20204 /bin/sh

因此,如果是您需要一个经常使用的iris container环境,我还是建议您使用下面的方法。

IRIS Container使用外部存储

简单的说,下面的命令

  • 使用"--volume"参数为container创建外部存储,将host上的"/root/data/dur"文件夹和container内部的"/dur"文件夹链接起来。(/root/data/dur文件夹要在host上手工创建,这只是一个示意,您可以使用任何可用的目录)

  • ISC_DATA_DIRECTORY环境设置会在创建iris container时在/dur文件夹下创建一个子目录”/dur/irisepy",所有IRIS运行的日志,客户配置,mgr目录下的用户数据都会保存在这个目录。因为"/dur"是外部存储,即使docker container被删除数据也不会丢失。这对container的重新创建或者iris升级都带来很大的方便。在线文档上对如果覆盖或者升级带有外部存储的iris container有详细的介绍。

  • “key"参数定义了在IRIS container创建时会把"/dur/license"目录下的iris.key拷贝到iris安装目录的mgr下并激活。也就是说,在运行命令前, 您需要在host上在"/root/data/dur"目录下创建"license"子目录并将iris.key拷贝进去。

      docker run --name irisepy --init -d\
           -p 9091:1972 -p 9092:52773\
           -v /root/data/dur:/dur\ 
           --env ISC_DATA_DIRECTORY=/dur/irisepy \
           containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0 \
           --key /dur/license/iris.key	
    

希望您喜欢使用docker

0
1 834
文章 Louis Lu · 四月 15, 2021 15m read

IRIS 中支持的四种方式:

SQL、Objects、REST 和 GraphQL  

卡济米尔·马列维奇,《运动员》(1932) 

>

> “你当然无法理解! 习惯了坐马车旅行的人怎么可能理解乘坐火车或者飞机旅行的人的感受和印象?”
>

> >

> 卡济米尔·马列维奇 (1916)
>

## 引言

我们已经讨论过为什么在主题领域建模使用对象类型优于使用 SQL。 当时得出的结论和总结的事实如今依然适用。 那么,我们为什么要退后到对象和类型之前的时代,讨论将对象的操作拖回到使用global的技术? 我们又为什么要鼓励面条式代码?难道是为了用它难以跟踪的错误考验开发者的技能熟练度? 

目前有几种观点支持通过基于 SQL/REST/GraphQL 的 API 传输数据,而不是将其表示为类型/对象:

  • 这些技术经过深入研究,相当易于部署。
  • 知名度非常高,已在便捷的开源软件中广泛实现。
  • 您通常别无选择,只能使用这些技术,尤其是在网络和数据库中。
  • 最重要的是,API 仍然使用对象,因为它们提供了在代码中实现 API 的最适途径。

在讨论实现 API 之前,我们先来看一下底层的抽象层。 下图显示了数据在永久存储位置与处理并向应用程序用户呈现的位置之间的移动方式。

0
0 680
文章 Nicky Zhu · 二月 4, 2021 7m read

上一篇: IRIS中的权限管理

在上一篇文章中,我们介绍了IRIS中的权限控制体系。在本文中我们将以一个常见的实施需求为例介绍如何使用IRIS的权限配置出一个只能使用SQL的用户。

需求的分解

和所有用户需求一样,当用户提出一个需求时,除其语义显式的含义之外,还需分析其是否具有没有明确说明的含义。 对于一个只能使用SQL的用户这样一个需求,即应当结合平台的特征分解成为功能需求: 具有一个合法,可通过用户名和密码使用IRIS的用户 该用户的数据库权限

  • 确认项:可以使用SQL访问所有数据库还是某几个特定的数据库? 该用户的SQL权限
  • 确认项:对于特定的数据库,是否可以执行所有的DDL?
  • 确认项:对于特定的数据库,是否对每一张表都可以执行Select、Update等所有的DML 该用户的程序权限
  • 确认项:用户是否可以通过Portal登录并管理IRIS?

如上所示,一个只能使用SQL的用户,这样一个看似简单的需求,如果需要与用户获得一致的理解并付诸实施,则需要将其分解,对于没有在用户需求中明确约定的部分,应作为待确认项与用户确认。 另外,需要注意的是,IRIS作为一个数据平台,除了提供底层的数据库之外,也提供了Portal等Web应用程序便于使用Sharding、HA、Interoperability等平台功能,通过平台对外提供的Webservice、REST等web接口也受Web应用程序控制。因此,当需要设计权限体系时,不但需要考虑用户使用的数据库相关的权限,也需要考虑是否需要控制Web应用程序的权限。

我们假设需要提供一个严格意义上的只能对某个库的某个Schema下的所有表具有只读权限的用户,即: 具有一个合法,可通过用户名和密码使用IRIS的用户 该用户的数据库权限

  • 该用户只能使用DemoSpace命名空间下的数据库 该用户的SQL权限
  • 该用户不能执行任何DDL
  • 该用户只能对DemoSchema下的所有表执行Select语句 该用户的程序权限
  • 该用户不能登录Portal,不能执行Portal中提供的任何管理功能

对数据库的配置

在IRIS中创建数据库时,默认的行为是引用%DB_%DEFAULT这个资源,并引用%DB_%DEFAULT角色(注意,平台中有一个叫做%DB_%DEFAULT的资源,同时还有一个叫做%DB_%DEFAULT的角色。%DB_%DEFAULT角色通过%DB_%DEFAULT资源获得默认的数据库访问权限)。如果直接使用%DB_%DEFAULT角色或%DB_%DEFAULT资源,都有可能影响到之前以默认配置创建的数据库,因此,在需要细粒度控制访问权限时,往往需要自定义资源和角色实现。

创建资源%DB_DemoDB_Res {#3.1}

资源可在创建数据库的同时创建,也可以在使用默认资源创建数据库之后,再给数据库指派其他资源。本例中,我们在创建命名空间DEMOSPACE的同时创建数据库DEMODB并创建资源。 image

创建角色DemoDB_Read_Role {#3.2}

image 在创建角色之后,即可为其分配资源。根据资源类型的不同,对资源的操作可以有读、写和使用三种权限。对于数据库引用的资源,是读权限和写权限。在本例中,我们需要创建的是只读用户,因此,资源权限分配读权限即可,不用赋写权限。 image

创建用户DemoUser并分配角色

通过Portal创建用户之后,即可给用户分配角色。在本例中,需要给这个用户分配之前创建的DemoDB_Read_Role角色。 image 在经过上述配置之后,用户DemoUser即已拥有对命名空间DEMOSPACE中数据库的访问权限。 image 此时,用户对数据库拥有读权限,但并没有对表执行查询或建表的权限,如果尝试create table,则会得到如下的权限错误信息: image 为继续实验,我们通过Portal执行这SQL语句先创建DemoSchema.Persons这张表 image

为角色DemoDB_Read_Role分配SQL权限

由于用户具有的权限不足,无法执行SQL操作,因此我们需要对该用户的角色赋予对应的权限(或直接给用户赋权,但平台用户较多时,考虑到用户管理的成本,并不推荐这样做)。可以采用如下手段进行SQL的授权

通过Portal授权

在Portal的用户管理和角色管理功能中,均可指定要授予的SQL权限。 SQL特权栏用于对DDL进行数据库级的授权,例如对DEMOSPACE命名空间下的数据库分配建表、修改表、建视图等DDL操作。在本例中,用户不具有这些权限,因此不对该用户对应的角色授予这些权限。 image 在SQL表,SQL视图和SQL过程栏中,则是分别对表、视图和存储过程授予查询、执行等权限。在本例中,用户需要对DemoSchema这个Schema下的表拥有select查询权限,即可通过对SQL表授权进行 image 授权后该角色的SQL表权限如下 image 此时通过SQL工具已可执行查询 image 使用Portal授权时是针对单个的表、视图或存储过程进行。在上例中,我们单独对表DemoSchema.Persons进行了授权,如果我们再建立一张DemoSchema.Employee表,当前的角色和用户并不能自动获得读取其数据的权限。

通过SQL授权

超级管理员或拥有SQL授权权限的用户可以通过SQL的GRANT语句对数据库对象(包括库,函数,表,视图和存储过程等)进行授权,SQL GRANT语句的语法和使用详见GRANT,此处不再赘述。 在上面的例子中我们建立了表DemoSchema.Persons,建表的同时建立的Schema DemoSchema。假如现在我们希望对授权进行简化,使角色DemoDB_Read_Role可以直接获得Schema下所有表的读权限,则可以用如下的SQL:

GRANT SELECT ON SCHEMA DemoSchema to DemoDB_Read_Role

执行成功后再查看DemoDB_Read_Role的SQL权限,会发现: image 即这个角色已经拥有了Schema级的授权,因此,对整个Schema下的所有表都拥有权限。之后在Schema中如果建立了新的表,则这个角色会自动拥有这些表的读权限。

限制用户登录和使用Portal

上例配置的用户可以用于登录IRIS的Portal,但由于没有任何系统功能的权限,不能执行操作。 image IRIS的Web应用程序在创建时默认并不需要额外的资源去访问,这意味着所有合法用户都能登入这个Web应用,但由于支撑应用的后台程序和数据是受到资源的保护的,能登入的用户不一定具有运行程序、访问数据的权限,正如我们建立的用户DemoUser可以登录Portal,但没有功能菜单可用。 如果需要进一步限制用户的行为,禁止其登录,则还需要对应用权限进行控制。 如我们在上一篇文章:IRIS中的权限管理 中所述,Portal是平台提供的Web应用程序,是通过Web应用的权限控制可访问性,因此,需要修改应用的资源要求。 通过菜单: 系统管理 > 安全 > 应用程序 > Web应用程序 可以访问当前系统提供的Web应用列表,其中的/csp/sys即为系统管理门户Portal image 其中必要的资源一栏即为该应用的资源需求,默认为空,即访问该应用不需要特定资源,只要是合法用户即可。我们可以为该应用指定所需资源,例如%Development,即只有具有%Development资源的角色及其对应的用户才能够访问该程序。保存设置后,再尝试以用户DemoUser登录Portal,结果是 image 除非我们为DemoUser引用的角色DemoDB_Read_Role分配资源%Development,该用户都不能登录。 当然,超级管理员由于拥有所有权限,不受这个设置的影响。

总结

通过上述实验,我们创建了一个用户,只能使用SQL连入并查询指定Schema下的表。希望通过这个实验,大家能够掌握IRIS权限管理的基本元素:

  • 用户,角色,权限,资源和许可构成的权限控制体系
  • 数据库、SQL和应用程序都是权限管理的对象

大家在实际项目中可以根据最终用户的实际需要,灵活应用这些概念,构建满足需求的权限配置。

上一篇: IRIS中的权限管理

3
1 448
文章 Qiao Peng · 三月 5, 2021 3m read

大家好!

InterSystems IRIS 有一个名为 Interoperability(互操作性)的菜单。

它提供了轻松创建系统集成(适配器、记录映射、BPM、数据转换等)的机制,因此可以轻松连接不同的系统。

数据中继过程中可以包括各种操作,例如:为了连接没有正常连接的系统,可以根据目标系统的规范来接收(或发送)数据。 此外,在发送数据之前,可以从其他系统获取和添加信息。 还可以从数据库(IRIS 等)获取和更新信息。

在本系列文章中,我们将讨论以下主题,同时查看 示例代码 以帮助您了解工作原理以及在系统中集成互操作性时需要进行哪种开发。

首先,我介绍一下我们将在本系列文章中使用的案例研究。

某公司运营着一个购物网站,他们正在更改产品信息的显示顺序以配合季节变化。
但是,有些商品无论季节如何都能卖得很好,而有些商品在意料之外的时间卖出,这不符合当前的显示顺序更改规则,
因此,我们研究了按照当天的温度而不是季节来更改显示顺序的可能性。 调查购买产品时的温度变得非常必要。
由于可以使用外部 Web API 来查询天气信息,因此我们计划收集购买时的天气信息,并将其记录在后面的审核数据库中。

案例非常简单,但您需要使用“外部 Web API”来收集信息,并且需要将获得的信息和购买信息结合起来记录在数据库中。

具体说明将在相关文章中讨论(不包括网站的创建)。 请移步观看!

至于我们这次使用的“外部 Web API”,我们使用的是 OpenWeather当前天气数据.

(如果您想要尝试一下,您需要注册一个帐户并获得 API ID).

以下是一个 REST 客户端发出的 GET 请求的结果(我们将以在 Interoperability 中实现的机制来运行此流程)。

HTTP 响应的 JSON 如下所示:

{
    "coord": {
        "lon": 135.5022,
        "lat": 34.6937
    },
    "weather": [
        {
            "id": 803,
            "main": "Clouds",
            "description": "broken clouds",
            "icon": "04d"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 17.05,
        "feels_like": 13.33,
        "temp_min": 16,
        "temp_max": 18,
        "pressure": 1017,
        "humidity": 55
    },
    "visibility": 10000,
    "wind": {
        "speed": 4.63,
        "deg": 70
    },
    "clouds": {
        "all": 75
    },
    "dt": 1611635756,
    "sys": {
        "type": 1,
        "id": 8032,
        "country": "JP",
        "sunrise": 1611612020,
        "sunset": 1611649221
    },
    "timezone": 32400,
    "id": 1853909,
    "name": "Osaka",
    "cod": 200
}

下一篇文章中,我们将讨论如何使用 Interoperability 菜单进行系统集成。

0
0 113
文章 Nicky Zhu · 二月 3, 2021 6m read

下一篇: 案例: 建立只能使用SQL的用户

IRIS通过认证(Authentication)与授权(Authorization)两项机制控制外部用户对系统及应用、数据资源的可访问性。因此。如需要进行权限控制,则需要通过配置认证和授权进行。

IRIS中的认证 {#2}

认证可以验证任何试图连接到InterSystems IRIS®的用户的身份。一旦通过认证,用户就与IRIS建立了通信,从而可以使用其数据和工具。有许多不同的方法可以验证用户的身份;每种方法都称为验证机制。IRIS 通常被配置为只使用其中一种方式。 支持的认证方式

  • 实例认证:通过用户名/密码对登录平台,即密码认证
  • LDAP:通过第三方LDAP服务器(如Windows Active Directory )完成认证
  • 操作系统认证:建立操作系统用户-平台用户映射,使用操作系统用户登录平台
  • Kerberos:使用Kerberos协议进行认证
  • 代理认证:使用自定义的代码实现认证过程

系统服务与认证 {#2.1}

在安装时,IRIS会启动一系列系统级的服务用与控制与外部用户或系统的交互,这些服务都绑定了默认的认证机制 image 图中红框标出的即为系统安装后会自动启用并需经认证才可使用的系统服务,认证手段可配置。 例如,如果变更%Service_Console的身份验证方法,取消密码方法,用户就不能通过输入用户名密码登入Terminal。 通过Portal的菜单 系统管理 > 安全 > 服务 可访问该设置。

账户控制参数 {#2.2}

通过系统管理 > 安全 > 系统安全 > 系统范围的安全参数中的选项可对于用户名/密码认证手段的行为进行更多的约束。 image

  • 非活动限制 - 指定用户账户不活跃的最大天数,它被定义为成功登录之间的时间。当达到此限制时,该帐户将被禁用。值为0(0)表示对登录之间的天数没有限制。[对于最低安全级别的安装,默认为0,对于正常和锁定的安装,默认为90]。
  • 无效登录限制 (0-64) - 指定连续不成功的登录尝试的最大次数。在达到此限制后,要么禁用账户,要么对每次尝试进行递增的时间延迟;行动取决于如果达到登录限制字段则禁用账户的值。值为0(零)表示对无效登录的次数没有限制。[默认为5]
  • 如果达到登录限制,则禁用账户 - 如果选中,则指定达到无效登录次数(在前一字段中指定)将导致用户账户被禁用。
  • 密码有效期天数(0-99999) - 指定密码过期的频率以及用户更改密码的频率(天数)。当初始设置时,指定密码过期的天数。0(0)表示密码永远不会过期。不会影响已设置了下次登录时更改密码字段的用户。[默认为0]

需要特别注意的是,密码有效性、过期和禁用账户等设置会影响IRIS实例的所有账户,包括IRIS超级管理员账户。如触发了控制策略,则在更新这些帐户的信息之前,可能无法进行各种操作,这可能导致意外的结果。如超级管理员账户被锁定,则需要通过紧急模式启动实例再进行修改。

对于系统可用的认证手段的配置和其他可用的安全配置,请参见Security Administration Guide

IRIS中的授权 {#3}

授权模型 {#3.1}

InterSystems公司的授权模式采用基于角色的访问控制。

  • Users – 用户
  • Roles – 角色
  • Privileges – 权限
  • Resources – 资源 | Permissions – 许可

在这种模式下,用户拥有与分配给各自用户身份的角色相关的权限。 image

  • 一个角色是一个命名的特权集合
  • 一个用户可以拥有一个以上的角色
  • 权限分配给角色,角色分配给用户

其中,Roles就是权限的集合,而权限提供对资源的特定类型的访问的许可。

  • 可控资源: 数据库,服务,应用(包括Web应用 )和其他
  • 可选用的许可: Read, Write or Use,其中执行代码需要数据库的读权限

资源的定义 {#3.2}

资源是一项相对抽象的概念,用来指代IRIS中的数据库,服务,应用等可被访问的对象。例如,对于数据库,在建立时默认采用%DB_%DEFAULT指代,也可自定义资源(数据库资源必须以%DB_开头): image

对于Web应用,默认不需要通过资源控制,即所有可登录用户都可访问(但该用户进程不一定能访问到数据,还需参照是否具有对数据库的访问权限)。如通过分配资源进行控制,则登录用户还需具有资源才能访问这个Web应用: image

因此,一项权限实际上是指对某个资源的一些特定操作的集合。 例如,对于数据库UserDB具有读写操作许可的权限A,对于Web应用/csp/sys具有使用操作许可的权限B。如果我们将这两项权限都赋给角色RoleA,那么这个角色就同时拥有A权限和B权限,从而能够访问数据库UserDB和访问Web应用/csp/sys。

SQL授权 {#3.3}

除了对数据库进行授权外,IRIS作为一个数据平台,需要对外提供数据访问。因此,IRIS也提供了SQL授权对用户可执行的SQL进行细粒度的权限控制。 SQL的授权可以分配给角色或用户。但通常在企业环境中,用户数量会很多,仍然需要对SQL用户进行分组,根据分组规划角色,通过角色进行授权的控制,才能有效降低维护授权所需的工作量。 SQL的授权针对SQL类型,可分为库、表级授权。 对于Create table、drop view、truncate table这一类的DDL,使用库级授权,即用户可在特定的库中执行建表、删除视图等经过授权的操作。如下: image

对于Select、update等DML,则使用表级授权,使用户能够通过DML访问特定的表中的数据。如下: image

除通过Portal操作之外,对于SQL的授权,还可使用IRIS SQL中的额GRANT语句,例如:

GRANT * ON Schema Test TO TestRole

这个SQL即可以将当前操作数据库下Schema Test中的所有表的所有权限都赋给TestRole这个角色。 关于GRANT语句的用法,可参见GRANT指令

以上即为IRIS中进行权限控制所需掌握的概念和内容,在后续文章中,我们会结合实例向大家介绍其使用。

下一篇: 案例: 建立只能使用SQL的用户

推荐阅读

Security Administration Guide - https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS

0
0 651
文章 Hao Ma · 一月 30, 2021 13m read

检查Apache工作状态

确认Apache正常工作, apache的版本已经安装路径。

[root@centos7 ~]# httpd -v
Server version: Apache/2.4.6 (CentOS)
Server built:   Apr 24 2019 13:45:48
[root@centos7 ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
  Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
  Active: active (running) since Mon 2020-06-15 16:46:36 CST; 5min ago
    Docs: man:httpd(8)
          man:apachectl(8)
Main PID: 6506 (httpd)
  Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec"
    Tasks: 272
  Memory: 31.3M
  CGroup: /system.slice/httpd.service
          ├─6506 /usr/sbin/httpd -DFOREGROUND
          ├─6592 /usr/sbin/httpd -DFOREGROUND
          ├─6607 /usr/sbin/httpd -DFOREGROUND
          ├─6608 /usr/sbin/httpd -DFOREGROUND
          ├─6609 /usr/sbin/httpd -DFOREGROUND
          ├─6610 /usr/sbin/httpd -DFOREGROUND
          ├─6611 /usr/sbin/httpd -DFOREGROUND
          ├─6612 /usr/sbin/httpd -DFOREGROUND
          ├─6613 /usr/sbin/httpd -DFOREGROUND
          ├─6622 /usr/sbin/httpd -DFOREGROUND
          ├─6623 /usr/sbin/httpd -DFOREGROUND
          └─6633 /usr/sbin/httpd -DFOREGROUND

Jun 15 16:46:36 centos7 systemd[1]: Starting The Apache HTTP Server...
Jun 15 16:46:36 centos7 systemd[1]: Started The Apache HTTP Server.
[root@centos7 ~]#

确认httpd.conf的位置。 在CentOS7中此位置为/etc/httpd/conf, 其他linux系统可能有其他位置, 如果不确认,可以使用 find命令寻找.

[root@centos7 ~]# ll /etc/httpd/conf
total 56
-rw-r--r-- 1 root root   890 Jun 26  2019 healthshare.conf
-rw-r--r-- 1 root root     0 Jun 26  2019 healthshare.conf_save
-rw-r--r-- 1 root root 11786 Jun 30  2019 httpd.conf
-rw-r--r-- 1 root root 11753 Jun 26  2019 httpd.conf.bak
-rw-r--r-- 1 root root 11746 Jun 30  2019 httpd.conf2
-rw-r--r-- 1 root root 13077 Apr 24  2019 magic
[root@centos7 ~]#

从Caché所在服务器用浏览器检查Apache测试页面可以访问。如果在Apache本地服务器访问, 网址为127.0.0.1(如果远端无法访问,请首先检查防火墙,后面步骤中有介绍)

picture testing123

关闭SELinux配置

查询确认SELinux状态为disabled

[root@centos7 ~]# getenforce
Disabled

如果非disabled状态,需要修改配置文件实现, 下图为修改后的文件内容,修改后重启电脑生效。

[root@centos7 ~]# cat /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

[root@centos7 ~]#

检查防火墙

确认apache所在服务器的防火墙打开了80端口。(为简化步骤, 这里不讨论Web Server的SSL接入)

[root@centos7 ~]# systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
  Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
  Active: active (running) since Mon 2020-06-15 17:24:15 CST; 2s ago
    Docs: man:firewalld(1)
Main PID: 27433 (firewalld)
    Tasks: 2
  Memory: 25.1M
  CGroup: /system.slice/firewalld.service
          └─27433 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid

Jun 15 17:24:15 centos7 systemd[1]: Starting firewalld - dynamic firewall daemon...
Jun 15 17:24:15 centos7 systemd[1]: Started firewalld - dynamic firewall daemon.
[root@centos7 ~]# firewall-cmd --state
running
[root@centos7 ~]# firewall-cmd --zone=public --list-ports

[root@centos7 ~]# firewall-cmd --zone=public --add-port=80/tcp --permanent
Success
[root@centos7 ~]# firewall-cmd --reload
success
[root@centos7 ~]# firewall-cmd --zone=public --list-ports
80/tcp 
[root@centos7 ~]

如果Caché安装在另一台服务器, Web gateway和Caché间的通信通过Caché的superserver端口(默认1972), 因此Caché所在服务器的防火墙必须运行此端口访问。

对Apache调优

如果apache的工作模式为Prefork, 通过修改配置文件后重启服务,将工作模式改成“worker”(下面cat命令显示修改后的配置文件)

[root@centos7 ~]# apachectl -V | grep MPM
Server MPM:     prefork
[root@centos7 ~]# vim /etc/httpd/conf.modules.d/
[root@centos7 ~]# cat /etc/httpd/conf.modules.d/00-mpm.conf
# Select the MPM module which should be used by uncommenting exactly
# one of the following LoadModule lines:

# prefork MPM: Implements a non-threaded, pre-forking web server
# See: http://httpd.apache.org/docs/2.4/mod/prefork.html
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so

# worker MPM: Multi-Processing Module implementing a hybrid
# multi-threaded multi-process web server
# See: http://httpd.apache.org/docs/2.4/mod/worker.html
#
LoadModule mpm_worker_module modules/mod_mpm_worker.so

# Worker MPM parameters
ServerLimit	40
StartServers 	10
MaxRequestWorkers	1000
MinSpareThreads	75
MaxSpareThreads	250
ThreadsPerChild	25

# event MPM: A variant of the worker MPM with the goal of consuming
# threads only for connections with active processing
# See: http://httpd.apache.org/docs/2.4/mod/event.html
#
#LoadModule mpm_event_module modules/mod_mpm_event.so

[root@centos7 ~]# systemctl restart httpd
[root@centos7 ~]# apachectl -V | grep MPM
Server MPM:     worker
[root@centos7 ~]#

安装Web Gateway

最新的IRIS或者HealthConnect安装包可能不包含让用户选择是否安装csp/web gateway的选项, 因此大多数情况, 用户更多的是使用专门的Web/CSP gateway的安装包来安装, 无论Apach Server 和Caché/IRIS Server是否在同一台服务器上。

以下的介绍是用WebGateway 2020.1版本安装的过程。

  1. 解压缩安装包到一个临时文件夹 [root@centos7 ~]# tar -xzf WebGateway-2020.1.0.197.0-lnxrhx64.tar.gz

  2. 使用touch命令在/etc/httpd/conf.d目录下创建空配置文件isc.conf

Apaceh启动时会调用主配置文件/etc/httpd/conf/httpd.conf。 该文件的默认配置项中会自动include目录/etc/httpd/conf.d下的*.conf文件, 因此不同的应用创建不同的conf文件放在conf.d目录下是方便管理的通常做法。 这个.conf的文件名可以任意名字,isc.conf只是示意。(下一步安装Web Gateway时需要输入此名字)。

[root@centos7 httpd]# touch /etc/httpd/conf.d/isc.conf

同理手动 在 /opt/webgateway/bin 下面建立CSP.ini文件,并且赋予读写权限

  1. 到解压后的安装包目录下的install子目录, 执行以下命令:

    [root@centos7 ~]# cd WebGateway-2020.1.0.197.0-lnxrhx64/ [root@centos7 WebGateway-2020.1.0.197.0-lnxrhx64]# ls install lnxrhx64 [root@centos7 WebGateway-2020.1.0.197.0-lnxrhx64]# cd install/ [root@centos7 install]# ./GatewayInstall Starting Web Gateway installation procedure.

    Please select WebServer type. Choose "None" if you want to configure your WebServer manually.

    1. Apache
    2. None WebServer type? [2] 1

    Please enter location of Apache configuration file [/etc/httpd/conf/httpd.conf]: /etc/httpd/conf.d/isc.conf

    Enter user name used by Apache server to run its worker processes :

    Please enter location of Apache executable file </usr/sbin/httpd>: Apache version 2.4 is detected.

    Please enter destination directory for Web Gateway files [/opt/webgateway]:

    Do you want to create directory /opt/webgateway [Y]:

    Please enter hostname of your InterSystems IRIS server [localhost]: HCDEMO

    Please enter superserver port number for your InterSystems IRIS server [51773]:

    Please enter InterSystems IRIS configuration name [IRIS]: HCDEMO (注 这里的 configuration name 其实是csp.ini 中服务器的配置代称,可以任意起,不一定必填为服务器本身的hostname)

    Please enter directory for static CSP content [/opt/webgateway/hcdemo]:

    Do you want to create directory /opt/webgateway/hcdemo [Y]:

    Installing InterSystems IRIS Web Gateway for Apache:

    Apache configuration file: /etc/httpd/conf.d/isc.conf InterSystems IRIS configuration name: HCDEMO InterSystems IRIS server address: HCDEMO InterSystems IRIS server port number: 51773 Web Gateway installation directory: /opt/webgateway

    Do you want to continue and perform the installation [Y]:

    Updating Apache configuration file ...
    - /etc/httpd/conf.d/isc.conf
    
    * You need to restart your Apache server before any
      configuration changes will take effect.
    

    Web Gateway configuration completed! [root@centos7 install]#

安装结束后

如果不是在Apache服务器访问而是远程登录该页面,此时会出现错误提示,显示CSP Gateway的版本信息和“You are not authorized to use this facility”的提醒。这是CSP Gateway的安全策略。默认不允许远程的访问,对于需要远程访问的源IP地址或者源网段,用户必须手工在CSP.ini配置文件的[SYSTEM]块里添加,比如添加 ”System_Manager=172.16.58.100",或者"System_Manger=172.16.."。虽然不推荐,但"System_Manager=...”允许任意地址远程访问的远程访问。如果CSP.ini没有自动生成,那需要手动 在 /opt/webgateway/bin 下面建立此文件,并且赋予读写权限。 下面是添加System_Manager后的CSP.ini例子:

[root@centos7 bin]# cat /opt/webgateway/bin/CSP.ini
[SYSTEM_INDEX]
HCDEMO=Enabled
LOCAL=Enabled

[HCDEMO]
Ip_Address=HCDEMO
TCP_Port=51773
Minimum_Server_Connections=3
Maximum_Session_Connections=6

[APP_PATH_INDEX]
/=Enabled
/csp=Enabled
/hcdemo=Enabled

[APP_PATH:/]
Default_Server=HCDEMO
Alternative_Server_0=1~~~~~~HCDEMO

[APP_PATH:/csp]
Default_Server=HCDEMO
Alternative_Server_0=1~~~~~~HCDEMO

[APP_PATH:/hcdemo]
Default_Server=HCDEMO
Alternative_Server_0=1~~~~~~HCDEMO

[SYSTEM]
SM_Timeout=28800
Server_Response_Timeout=60
No_Activity_Timeout=86400
Queued_Request_Timeout=60
Configuration_Initialized=Tue Nov 17 07:58:29 2020
Configuration_Initialized_Build=2001.1740
System_Manager=*.*.*.*

[LOCAL]
Ip_Address=127.0.0.1
TCP_Port=1972
Minimum_Server_Connections=3
Maximum_Session_Connections=6
[root@centos7 bin]#

登录Web Gateway管理页面的抓图

检查Web Gateway的配置文件位置,版本,log位置

配置CSP Gateway到IRIS的连接,并测试从Apache登录IRIS维护界面

在CSP Gateway配置页面,查看Server Access。

Server Access中会列出这本CSP Gateway连接的IRIS实例的列表。在上面的安装步骤中,当问到了“ Please enter hostname of your InterSystems IRIS server [localhost]: HCDEMO ”选择了HCDEMO, 这时这个列表中会显示有两个Server, localhost和HCDEMO. (localhost无法被删除,遗憾)

下面检查HCDEMO Server的配置

  • 检查服务器地址为127.0.0.1
  • 添加到Caché服务器的账号密码,默认为CSPSystem, SYS

如果IIS服务器+Web Gateway和Caché位于两个不同的服务器, 或者需要添加到另一Caché Server的连接, 需要添加Server, 如下图, 需要的配置: Caché服务器的IP,superserver端口号, CSPSystem用于的密码,服务器的类型(可选)

测试Caché Server连接成功

双击左边菜单栏的"Test Server Connection", 确认结果中收到"Server connection test was successful: ...."的结果。

访问IRIS维护主页 (可选)

从链接 http://WebServer/csp/sys/Utilhome.csp 访问IRIS维护主页System Management Portal应该可以成功了,但您会发现有部分网页内容(element)无法加载。这是因为在默认的安装中,isc.conf中CSP Gateway路径的<Directory>配置的"CSPFileTypes csp cls zen cxw"中只将这4种类型的请求发送给CSP Gateway, 而被称为Static file的文件,比如.js, .css, .png等等类型的文件并没有被发送给CSP Gateway. 这是另外的一个安全机制,强制客户人工的配置是否需要从Web服务器访问IRIS维护主页。如果答案是NO, 那么访问IRIS维护页面就只能通过PWS,用IRIS服务器的52773的接口。 如果用户认为从Web服务器访问IRIS维护页面是必要的, 需要修改CSPFileTypes配置,比如修改成"CSPFileTypes *",作用是把任意类型的请求发送给IRIS。以下是安装并修改后的isc.conf文件示例。

[root@centos7 conf.d]# cat isc.conf
#### BEGIN-ApacheCSP-SECTION ####
LoadModule csp_module_sa "/opt/webgateway/bin/CSPa24.so"
CSPModulePath "/opt/webgateway/bin/"
CSPConfigPath "/opt/webgateway/bin/"
Alias /csp/ "/opt/webgateway/hcdemo/csp/"
<Location "/csp/bin/Systems/">
  SetHandler csp-handler-sa
</Location>
<Location "/csp/bin/RunTime/">
  SetHandler csp-handler-sa
</Location>

<Directory "/opt/webgateway/hcdemo/csp">
  CSPFileTypes *
  AllowOverride None
  Options MultiViews FollowSymLinks ExecCGI
  Require all granted
  <FilesMatch "\.(log|ini|pid|exe)$">
    Require all denied
  </FilesMatch>
</Directory>
<Directory "/opt/webgateway/bin/">
  AllowOverride None
  Options None
  Require all granted
  <FilesMatch "\.(log|ini|pid|exe)$">
    Require all denied
  </FilesMatch>
</Directory>
#### END-ApacheCSP-SECTION ####
#### BEGIN-ApacheCSP-SECTION-HCDEMO ####
Alias /hcdemo/csp/ "/opt/webgateway/hcdemo/csp/"
#### END-ApacheCSP-SECTION-HCDEMO ####
[root@centos7 conf.d]#

注意isc.conf修改后需要重启apache server [root@centos7 conf.d]# systemctl restart httpd [root@centos7 conf.d]#

访问IRIS上的其他Web Application

IRIS上其他的Web Application需要经过配置才可以发送到IRIS Server。这些Web Application可能是一个访问HTTP, REST的URL, 或者是一个用户自己定义的SOAP,甚至可能是一个简单的CSP文件。要确保他们被发送给IRIS Server, 用户需要:

  1. 配置Apache配置文件isc.conf, 保证请求被发送给了CSP Gateway。 可以通过CSP Gateway管理页面的HTTP Trace来确认。
  2. 如果需要,配置CSP Gateway, 将请求发送给IRIS.

访问带文件后缀的应用

在isc.conf中的<Directory>中定义的是Web Server中文件对象的地址,比如"/opt/webgateway/bin/"是CSP Gateway的.so文件的存放位置。 Alias是URL中资源地址,比如"/csp/"到<Directory>定义的映射。他们在apache中注册一个有后缀的文件的发送路径, 这个配置使得访问"http://WebServer/csp/sys/Utilhome.csp"可以成功发送给CSP Gateway。

Alias /csp/ "/opt/webgateway/hcdemo/csp/"
<Directory "/opt/webgateway/hcdemo/csp">
      CSPFileTypes *
      AllowOverride None
      Options MultiViews FollowSymLinks ExecCGI
      Require all granted
      <FilesMatch "\.(log|ini|pid|exe)$">
        Require all denied
      </FilesMatch>
</Directory>

对于其他的Web Application, 比如如果需要将"http://WebServer/test/Hello.csp"成功发送给CSP Gateway, 需要添加以下配置,它把路径为”/test/"的URL发送给CSP Gateway处理。

Alias /test/ "/opt/webgateway/hcdemo/csp/"

测试连接一个SOAP服务,注意这个服务要在IRIS的Web Applicatin里配置正确,它至少可以从PWS用匿名用户访问。(关于Web Application的配置另行文档, 简单说, 要匿名访问, 要使用%Security_WebGateway的资源).测试结果:

[root@centos7 conf.d]# curl http://172.16.58.100/test/test.webservice1.cls?soap_method=winter
<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:s='http://www.w3.org/2001/XMLSchema'>
  <SOAP-ENV:Body><winterResponse xmlns="http://tempuri.org"><winterResult>Winter is Coming...</winterResult></winterResponse></SOAP-ENV:Body>
</SOAP-ENV:Envelope>
[root@centos7 conf.d]#

访问其他URL应用

对于 “http://172.16.58.100/api/mgmnt/v2/”这样的URL地址, 映射到CSP Gateway处理需要的配置是<Location>。下面的配置保证对"/api/"开头的,没有文件地址的URL的处理:

<Location "/api/">
      SetHandler csp-handler-sa
      CSP on
</Location>

请求的结果如下:

[root@centos7 ~]# curl -X GET "http://172.16.58.100/api/mgmnt/v2/" [{"name":"%Api.IAM.v1","webApplications":"/api/iam","dispatchClass":"%Api.IAM.v1.disp","namespace":"%SYS","swaggerSpec":"/api/mgmnt/v2/%25SYS/%25Api.IAM.v1"},{"name":"%Api.Mgmnt.v2","webApplications":"/api/mgmnt","dispatchClass":"%Api.Mgmnt.v2.disp","namespace":"%SYS","swaggerSpec":"/api/mgmnt/v2/%25SYS/%25Api.Mgmnt.v2"},{"name":"PetStore","dispatchClass":"PetStore.disp","namespace":"DEMO","swaggerSpec":"/api/mgmnt/v2/DEMO/PetStore"}][root@centos7 ~]#

备注: 如果得到了{"msg":"错误 #8754: Unable to use namespace: USER."},或者403 forbidden, 需要在IRIS上给Web Application "/api/mgmnt"添加”%DB_USER"的权限;或者,也可以将应用的“安全设置”设成"密码",然后使用下面的命令查看:

[root@centos7 conf.d]# curl -i -X GET http://172.16.58.100/api/mgmnt/v2/ -u "_system:SYS"
HTTP/1.1 200 OK。。。(后面省略)
[{"name":"%Api.IAM.v1","webApplications":"/api/iam","dispatchClass":"%Api.IAM.v1.disp","namespace":"%SYS","swaggerSpec":"/api/mgmnt/v2/%25SYS/%25Api.IAM.v1"},{"name":"%Api.Mgmnt.v2","webApplications":"/api/mgmnt","dispatchClass":"%Api.Mgmnt.v2.disp","namespace":"%SYS","swaggerSpec":"/api/mgmnt/v2/%25SYS/%25Api.Mgmnt.v2"},{"name":"PetStore","dispatchClass":"PetStore.disp","namespace":"DEMO","swaggerSpec":"/api/mgmnt/v2/DEMO/PetStore"}][root@centos7 conf.d]#

CSP Gateway配置 "Application Access"

通常情况下,CSP Gateway测试成功连接IRIS Server后,会发现IRIS上的Web Application列表,并添加到自己的”APPlicaiton Access"列表里。如下图所示。每次用户在IRIS添加一个新的Web应用, 只需要在isc.conf上做相应的配置,无需人工去更新CSP Gateway的配置。

特殊的情况,如果发现某个URL无法发送到IRIS。先打开了CSP Gateway的HTTP Trace,确认CSP Gateway可以收到请求消息但无法发送到IRIS, 这是需要人工检查并且配置"Application Access".

0
0 599
文章 Hao Ma · 一月 30, 2021 6m read

本文介绍InterSystems Web Gateway的安装和配置。

在2018以前的ISC产品中, InterSystems Web Gateway被称为CSP Gateway。, CSP是Cache'的页面技术。InterSystems的产品页面,Web服务等大多是CSP写成的。IRIS发布后CSP Gateway改名成Web Gateway, 但内部的配置文件,说明等等还到处可见CSP Gateway的叫法。在本文里不同的地方有这两个说法别奇怪,他们是一个东西。 IRIS通过它和外部Web服务器连接。 本文的内容适用任何ISC产品的部署,包括页面的选项Cache'. IRIS,HealthConnect, Ensemble等等。它的作用和表现是一样的。 无论您使用的是Cache',IRIS , HealthConnect还是HealthShare, 只有在生产环境中使用HTTP请求,基本上都需要使用Web Gateway。

如果需要更详细的内容,请参考在线文档:InterSystems Web Gateway

什么是Web Gateway

CSP是Cache' Server Page的缩写,如同JSP(Java Server Page)是Java的前端技术, CSP是InterSystems的前端技术。要在IRIS或者HealthConnect上提供一个HTTP服务,唯一安全可靠的技术是CSP. 创建HTTP,REST服务直接创建CSP页面, 创建SOAP服务使用%SOAP.WebService或者EnsLib.SOAP.Service, 它们都是%CSP.Page的子类,因此在IRIS的在线文档中有CSP Server的称法,指的就是IRIS中负责CSP处理的那部分功能。

CSP Server并不监听TCP端口得到HTTP消息,它只能通过CSP Gateway从Web服务器接收请求。用户的请求要先发给IIS/Apach/Nginx等Web服务器,转发给IRIS, 而Web Gateway就是Web服务器发请求给IRIS所使用的网关。

或者说, 它是InterSystems提供的给第三方Web服务器的一个组件,或者称为模块。在Windows系统中是若干DLL库文件,在LINUX环境是SO动态链接库。安装CSP Gateway就是诸如”CSPa24.so"等文件拷贝到Web服务器的目录,将这些模块配置到Web服务器,并将以.csp,.cls,.zen结尾的HTTP请求发送给IRIS。如果Web服务器和IRIS独立安装在不同的硬件服务器上(更安全的方式),发送的是TCP消息,到IRIS的superserver端口,默认是51773(Cache'是1972)。

CSP Gateway支持3种Web服务器:IIS, Apache Web Server, Nginx。 后面的链接提供了完整的在各种操作系统中ISC产品支持的Web Server的版本: IRIS支持的第三方Web Server列表

听上去是不是挺简单?那用户还有什么可糊涂的?

Private Web Server(PWS)带来的混乱

混乱来自IRIS的安装过程会安装一个私有的Apache Web服务器,被称作PWS。它的作用有两个:支持访问维护页面;给一个测试环境提供测试Web服务的能力。在线文档是这么描述PWS的: >> The PWS is not supported for any other purpose. For deployments of http-based applications, including REST, CSP, Zen, and SOAP over http or https, you should not use the private web server for any application other than the Management Portal; instead, you must install and deploy one of the supported web servers. For information, see the section “Supported Web Servers” in the online InterSystems Supported Platforms document for this release.(如果要部署http应用, 包括在http或者https上层的REST, CSP, Zen, SOAP,你绝不能让除Management Portal以外的任何应用使用PWS. 你必须安装一个IRIS兼容的Web服务器。了解这部分内容, 请查看InterSystems在线文档的"Supported Web Servers"部分)

然后很多用户没有意识到这个提醒。当安装IRIS时被问到”你想要安装CSP网关并未CSP网关配置外部Web服务器(IIS和Apache)吗?"时,他们选择了“不要安装CSP网关",然后浏览器接入维护界面,开发了若干Web服务,一直没有意识使用PWS访问IRIS上的Web服务在生产环境是不可接受的。PWS是一个非常轻量级的Apache Web服务器。它的程序包在IRIS安装目录下的httpd子目录里。IRIS启动后, 它开始工作,监听IRIS上配置的Web端口,默认是57772,或者52773.它的工作机制决定了它无法承受大的负载,因此不能用于生产环境的http应用。

它和CSP/IRIS Server的连接用的是与上面讲的CSP Gateway完全相同的方式,也就是说,这里有一个PWS专用的Gateway, 我们可以称它为Private CSP Gateway。为了写的更清楚,总结了下面几点:

CSP Gateway

  • 安装IRIS实例时用户可以选择是否安装CSP Gateway. 如果这时没选择安装,后面可以用单独的安装包安装。
  • 安装的程序可以放在任何位置。比如在Linux默认放在"/opt/webgateway"目录,配置文件在Web Gateway的配置文件目录。
  • 访问CSP Gateway的管理页面是 http://WebServer:80/csp/bin/Systems/Module.cxw 。 (这里的WebServer是Web服务器的地址,➕它的端口是默认的80)

PWS

  • 安装时自动安装
  • 程序和配置都在IRIS的安装目录,比如"C:/InterSystems/HealthConnect/CSP/bin/"
  • 访问管理页面的地址是 http://IRIS:57772/csp/bin/Systems/Module.cxw,这里的IRIS是IRIS服务器的地址,如果是本机登录,也就是localhost.

注意一点:从PWS访问IRIS管理页面, 比如 http://localhost/csp/sys/UtilHome.csp, 选择其中的 “系统管理 > 配置 > Web网关管理"进入的是PWS的配置。如果是从 http://WebServer/csp/sys/UtilHome.csp进入的IRIS管理页面, 那么同样的操作进入的是CSP Gateway的管理页面。 这很容易从页面显示的Web Server的类型和版本发现区别。

其他关于部署CSP Gateway的疑问

  • 一个Web服务器可以连接多个CSP Gateway吗? 如果你真正理解了CSP Gateway, 你就明白它是Web服务器工作的一部分,比如在IIS里面它就是配置的一个虚拟路径。技术上你可以多配一个,但完全没有必要。 如果要把HTTP从一个Web服务器发到多个IRIS, 可以在一个Web Gateway里配置多个"Server Access"连接。

  • 一个CSP Gateway是怎么连接多个IRIS Server的?

CSP Gateway可以配置多个"Server Access”, 只是要区分出收到的请求应该发给那个IRIS Server.如果分发给不同的IRIS的URL是不同的,比如CSP Gateway可以路由"/csp/demo1"到第一个IRIS, "/csp/demo2"到第2个IRIS。

  • Web Server要和IRIS部署在一台服务器吗?

生产环境中, 部署单独的Web Server通常是好选择。为了安全起见,很多用户会部署Web Server的高可用。

如果Web Server和Caché/IRIS分别装在两台服务器,IRIS安装时选择“不要安装CSP网关”,在Web Server的服务器上安装单独的Web Gateway软件包,测试和Caché/IRIS的连接。

如果是Web Server和Caché/IRIS装在同一台服务器, 那么应该先安装Web Server, 然后使用Caché/IRIS安装包安装Caché, 选择 “安装CSP网关”, 这样CSP网关会被安装在Web Server的目录下, 相关的模块和Web Server配置也会自动完成。 如果顺序反过来, 那么需要手工配置Web Server, 增加的不必要的复杂步骤。

  • 安装外部Web Server能使用私有Web Gateway吗?

对Web服务器有了解的用户更会有这样的疑问。既然Web Gateway只是给Web Gateway工作的程序组件,那么是否从外部服务器就可以直接使用私有的Web Gateway了,何必再多安装一个。 是的,技术上这样是可行的。前提是,1. 外部Web服务器和IRIS在一台硬件服务器上。2. 客户要对外部服务器的配置非常熟悉,可以手工配置外部Web服务器对私有Web Gateway的访问, 包括路径或者虚假路径,文件夹的访问权限,用户或者用户组的权限等等。总的说, 这样既麻烦,又不便于后期的管理,因此我推荐还是重新装一个Web Gateway。只是要分清它和私有的连接PWS的Web Gateway的区别,而永远不要让他们混在一起。

安装CSP Gateway的具体步骤请参考下面的文章:

WebGateway系列(2): 配置Apache连接IRIS WebGateway系列(3): 配置IIS连接IRIS

0
3 923
文章 Nicky Zhu · 一月 11, 2021 3m read

当我向技术人员介绍InterSystems IRIS时,我一般会先讲其核心是一个多模型DBMS。

我认为这是其主要优势(在DBMS方面)。数据仅存储一次。您只需访问您想用的API。

  • 您想要数据的概要?用SQL!
  • 您想用一份记录做更多事情?用对象!
  • 想要访问或设置一个值,并且您知道键?用Globals!

乍一看挺好的,简明扼要,又传达了信息,但当人们真正开始使用InterSystems IRIS时,问题就来了。类、表和Globals是如何关联的?它们之间有什么关系?数据是如何存储的?

本文我将尝试回答这些问题,并解释这些到底是怎么回事。

第一部分 模型偏见

处理数据的人往往对他们使用的模型有偏见。

开发者们把数据视为对象。对他们而言,数据库和表都是通过CRUD(增查改删,最好是基于ORM)交互的盒子,但底层的概念模型都是对象(当然这对于我们大多数使用面向对象编程语言的开发者来说没错)。

而DBA大部分时间都在搞关系型DBMS,他们把数据视为表。对象只是行的封装器。

对于InterSystems IRIS,持久类也是一个表,将数据存储在Global中,因此需要进行一些澄清。

第二部分 举例

假设您创建了类Point:

Class try.Point Extends %Persistent [DDLAllowed]
{
    Property X;
    Property Y;
}

您也可以用DDL/SQL创建相同的类:

CREATE Table try.Point (
    X VARCHAR(50),
    Y VARCHAR(50))

编译后,新类将自动生成一个存储结构,将原生存储在Global中的数据映射到列(对于面向对象开发者而言,是属性):

Storage Default
{
<Data name="PointDefaultData">
    <Value name="1">
        <Value>%%CLASSNAME</Value>
    </Value>
    <Value name="2">
        <Value>X</Value>
    </Value>
    <Value name="3">
        <Value>Y</Value>
    </Value>
</Data>
<DataLocation>^try.PointD</DataLocation>
<DefaultData>PointDefaultData</DefaultData>
<IdLocation>^try.PointD</IdLocation>
<IndexLocation>^try.PointI</IndexLocation>
<StreamLocation>^try.PointS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

这是怎么回事?

自下向上(加粗文字很重要):

  • Type: 生成的存储类型,本例中是持久对象的默认存储
  • StreamLocation - 存储流的Global
  • IndexLocation - 索引Global
  • IdLocation - 存储ID自增计数器的Global
  • DefaultData - 存储将Global值映射到列/属性的XML元素
  • DataLocation - 存储数据的Global

现在我们的DefaultData是PointDefaultData,让我们分析下它的结构。本质上Global节点有这样的结构:

  • 1 - %%CLASSNAME
  • 2 - X
  • 3 - Y

所以我们可能期望我们的Global是这样的:

^try.PointD(id) = %%CLASSNAME, X, Y

但如果我们输出 Global 它会是空的,因为我们没有添加任何数据:

zw ^try.PointD

让我们添加一个对象:

set p = ##class(try.Point).%New()
set p.X = 1
set p.Y = 2
write p.%Save()

现在我们的Global变成了这样

zw ^try.PointD
^try.PointD=1
^try.PointD(1)=$lb("",1,2)

可以看到,我们期望的结构%%CLASSNAME, X, Y是用 $lb("",1,2) 设置的,它对应的是对象的X和Y属性(%%CLASSNAME 是系统属性,忽略)。

我们也可以用SQL添加一行:

INSERT INTO try.Point (X, Y) VALUES (3,4)

现在我们的Global变成了这样:

zw ^try.PointD
^try.PointD=2
^try.PointD(1)=$lb("",1,2)
^try.PointD(2)=$lb("",3,4)

所以我们通过对象或SQL添加的数据根据存储定义被存储在Global中(备注:可以通过在PointDefaultData 中替换X和Y来手动修改存储定义,看看新数据会怎样!)。

现在,如果我们想执行SQL查询会怎样?

SELECT * FROM try.Point

这段sql查询被转换为ObjectScript代码, 遍历^try.PointD,并根据存储定义(其中的 PointDefaultData 部分)填充列。

下面是修改。让我们从表中删除所有数据:

DELETE FROM try.Point

看看我们的Global变成什么样了:

zw ^try.PointD
^try.PointD=2

可以看到,只剩下ID计数器,所以新对象/行的ID=3。我们的类和表也继续存在。

但如果我们运行会怎样:

DROP TABLE try.Point

它会销毁表、类并删除Global。

zw ^try.PointD

看完这个例子,希望您现在对Global、类和表如何相互集成和互补有了更好的理解。根据实际需要选用正确的API会让开发更快、更敏捷、bug更少。

0
0 161
文章 Nicky Zhu · 一月 10, 2021 8m read

当您首次使用InterSystems IRIS时,通常只需安装最低安全级别的系统。您输入密码的次数会比较少,这样有利于快速了解和操作开发服务和Web应用程序。而且,最低的安全性有时更便于部署开发项目或解决方案。

然而,有时需要将项目移出开发环境,迁移到一个可能很不友好的互联网环境中。在部署到生产环境之前,需要使用最大的安全设置(即,完全锁定)对其进行测试。这就是我们在本文中将要讨论的内容。

如果想更全面地了解InterSystems Caché、Ensemble和IRIS中的DBMS安全性问题,请阅读我的另一篇文章《在生产环境中安装InterSystems Caché DBMS的相关建议》。

InterSystems IRIS中安全系统的设计概念是针对不同的类别(用户、角色、服务、资源、特权和应用程序)应用不同的安全设置。

image 可以为用户分配角色。用户和角色可以对资源(数据库、服务和应用程序)拥有不同的读、写和使用权限。用户和角色还可以对数据库中的SQL表拥有SQL权限。

安全级别的差异

用户在安装InterSystems IRIS时,可以为其选择安全级别:最小、正常或锁定。这些级别在用户参与程度、可用角色和服务,以及服务和应用程序的身份验证方法的配置方面存在差异。如需了解更多信息,请阅读《InterSystems IRIS安装准备》指南中的《InterSystems安全性准备》章节。

在文档中,您可以找到下面这些表格,其中显示了每个级别的安全设置。这些安全设置可以在系统管理门户界面进行更改。

初始用户安全设置

Security SettingMinimalNormalLocked Down
Password Pattern3.32ANP3.32ANP8.32ANP
Inactive Limit090 days90 days
Enable _SYSTEM UserYesYesNo
Roles assigned to UnknownUser%AllNoneNone

初始服务属性设置

Service PropertyMinimalNormalLocked Down
Use Permission is PublicYesYesNo
Requires AuthenticationNoYesYes
Enabled ServicesMostSomeFewest

初始服务启用设置

ServiceMinimalNormalLocked Down
%Service_BindingsEnabledEnabledDisabled
*%Service_CSPEnabledEnabledEnabled
%Service_CacheDirectEnabledDisabledDisabled
%Service_CallInEnabledDisabledDisabled
%Service_ComPortDisabledDisabledDisabled
%Service_ConsoleEnabledEnabledEnabled
%Service_ECPDisabledDisabledDisabled
%Service_MSMActivateDisabledDisabledDisabled
%Service_MonitorDisabledDisabledDisabled
%Service_ShadowDisabledDisabledDisabled
%Service_TelnetDisabledDisabledDisabled
%Service_TerminalEnabledEnabledEnabled
%Service_WebLinkDisabledDisabledDisabled

*InterSystems IRIS环境下,%Service_CSP应用%Service_WebGateway。 不同的操作系统所使用的服务略有不同。

如何提高安全性

您需要为每个启用的服务选择合适的身份验证方法,包括:无认证(unauthenticated)、密码、Kerberos或授权。

您还需要禁用系统中未使用的web应用程序。对已启用的web应用程序选择正确的身份验证方法:认证、密码、Kerberos、授权、登录或cookie。

当然,管理员可以为每一个项目和解决方案选择安全设置,以满足客户的项目要求。整个过程应始终保持一种平衡,即,一方面要保证系统足够方便以支持用户完成实际工作,另一方面又要保证系统足够安全能够阻止入侵者。不过众所周知,被禁用的系统才是最安全的系统。

如果遇到需要多次手动提高系统安全级别的情况,这就是一个明确的迹象,表明需要编写一个软件模块来解决这些问题。

实际上,InterSystems Open Exchange提供了一个锁定(LockDown)程序,可以帮助您提高安全性。该程序的源代码可以在InterSystems isc-apptools-lockdown页面的存储库中找到。

LockDown程序有以下几种作用:

首先,更改以下预安装用户的密码:

  • Admin,
  • CSPSystem,
  • IAM,
  • SuperUser,
  • UnknownUser,
  • _Ensemble,
  • _SYSTEM.

其次,禁用除以下服务之外的所有服务:

  • %%service_web gateway
  • %service_console
  • %service_login
  • %service_terminal

再次,为所有web应用程序设置密码保护,包括:

  • /csp/ensdemo
  • /csp/samples
  • /csp/user
  • /isc/studio/usertemplates
  • /csp/docbook
  • /csp/documatic
  • /isc/studio/rules
  • /isc/studio/templates

最后,设置系统范围内的安全参数,包括:

  • 密码复杂度为 "8.32 ANP"
  • 限制90天内不活跃的用户
  • 开启审计和其他所有安全相关的事件

您可以从GitHub下载LockDown.cls,在系统上安装好LockDown程序。然后在终端输入以下内容:

USER>zn “%SYS”
%SYS>do $system.OBJ.Load("/home/irisusr/LockDown.cls","ck")

或者可以使用以下命令从公共注册中心通过ZPM批处理管理器进行安装:

USER>zn “%SYS”
%SYS> zpm “install isc-apptools-lockdown”

执行LockDown程序

强烈建议在执行LockDown程序之前先进行备份。

必须从%SYS命名空间执行LockDown程序。如果您不想更改所有预安装用户的密码,请将第一个参数保留为空。

如果希望保留使用IRIS Studio、Atelier或VSCode编辑程序和类的能力,请不要禁用%Service_Bindings服务,只需将bindings参数设置为1即可。下面是一个示例: do ##class(App.Security.LockDown).Apply("New Password 123",.msg,1) 此模块还包含一个功能,该功能在系统密码被盗用、需要替换所有预装帐户但无需执行锁定的情况下很有用。可以参考下面的运行: do ##class(App.Security.LockDown).Change Password("New Password 123", "Admin,CSPSystem,IAM,SuperUser,Unknown User, _Ensemble,_SYSTEM") 在执行锁定之后,应用程序或项目极有可能会停止工作。为了解决这个问题,需要将一些安全设置恢复到原始状态。这个操作可以通过管理门户界面(安全性部分)实现或以编程方式完成。

锁定后更改安全设置

锁定之后,如果您的web应用程序使用了密码以外的身份验证方法,就需要进行启用。

建议运行软件模块zpm-registry-test-deployment,它有一个在ZPM-registry项目中使用LockDown的示例。

在IRIS上以最低的安全级别安装该项目,安装结束时将开始运行其中的代码。代码可以用来:

  • 更改所有预安装用户的密码。
  • 禁用此项目中未使用的所有服务。
  • 为系统上的所有应用程序启用密码保护,但Web应用程序/注册表(允许未经授权的用户获取注册表中的软件包列表)除外。
  • 创建一个拥有在注册表中发布新包特权的新用户。该用户必须对IRISAPP数据库中的项目表具有写权限。

创建一个新用户:

set tSC= ##class(App.Security.LockDown).CreateUser(pUsername, "%DB_"_Namespace, pPassword, "ZMP registry user",Namespace)
If $$$ISERR(tSC) quit tSC
write !,"Create user "_pUsername

为新的未授权用户添加权限:

set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM.Package", "s", "UnknownUser")
set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM.Package", "s", pUsername)
set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM.Package_dependencies", "s", pUsername)
set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM_Analytics.Event", "s", pUsername)
set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "9,ZPM.Package_Extent", "e", pUsername)
set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "9,ZPM_Analytics.Event_Extent", "e", pUsername)
If $$$ISERR(tSC) quit tSC
write  !,"Add privileges "

运行LockDown项目:

set tSC= ##class(App.Security.LockDown).Apply(NewPassSys)
If $$$ISERR(tSC) quit tSC

Change the settings for the web app so that an unknown user can log in:
set prop("AutheEnabled")=96
set tSC=##class(Security.Applications).Modify("/registry",.prop)
If $$$ISERR(tSC) quit tSC
write !,"Modify /registry "

Change the settings for the %service_terminal service, changing the authorization method to Operating System, Password:
set name="%service_terminal"
set prop("Enabled")=1
set prop("AutheEnabled")=48 ; Operating System,Password
set tSC=##class(Security.Services).Modify(name,.prop)
If $$$ISERR(tSC) quit tSC
write !,"Modify service terminal"

总结

在本文中,我讨论了为何要提高系统安全性级别的原因,并且通过一个InterSystems LockDown程序运行示例,演示了如何通过编程的方式提升安全性。

在本文所介绍的方法中,我们首先关闭了系统中的所有内容(即,设置最大安全级别)。然后通过开放项目运行所需的服务和应用程序(但仅限于这些)来控制安全性。我相信还有许多其他的方法和最佳实践,欢迎大家在文章评论区留言告诉我们。

0
0 248
文章 Hao Ma · 一月 10, 2021 17m read

RESTful 应用程序编程接口 (API) 设计和文档编制初学者指南。 通过示例,您将学习一些常见的 RESTful API 模式。

在阅读之前

您需要知道

  • 如何在 Ensemble 中创建 RESTful Web 服务
  • 如何在 Ensemble 中使用 RESTful Web 服务
  • 如何传递服务参数
  • 如何返回服务结果

什么是服务 API?

什么是应用程序编程接口? 是具体化的东西吗? 是单一编程单元吗? API 的作用是什么? 在我看来,API 是由程序代码以间接方式决定的。 但完全定义的 API 是由运行可执行程序的容器(由部署设置控制)提供的。 因此,我宁愿将 API 定义为服务的公共描述。 该描述可以是人类可读的,也可以仅机器可读, 或者两者均可。 API 用于与将要使用服务的人员共享有关服务的基本信息。 API 说明了服务的作用、使用环境、功能以及管理的数据结构等。

在过去的好时光,“编制程序文档”或多或少是一种“必要之恶”。 现代编程语言通过在程序源码中引入声明来强制编制文档。 虽然声明是“机器人”可读的文档,但通过使用工具(runoff、Java doc...),可以提取信息并将其格式化成人类可读的格式。 即使没有在源码中添加任何一行真正的文档,这些工具仍然能够生成少量文本。

现在有什么不同吗? 并没有。 服务 API 仍然是一个抽象的概念,表示正常使用一个功能性计算机软件所需的信息集合。 有一些语言可以将 API 定义形式化,例如 Web 服务描述语言 (WSDL)。 遗憾的是,这些语言的使用受限。 原因并不在于,例如,WSDL 的能力不足以表达 RESTful API,而是因为非技术上的不匹配。 (用 XML 表达 JSON 结构会是怎样的?) 最终,没有像适用于 SOAP Web 服务的 WSDL 那样的适用于 REST 的实际标准语言。 很可惜。 是不是?

没关系。 无论如何,我们首先需要了解 API 编制的内容。

API 由什么组成?

服务的核心属性是什么?

  • 服务位置。 服务的 URL 根路径。 例如 http://localhost:57774/csp/msa/person
  • 服务方法。 即服务的功能。 方法由 HTTP 标头中的动词(GET、POST、PUT…)与其他路径类型参数的组合定义。
  • 接受的方法参数。 参数及其类型的列表。 类型可以是路径,表示 URL 路径中包含的参数;查询,表示编码的 URL 查询;表单,表示表单数据;内容,表示 HTTP 消息正文。
  • 返回状态。 HTTP 响应标头中的状态字段。 每个服务方法可能有多个返回状态码。 数字取决于服务方法和异常处理的粒度。
  • 响应内容。 每个状态码的预期内容。 格式可能因状态码而异。 例如,成功完成某个请求后,预期获得一个 JSON 序列化对象。 如果服务器出错 (500),将发送纯文本说明。

示例 API

首先,让我们试着描述一下我们要实现的目标。 我们要构建一个非常简单的服务。 一个注册表服务。 它将管理相同类型的资源。 例如人员。

结构非常简单:姓名、出生日期和地点、母亲的娘家姓和生成的内部唯一注册表 ID。 地点的结构如下:国家/地区、城市。

我们要向注册表插入一条新记录(更新完整的注册表条目),更新条目的各个属性(更新属性),删除条目,按注册表 ID 获取单个条目,根据属性匹配查询注册表 ID 列表。

我们还需要一些服务功能:初始化注册表,填充一些记录以进行测试。

从外部看,我们希望 http://localhost:57774/csp/msa/person 为服务位置。

在服务位置通过 PUT 添加新条目。 将注册表记录作为内容发送。 预期返回带注册表 ID 的完整条目。

通过 POST 更新。 URL 包含要更新的条目的注册表 ID。 要更新的属性以表单数据的形式发送。

GET 用于检索数据。 如果 URL 路径以 ID 结尾,则返回由 ID 标识的注册表条目。 如果找不到 ID,但 URL 中有查询,则返回 ID 列表。 例如 http://localhost:57774/csp/msa/person/12A33 返回条目 12A33。 查询键值对是内部用于选择条目的属性匹配子句。 例如,http://localhost:57774/csp/msa/person?name=Hahn%20Istvan&dob=1961 返回出生于 1961 年且姓名为 Hahn Istvan 的人员名单。

DELETE 执行删除。

POSThttp://localhost:57774/csp/msa/person/_init 将初始化注册表。

POSThttp://localhost:57774/csp/msa/person/_populate/100 将加载 100 条测试条目。

API 文档

下面一节给出了一个如何对服务 API 编制文档的示例。 请记住,结构和内容均未标准化。 这只是一个示例。

我尽量让文档编制变得“工具不可知”。 市场上已有文档编制工具。 其中一些的表现已几乎达到应有的效果。 本节的目的是让您感受一下,使用文本编辑器来编制 API 文档有多复杂。

资源:            人员

一个用于管理人员类型资源的通用服务。 人员具有最少的一组属性。 基本上是人口统计特征和注册表 ID。

位置:    http://localhost:57774/csp/msa/person

方法:

根据资源的唯一 ID 获取单个资源。

动词:GET

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
  资源 ID
</td>

<td>
  要检索的资源的唯一 ID。
</td>
名称
1

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  找到记录。
</td>
<td>
  无
</td>

<td>
  不存在该资源 ID 的记录。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
204
401
403
500
501
503

 

方法:

根据非唯一查询获取匹配的资源 ID 列表。 该方法使用查询部分来构建查询字符串。 查询键/值对会转换为列名/值对。

动词:GET

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
  搜索条件。
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  查询
</td>

<td>
  日期
</td>

<td>
   
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
   
</td>
名称
name
motherMaidenName
dob
birthPlaceCounty
birthPlaceCity

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  找到记录。
</td>
<td>
  无
</td>

<td>
  无匹配记录。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
204
401
403
500
501
503

 

方法:

从注册表中删除条目。

动词:DELETE

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
  字符串
</td>

<td>
  唯一注册表 ID。
</td>
名称
1

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  记录已删除。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

方法:

向注册表添加或更新条目。

动词:PUT

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  内容
</td>

<td>
  JSON
</td>

<td>
  序列化为 JSON 格式的对象。
</td>
名称

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  注册表服务已新增或更新具有生成的资源 ID 的条目。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

方法:

更新注册表条目的单个属性。

动词:POST

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>

<td>
   
</td>
<td>
  路径
</td>

<td>
  字符串
</td>

<td>
  要更新的条目的资源 ID。
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
  属性的新值
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  日期
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
   
</td>
名称
1
name
motherMaidenName
dob
birthPlaceCounty
birthPlaceCity

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  记录已更新。
</td>
<td>
  无
</td>

<td>
  不存在该资源 ID 的记录。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
204
401
403
500
501
503

 

方法:

初始化注册表。

动词:POST

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
   
</td>

<td>
   
</td>
名称
_init

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  无
</td>

<td>
  已初始化。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

方法:

填充测试数据。

动词:POST

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
   
</td>

<td>
   
</td>
<td>
  路径
</td>

<td>
  数值
</td>

<td>
  要填充的条目数。
</td>
名称
_populate
2

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  无
</td>

<td>
  已初始化。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

数据结构:

人员

名称类型标记注释
ID注册表 IDR生成的注册表 ID。
Name字符串R该人员的母语形式的姓名。
DOB日期R出生日期。
BirthPlace出生地O出生地。
MotherMaidenName字符串O母亲的娘家姓。

出生地

名称类型标记注释
Country字符串O国家/地区代码。
City字符串R城市名称

错误

名称类型标记注释
Code字符串R错误代码
Text字符串O错误文本
InnerError错误O报告组件的子组件报告内部错误。

实现

以下部分给出了我们前面讨论的资源注册表的示例。 这同样只是一个示例。

为了让您更容易理解,我人为地将源码进行了分组。

n  属于 API 的所有内容都放到资源映射类中。

n  完整的 UrlMap XData 块分成单个 Route 条目。

n  每个条目都粘附到实际实现功能的静态方法。

所以,要恢复真实的类,需要进行一些(重新)设计。 请愉快地(重新)设计!

第一个服务方法是查询...



<

Route Url="/:service" Method="GET" Call="QueryRegistry"/>

classmethod QueryRegistry(service) as %Status {     

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.runQuery(..getQueryParameters($listbuild("name","dob","motherMaidenName","birthPlaceCountry","birthPlaceCity"))))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/:registryID" Method="GET" Call="GetEntry"/>

classmethod GetEntry(service,registryID) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.get(registryID))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/:registryID" Method="DELETE" Call="DeleteEntry"/>

classmethod DeleteEntry(service,registryID) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.delete(registryID))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/_init" Method="POST" Call="InitializeRegistry"/>

classmethod InitializeRegistry(service) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.init())

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/_populate/:numberOfRecords" Method="POST" Call="Populate"/>

classmethod Populate(service,numberOfRecords) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.populate(numberOfRecords))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/:registryID" Method="POST" Call="UpdateAttribute"/>

classmethod UpdateAttribute(service,registryID) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.updateAttribute(registryID, ..getFormParameters($listbuild("name","dob","motherMaidenName","birthPlaceCountry","birthPlaceCity"))))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service" Method="PUT" Call="AddOrUpdate"/>

classmethod AddOrUpdate(service) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.addOrUpdate(..getContentParameter()))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 

现在是时候分享您的实用方法了。

classmethod getServiceInstance(serviceName) as Ens.BusinessService {

        set status = ##class(Ens.Director).CreateBusinessService(serviceName, .instance)

        throw:$$$ISERR(status) ##class(NoProduction).%New(status)

        quit instance

}

 

classmethod getHTTPStatusCode(ex) {

        quit $case(ex.%ClassName(1),

                              ##class(NoProduction).%ClassName(1)                  :503,

                              ##class(NotImplemented).%ClassName(1)        :501,

                                                                                                                 :500)

}

 

classmethod dumpResponse(responseObject) {

        if $isObject(responseObject) {

               if responseObject.%Extends(##class(%DynamicObject).%ClassName(1)) { write responseObject.%ToJSON() }

               elseif responseObject.%Extends(##class(%ZEN.proxyObject).%ClassName(1)) {

                       do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(responseObject)

               }

               elseif responseObject.%Extends(##class(%XML.Adaptor).%ClassName(1)) {

                       do responseObject.XMLExportToString(.ret)

                       write ret

               }

               else { throw ##class(Serialization).%New() }

        }

        else {

               write responseObject

        }      

}

 

classmethod getQueryParameters(parameterList) as %DynamicObject {

        set parameterObject = {}

        for i=1:1:$listlength(parameterList) {

               set parameterName=$listget(parameterList,i)

               set $property(parameterObject, parameterName) = %request.Get(parameterName)

        }

        quit parameterObject

}

 

classmethod getFormParameters(parameterList,queryObject) as %DynamicObject {

        if $data(queryObject) { set parameterObject = queryObject }

        else { set parameterObject = {} }

        for i=1:1:$listlength(parameterList) {

               set parameterName=$listget(parameterList,i)

               set $property(parameterObject, parameterName) = %request.Get(parameterName)

        }

        quit parameterObject

}

 

classmethod getContentParameter() as %DynamicObject {

        quit {}.%FromJSON(%request.Content)

}

 

到此为止。 我们完成了一个 RESTful Web 服务 API 的设计(?)、实现和文档编制。

敬请关注,我很快会回来进一步解读 Ensemble RESTful Web 服务。 下一个主题是“使用 RESTful Web 服务创建 Ensemble 微服务”。

0
0 184
文章 Nicky Zhu · 一月 8, 2021 3m read

我打算基于实例中的数据实现业务智能。 怎样才是设置数据库和环境来使用 DeepSee 的最佳方法呢?

本教程通过 3 个 DeepSee 架构示例来解决此问题。 首先,我们从基本架构模型开始,并重点说明其局限性。 对于复杂程度中等的业务智能应用,建议使用下一个模型,对于大多数用例而言,该模型应该足矣。 在本教程的最后,我们将说明如何增强架构的灵活性以管理高级实现。

本教程中的每个示例都介绍了新的数据库和全局映射,并讨论了为何以及何时设置它们。 在构建架构时,则重点说明更灵活的示例提供的好处。

开始前

主服务器和分析服务器

为了使数据高度可用,InterSystems 通常建议使用镜像或映射,并将 DeepSee 实现基于镜像/映射服务器。 承载数据原始副本的机器称为“主服务器”,而承载数据副本和业务智能应用程序的计算机通常称为“分析服务器”(有时称为“报告服务器”)。

拥有主服务器和分析服务器至关重要,主要原因是避免任一台服务器出现性能问题。 请查阅有关推荐架构的文档。

数据和应用程序代码

通常,将源数据和代码存储在同一数据库中仅对小型应用程序有效。 对于更大型的应用程序,建议将源数据和代码存储在两个专用数据库中,这样您就可以与运行 DeepSee 的所有命名空间共享代码,同时保持数据分离。 源数据的数据库应从生产服务器镜像。 该数据库可以为只读,也可为读写。 建议为此数据库持续启用日志功能。

源类和自定义应用程序应存储在生产和分析服务器上的专用数据库中。 请注意,这两个用于源代码的数据库不需要同步,甚至不需要运行相同的 Caché 版本。 只要定期将代码备份到其他地方,通常就不需要日志。

在本教程中,我们采用以下配置。 分析服务器上的 APP 命名空间有 APP-DATA 和 APP-CODE 作为默认数据库。 APP-DATA 数据库可以访问主服务器上的源数据数据库中的数据(源表类及其事实数据)。 APP-CODE 数据库存储 Caché 代码(.cls 和 .INT 文件)以及其他自定义代码。 数据和代码的这种分离是一种典型的架构,这允许用户,例如,有效地部署 DeepSee 代码和自定义应用程序。

在不同的命名空间上运行 DeepSee

使用 DeepSee 的业务智能实现通常在不同的命名空间中运行。 在本文中,我们将说明如何设置单个的 APP 命名空间,但是相同的过程适用于运行业务智能应用程序的所有名称空间。

文档

建议熟悉文档页面执行初始设置。 该页面的内容包括:设置 Web 应用程序,如何将 DeepSee 全局变量放置在单独的数据库中,以及 Deepeep 全局变量的替代映射列表。


在本系列的第二部分中,我们将阐述基本架构模型的实现

0
0 331