本章描述了如何通过%CSP.REST
的子类来手动创建InterSystems IRIS®REST服务;此过程创建了一个手动编码的REST服务,该服务不能与所有API管理工具一起使用。
另请参阅“保护REST服务”中的“为REST服务设置身份验证”
10.1 手动创建REST服务的基础知识
要手动定义REST服务,请执行以下操作:
-
创建一个REST服务类-
%CSP.REST
的子类。在子类中:- 定义一个URL映射,该映射指定为
REST URL和HTTP方法
执行的InterSystems IRIS方法。 - (可选)指定
UseSession
参数。此参数控制每个REST调用是在其自己的web会话下执行,还是与其他REST调用共享单个会话。 - 可选地,重写错误处理方法。
如果要将实现代码与分派代码分开,可以在单独的类中定义实现REST服务的方法,并从URL映射中调用这些方法。
- 定义一个URL映射,该映射指定为
-
定义一个使用REST服务类作为其分派类的web应用程序。
要定义web应用程序及其安全性,请转到Web Application页面(System Administration > Security > Applications > Web Applications)。
定义web应用程序时,将
DispatchClass
设置为REST服务类的名称。此外,将应用程序的名称指定为REST调用URL的第一部分。示例名称为
/csp/mynamespace
或/csp/myapp
,但您可以指定URL中允许的任何文本。
您可以在命名空间中定义多个REST服务类。每个具有自己入口点的REST服务类都必须有自己的web应用程序。
10.2 创建URL映射
在REST服务类中,定义一个名为UrlMap
的XData块,该块将REST调用与实现服务的方法相关联。它可以根据URL的内容直接将调用发送给方法,也可以根据URL将调用转发给另一个REST服务类。如果web应用程序正在处理少量相关服务,则可以将调用直接发送给实现它的方法。但是,如果web应用正在处理大量不同的服务,则可定义单独的REST服务类,每个类处理一组相关服务。然后将web应用程序配置为使用一个中央REST服务类,该类将REST调用转发到其他适当的REST服务类。
如果%CSP.REST
子类直接向方法发送调用,则URLMap
包含一个包含一系列<Route>
元素的<Routes>
定义。每个<Route>
元素指定要为指定的URL和HTTP操作调用的类方法。REST通常使用GET、POST、PUT或DELETE操作,但您可以指定任何HTTP操作。URL可以选择包含作为REST URL的一部分指定的参数,并作为参数传递给指定的方法。
如果%CSP.REST
子类正在将调用转发到%CSP.REST
的其他子类,则UrlMap
包含包含一系列<Map>
元素的<Routes>
定义。<Map>
元素将具有指定前缀的所有调用转发到另一个REST服务类,然后该类将实现该行为。它可以通过将调用直接发送到方法或将其转发到另一个子类来实现该行为。
重要:
InterSystems IRIS将传入的
REST URL
与每个<Route>
的URL属性和每个<Map>
的Prefix属性进行比较,从URL映射中的第一项开始,并在使用相同HTTP请求方法的第一个可能匹配处停止。因此,<Routes>
中元素的顺序很重要。如果传入的URL可以匹配URL映射的多个元素,则InterSystems IRIS使用第一个匹配元素,并忽略任何后续可能的匹配
10.2.1 URLMap <Route>
元素
InterSystems IRIS将传入的URL和HTTP请求方法与URL映射中的每个<Route>
元素进行比较。它调用第一个匹配的<Route>
元素中指定的方法。<Route>
元素有三部分:
Url
— 指定调用REST服务的REST Url的最后一部分的格式。Url由文本元素和以":"(冒号)开头的参数组成。Method
— 指定REST调用的HTTP请求方法:通常是GET、POST、PUT或DELETE,但可以使用任何HTTP请求方法。您应该选择适合服务执行的函数的请求方法,但%CSP.REST不会对不同的方法执行任何特殊处理。您应该以所有大写字母指定HTTP请求方法。Call
— 指定要调用以执行REST服务的类方法。默认情况下,该类方法在REST服务类中定义,但您可以显式指定任何类方法。
例如,考虑以下<Route>
:
<Route Url="/echo" Method="POST" Call="Echo" Cors="false" />
这指定REST调用将以/echo
结束,并使用POST
方法。它将调用定义REST服务的REST.DocServer
类中的Echo
类方法。Cors属性是可选的;有关详细信息,请参阅“修改REST服务以使用CORS”。
完整的REST URL由以下部分组成:
- InterSystems IRIS服务器的服务器名称和端口。在本章中,服务器名称和端口
http://localhost:52773/
在示例中使用。 - Web Application页面上定义的web应用程序的名称(单击System Administration > Security > Applications > Web Applications)。(例如,
/csp/samples/docserver
) <Route>
元素的Url属性。如果Url属性的段前面有":"(冒号),则表示参数。参数将匹配该URL段中的任何值。此值作为参数传递给方法。
对于前面的示例,TCP跟踪实用程序显示的完整REST调用是:
POST /csp/samples/docserver/echo HTTP/1.1
Host: localhost:52773
如果要在分派代码中将实现REST服务的代码与%CSP.REST
分开,可以在另一个类中定义实现REST服务方法,并在Call元素中指定类和方法。
10.2.1.1 指定参数
以下<Route>
定义在URL中定义了两个参数,命名空间和类:
<Route Url="/class/:namespace/:classname" Method="GET" Call="GetClass" />
REST调用URL以/csp/samples/docserver/class/
开头,URL的下两个元素指定这两个参数。GetClass()
方法使用这些参数作为要查询的命名空间和类名。例如,考虑此REST调用:
http://localhost:52773/csp/samples/docserver/class/samples/Cinema.Review
此REST调用GetClass()
方法,并传递字符串“samples
”和“Cinema.Review
”作为参数值。GetClass()
方法具有以下签名:
/// This method returns the class text for the named class
ClassMethod GetClass(pNamespace As %String,
pClassname As %String) As %Status
{
10.2.1.2 为单个URL指定多个路由
对于给定的URL,您可以支持不同的HTTP请求方法。您可以为每个HTTP请求定义单独的ObjectScript方法,也可以使用检查请求的单个ObjectScript方法。
以下示例对单个URL的每个HTTP请求方法使用不同的方法:
<Route Url="/request" Method="GET" Call="GetRequest" />
<Route Url="/request" Method="POST" Call="PostRequest" />
使用这些路由,如果使用HTTPGET方法调用URL/csp/samples/docserver/request
,则会调用GetRequest()
方法。如果使用HTTP POST方法调用它,则会调用PostRequest()
方法。
相反,您可以使用以下<Route>
定义:
<Route Url="/request" Method="GET" Call="Request" />
<Route Url="/request" Method="POST" Call="Request" />
在这种情况下,Request()
方法处理GET或POST操作的调用。该方法检查%request对象,该对象是%CSP.Request
的实例。在此对象中,URL属性包含URL的文本。
10.2.1.3 路由映射的正则表达式
可以在路由映射中使用正则表达式。InterSystems建议,只有在没有其他方法来定义REST服务以满足您的需求时,才能这样做。本节提供了详细信息。(有关ObjectScript中正则表达式的信息,请参见使用ObjectScript中的“正则表达式”。)
在内部,用于在URL中定义参数的:parameter-name
语法是使用正则表达式实现的。指定为:parameter-name
的每个段都将转换为包含重复匹配组的正则表达式,特别是([^/]+)
正则表达式。此语法匹配任何字符串(非零长度),只要该字符串不包含/(斜杠)字符。因此,GetClass()
示例Url="/class/:namespace/:classname"
相当于:
<Route Url="/class/([^/]+)/([^/]+)" Method="GET" Call="GetClass" />
其中存在指定两个参数的两个匹配组。
在大多数情况下,这种格式提供了足够的灵活性来指定RESTURL,但是高级用户可以直接在路由定义中使用正则表达式格式。URL必须与正则表达式匹配,并且由一对括号指定的每个匹配组都定义了要传递给该方法的参数。
例如,考虑以下路由映射:
<Routes>
<Route Url="/Move/:direction" Method="GET" Call="Move" />
<Route Url="/Move2/(east|west|north|south)" Method="GET" Call="Move" />
</Routes>
对于第一条路由,参数可以具有任何值。无论参数的值是多少,都会调用Move()
方法。对于第二条路由,参数必须是东西北或南;如果使用其他参数值调用第二个路由,则不会调用Move()
方法,REST服务将返回404错误,因为找不到资源。
这个简单的示例只是为了演示常用参数语法和正则表达式之间的区别。在这里讨论的情况下,不需要正则表达式,因为Move()
方法可以(并且应该)检查参数的值并做出适当的响应。但是,在以下情况下,正则表达式很有用:
-
如果参数是可选的。在这种情况下,请使用正则表达式
([^/]\*)
而不是:parameter-name
语法。例如:<Route Url="/Test3/([^/]*)" Method="GET" Call="Test"/>
当然,被调用的方法还必须能够处理参数的空值。
-
如果参数是最后一个参数,并且其值可以包含斜线。在这种情况下,如果需要参数,请使用正则表达式
((?s).*)
而不是:parameter-name
语法。例如:<Route Url="/Test4/((?s).+)" Method="GET" Call="Test"/>
或者,如果此参数是可选的,请使用正则表达式
((?s).*)
而不是:parameter-name
语法。例如:<Route Url="/Test5/((?s).*)" Method="GET" Call="Test"/>
10.2.2 URLMap <Map>
元素
InterSystems IRIS将传入的URL与URL映射中每个<Map>
元素中的前缀进行比较。它将传入的REST调用转发到第一个匹配的<Map>
元素中指定的REST服务类。该类处理URL的其余部分,通常调用实现服务的方法。<Map>
元素有两个属性:
- Prefix — 指定要匹配的URL段。传入URL通常在匹配段之后具有其他段。
- Forward — 指定将处理匹配段后面的URL段的另一个REST服务类。
考虑以下包含三个<Map>
元素的URLMap
。
XData UrlMap
{
<Routes>
<Map Prefix="/coffee/sales" Forward="MyLib.coffee.SalesREST"/>
<Map Prefix="/coffee/repairs" Forward="MyLib.coffee.RepairsREST"/>
<Map Prefix="/coffee" Forward="MyLib.coffee.MiscREST"/>
</Routes>
}
此UrlMap
将REST调用转发到三个REST服务类之一:MyLib.coffee.SalesREST
、MyLib.coffee.RepairsREST
或MyLib.cofee.MiscREST
。
调用这些REST服务之一的完整REST URL由以下部分组成:
-
InterSystems IRIS服务器的服务器名称和端口,例如
http://localhost:52773/
-
Web Application
页面上定义的web应用程序的名称(单击System Administration > Security > Applications > Web Applications)。例如,这些REST调用的web应用程序可以命名为/coffeeRESTSvr
-
<Map>
元素的前缀。 -
REST URL的其余部分。这是接收转发的REST请求的REST服务类将处理的URL。
例如,以下REST调用:
http://localhost:52773/coffeeRESTSvr/coffee/sales/reports/id/875
将第一个<Map>
与前缀/coffee/sales
匹配,并将REST调用转发到MyLib.coffee.SalesREST
类。该类将查找URL剩余部分“/reports/id/875
”的匹配项。
作为另一个示例,以下REST调用:
http://localhost:52773/coffeeRESTSvr/coffee/inventory/machinetype/drip
将第三个<Map>
与前缀/coffee
匹配,并将REST调用转发到MyLib.coffee.MiscREST
类。该类将查找URL其余部分的匹配项,“/inventory/machinetype/drop
”。
注:
在这个
URLMap
示例中,如果前缀为“/coffee
”的<Map>
是第一个映射,那么所有带有/coffee
的REST调用都将被转发到MyLib.coffee.MiscREST
类,即使它们匹配以下<Map>
元素之一。<Routes>
中<Map>
元素的顺序很重要。
10.3 指定数据格式
您可以定义REST服务来处理不同格式的数据,例如JSON、XML、文本或CSV。REST调用可以通过在HTTP请求中指定ContentType元素来指定它希望发送的数据的形式,也可以通过在HTTP请求中指定Accept元素来请求返回数据格式。
在DocServer
示例中,GetNamespaces()
方法检查REST调用是否请求了以下JSON数据:
If $Get(%request.CgiEnvs("HTTP_ACCEPT"))="application/json"
10.4 本地化REST服务
REST服务返回的任何字符串值都可以本地化,以便服务器以不同的语言存储字符串的多个版本。然后,当服务接收到包含HTTP Accept Language
标头的HTTP请求时,服务将使用字符串的适当版本进行响应。
要本地化REST服务,请执行以下操作:
-
在实现代码中,不要包含硬编码的文本字符串,而是使用
$$Text
宏的实例,为宏参数提供如下值:-
默认字符串
-
(可选)此字符串所属的域(当字符串分组到域中时,本地化更易于管理)
-
(可选)默认字符串的语言代码
例如,不是这样:
set returnvalue="Hello world"
包括以下内容:
set returnvalue=$$$TEXT("Hello world","sampledomain","en-us")
-
-
如果省略
$$Text
宏的域参数,请在REST服务类中包含domain
类参数。例如:Parameter DOMAIN = "sampledomain"
-
编译代码。这样做时,编译器会在消息字典中为
$$Text
宏的每个唯一实例生成条目。消息字典是全局的,因此可以在管理门户中轻松查看(例如)。有一些类方法可以帮助完成常见任务。
-
开发完成后,导出该域或所有域的消息字典。
结果是一个或多个包含原始语言文本字符串的XML消息文件。
-
将这些文件发送给翻译人员,请求翻译版本。
-
当您收到翻译后的XML消息文件时,将它们导入到导出原始文件的同一命名空间中。
翻译文本和原文在信息字典中共存。
-
在运行时,REST服务根据
HTTP Accept Language
标头选择要返回的文本。
有关更多信息,请参阅文章字符串本地化和消息字典。
10.5 将Web会话与REST结合使用
有关介绍,请参阅本书前面的“将Web会话用于REST”。
要使REST服务在多个REST调用上使用单个web会话,请在REST服务类中将UseSession
参数设置为1:
Parameter UseSession As Integer = 1;
10.6 支持CORS
有关简介,请参阅本书前面的“CORS简介”。请注意,如本附录所述,手动创建web服务时,支持CORS的细节略有不同。
10.6.1 修改REST服务以使用CORS
要指定REST服务支持CORS,请按如下方式修改REST服务类,然后重新编译它。
-
指定
HandleCorsRequest
参数的值。要为所有调用启用CORS标头处理,请将
HandleCorsRequest
参数指定为1:Parameter HandleCorsRequest = 1;
或者,要为某些调用而不是调用启用CORS标头处理,请将
HandleCorsRequest
参数指定为""(空字符串):Parameter HandleCorsRequest = "";
(如果
HandleCorsRequest
为0,则对所有调用禁用CORS标头处理。在这种情况下,如果REST服务接收到带有CORS标头的请求,则该服务拒绝该请求。这是默认值。) -
如果将
HandleCorsRequest
参数指定为"",请编辑URLMapXData
块以指示哪些调用支持CORS。具体而言,对于任何应支持CORS的<Route>
,请添加以下属性名称和值:Cors="true"
或者在
<Route>
元素中指定Cors=“false”
以禁用Cors处理。
如果REST服务类将REST请求转发给另一个REST服务类,则CORS处理的行为由包含与给定请求匹配的<Route>
元素的类确定。
10.6.2 覆盖CORS标头处理
重要:
默认的CORS标头处理不适用于处理机密数据的REST服务。
默认的CORS头处理不进行任何过滤,只是将CORS头传递给外部服务器并返回响应。您可能希望限制对域允许列表中源的访问,或限制允许的请求方法。您可以通过重写REST服务类中的OnHandleCorsRequest()
方法来实现这一点。
有关实现OnHandleCorsRequest()
方法的信息,请参阅本书前面的“定义OnHandleCorsRequest()
”。
注意,所有与UrlMap
中的<Route>
元素匹配的URL请求都使用类中定义的单个OnHandleCorsRequest()
方法处理。如果对于不同的RESTURL请求需要不同的OnHandleCorsRequest()
方法实现,则应使用Forward
将请求发送到其他REST服务类。
10.7 变体:访问查询参数
向REST服务传递参数的推荐方法是将它们作为用于调用服务的URL路径的一部分传递(例如,/myapi/someresource/parametervalue
)。然而,在某些情况下,将参数作为查询参数传递可能更方便(例如,/myapi/someresource?parameter=value
)。在这种情况下,可以使用%request
变量来检索参数值。在REST服务中,%request
变量是%CSP.Request
的一个实例,它位于保存整个URL查询。要检索给定查询参数的值,请使用以下语法:
$GET(%request.Data(name,1),default)
其中name
是查询参数的名称,default
是要返回的默认值。或者,如果同一URL包含同一查询参数的多个副本,请使用以下语法:
$GET(%request.Data(name,index),default)
其中index是要检索的副本的数字索引。有关详细信息,请参阅%CSP.REST
的类引用。
10.8 示例:Hello World!
以下代码片段代表REST服务的一个极其简单的示例。有三个类:helloWorld.disp
、helloWorld.impl
和helloWorld.hwObj
。虽然helloWorld.disp
和helloWorld.impl
扩展%CSP.REST
以建立REST服务,但helloWorld.hwObj
扩展%Persistent
,和%JSON.Adaptor
,这在使用POST方法创建对象时非常有用。
Class helloWorld.disp Extends %CSP.REST
{
Parameter HandleCorsRequest = 0;
XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/hello" Method="GET" Call="Hello" />
<Route Url="/hello" Method="POST" Call="PostHello" />
</Routes>
}
ClassMethod Hello() As %Status
{
Try {
Do ##class(%REST.Impl).%SetContentType("application/json")
If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
Set response=##class(helloWorld.impl).Hello()
Do ##class(%REST.Impl).%WriteResponse(response)
} Catch (ex) {
Do ##class(%REST.Impl).%SetStatusCode("400")
return {"errormessage": "Client error"}
}
Quit $$$OK
}
ClassMethod PostHello() As %Status
{
Try {
Do ##class(%REST.Impl).%SetContentType("application/json")
If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
Set response=##class(helloWorld.impl).PostHello()
Do ##class(%REST.Impl).%WriteResponse(response)
} Catch (ex) {
Do ##class(%REST.Impl).%SetStatusCode("400")
return {"errormessage": "Client error"}
}
Quit $$$OK
}
请注意上述分派类的结构:URLMap
定义了不同方法的不同端点,并且存在将这些路由分派到实现类的类方法。在此过程中,该类执行一些错误处理。阅读有关文档中%REST.Impl
中报告错误和设置状态代码的详细信息。作为POST或PUT方法端点的类方法需要参数中使用特殊的%request
,并将所述参数中的信息传递给实现方法。
Class helloWorld.impl Extends %CSP.REST
{
ClassMethod Hello() As %DynamicObject
{
Try {
return {"Hello":"World"}.%ToJSON()
} Catch (ex) {
Do ##class(%REST.Impl).%SetStatusCode("500")
return {"errormessage": "Server error"}
}
}
ClassMethod PostHello(body As %DynamicObject) As %DynamicObject
{
Try {
set temp = ##class(helloWorld.hwobj).%New()
Do temp.%JSONImport(body)
Do temp.%Save()
Do temp.%JSONExportToString(.ret)
return ret
} Catch (ex) {
Do ##class(%REST.Impl).%SetStatusCode("500")
return {"errormessage": "Server error"}
}
}
}
上面的示例实现类有两个方法,一个简单地返回一个JSON格式的“HelloWorld”消息,另一个基于一些输入创建一个对象,然后返回所述对象的内容。有关分别使用%new()
和%JSONImport()
等方法的详细信息,请参阅%Persistent
和%JSON.Adapter
的文档。
尽管helloWorld.hwobj
类可以有许多属性,但为了简化本例,它只有一个属性:
Class helloWorld.hwobj Extends (%Persistent, %JSON.Adaptor)
{
Property Hello As %String;
Storage Default
{
<Data name="hwobjDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Hello</Value>
</Value>
</Data>
<DataLocation>^helloWorld.hwobjD</DataLocation>
<DefaultData>hwobjDefaultData</DefaultData>
<IdLocation>^helloWorld.hwobjD</IdLocation>
<IndexLocation>^helloWorld.hwobjI</IndexLocation>
<StreamLocation>^helloWorld.hwobjS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
}
按照“创建REST服务的基础知识”中的说明在管理门户中配置Web应用程序后,手动使用Postman
或任何其他REST客户端向Web应用程序发送请求并查看其响应。