Article
· Feb 14, 2023 9m read

Generate client SOAP and REST

Hi Community,

I would like to take advantage of our topic on capture for Health Data Warehouses (on DC-FR) to show you how to quickly create HTTP SOAP and REST clients. IRIS, as well as applications available on Open Exchange offers solutions to generate them from a WSDL or a swagger specification.

SOAP client

Nothing could be easier than creating a SOAP client. All you need is the WSDL. A wizard is available from the IRIS Studio. It allows you to generate not only your classes for a web service client but also the “Business Services” and “Business Operations” if you want to consume them with the interoperability framework.

 

 

Simply provide the URL (or path to the file) by pointing to the WSDL.

If the WSDL URL uses HTTPS protocol, you must select an SSL Configuration.  If you don’t have SSL configuration yet on your instance, you can create it with this line and retry:

set $namespace="%SYS", name="default" do:'##class(Security.SSLConfigs).Exists(name) 

 

 

In the next window, you will have the choice of generating not only a simple web service client but also the classes dedicated to the interoperability framework. To do that, check "Business Operation". You can then use it in your productions.

 

Click on "Next" and get your client generated.

 

If you are a Visual Studio user, you can generate the SOAP client with the terminal: 

set ns = $namespace, $namespace="%SYS", name="default" do:'##class(Security.SSLConfigs).Exists(name) ##class(Security.SSLConfigs).Create(name) 
set $NAMESPACE = ns
Set wsdlReader = ##class(%SOAP.WSDL.Reader).%New() 
Set url = "https://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL"
Set wsdlReader.SSLConfiguration = name; 
Set wsdlReader.MakeEnsembleClasses = 1 ; if you want generate BO
Set sc = wsdlReader.Process(url, "MySoapClient")

 

This web service contains a method to convert a number to a word “NumberToWords”.  It can be invoked by using “MySoapClient.NumberConversionSoap” class in the way stated below:

Set soapClient = ##class(MySoapClient.NumberConversionSoap).%New()
Set soapClient.SSLConfiguration = "default"
Set value = soapClient.NumberToWords(3)
Write value

 

Client REST Production

If you have the swagger 2.0 specification corresponding to the REST services you wish to consume, it is possible to generate a production client with the openapi-client-gen package available on OpenExchange.

You can install this package via the followin ZPM command.

 

zpm "install openapi-client-gen"

In case ZPM is not yet available on your instance, you can install it with just one command line:

set $namespace="%SYS", name="default" do:'##class(Security.SSLConfigs).Exists(name) ##class(Security.SSLConfigs).Create(name) set url="https://pm.community.intersystems.com/packages/zpm/latest/installer" Do ##class(%Net.URLParser).Parse(url,.comp) set ht = ##class(%Net.HttpRequest).%New(), ht.Server = comp("host"), ht.Port = 443, ht.Https=1, ht.SSLConfiguration=name, st=ht.Get(comp("path")) quit:'st $System.Status.GetErrorText(st) set xml=##class(%File).TempFilename("xml"), tFile = ##class(%Stream.FileBinary).%New(), tFile.Filename = xml do tFile.CopyFromAndSave(ht.HttpResponse.Data) do ht.%Close(), $system.OBJ.Load(xml,"ck") do ##class(%File).Delete(xml)

Next, you need to define a default SSL configuration.

In the examples presented in this article, the requests will always be made in HTTPS. The SSL configuration is therefore mandatory.

set $namespace="%SYS", name="default" do:'##class(Security.SSLConfigs).Exists(name) ##class(Security.SSLConfigs).Create(name)

For our example, the REST services available at petstore.swagger.io were used. The following command generates the REST production client by passing the URL of the swagger 2.0 specification as the second parameter. However, it is not mandatory to use a URL. The second parameter could also be a path to a file containing the specification in JSON or YAML format.

Set sc = ##class(dc.openapi.client.Spec).generateApp("petstore", "https://petstore.swagger.io/v2/swagger.json")

The “petshop” production is now generated and accessible in the administration portal, where you can find the Business Services, Processes, and Business Operation.

 

Once production has started, the services can be used. For example, to retrieve the list of animals, use the "FindPetsByStatusService".

Here is the code allowing you to retrieve it from the pet store in a synchronous way:

 

Set ensRequest = ##class(petshop.requests.FindPetsByStatusRequest).%New()
Set ensRequest.accept = "application/json"
Set ensRequest.querystatus = "available"
Set sc = ##class(petshop.Utils).invokeHostSync("petstore.bp.SyncProcess", ensRequest, "petstore.bs.ProxyService", , .pResponse)

To display the indented JSON response in the terminal, use the following line of code:

Do ##class(%JSON.Formatter).%New().Format({}.%FromJSON(pResponse.body))

In the "Visual Trace" (available via the administration portal), it is possible to observe all messages exchanged between different services, processes, and operations layers.

 

This is how, in just a few lines of code, it is possible to create a ready-to-use REST production client.

REST client

Of course, it is also possible to generate an HTTP client without using the interoperability framework. The openapi-client-gen package also allows you to do it. Just remember to add the necessary parameter at the time of generation:

 

Set features("simpleHttpClientOnly") = 1
Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreclient", "https://petstore3.swagger.io/api/v3/openapi.json", .features)

Once the operation is completed, the "petshopclient.HttpClient" class becomes available.  

This class contains all the methods to make HTTP requests for all the services defined in the specification. Here is an extract from this class: 

Class petstoreclient.HttpClient Extends %RegisteredObject [ ProcedureBlock ]
{

Parameter SERVER = "https://petstore3.swagger.io/api/v3";
Parameter SSLCONFIGURATION = "DefaultSSL";
Property HttpRequest [ InitialExpression = {##class(%Net.HttpRequest).%New()} ];
Property SSLConfiguration As %String [ InitialExpression = {..#SSLCONFIGURATION} ];
Property Server As %String [ InitialExpression = {..#SERVER} ];
Property URLComponents [ MultiDimensional ];
Method %OnNew(Server As %String, SSLConfiguration As %String) As %Status
{
	Set:$Data(Server) ..Server = Server
	Set:$Data(SSLConfiguration) ..SSLConfiguration = SSLConfiguration
	Quit ..InitializeHttpRequestObject()
}

Method InitializeHttpRequestObject() As %Status
{
	Set ..HttpRequest = ##class(%Net.HttpRequest).%New()
	Do ##class(%Net.URLParser).Decompose(..Server, .components)
	Set:$Data(components("host"), host) ..HttpRequest.Server = host
	Set:$Data(components("port"), port) ..HttpRequest.Port = port
	Set:$$$LOWER($Get(components("scheme")))="https" ..HttpRequest.Https = $$$YES, ..HttpRequest.SSLConfiguration = ..SSLConfiguration
	Merge:$Data(components) ..URLComponents = components
	Quit $$$OK
}
...
/// Implement operationId : findPetsByStatus
/// get /pet/findByStatus
Method findPetsByStatus(requestMessage As petstoreclient.requests.findPetsByStatus, Output responseMessage As petstoreclient.responses.findPetsByStatus = {##class(petstoreclient.responses.findPetsByStatus).%New()}) As %Status
{
	Set sc = $$$OK
	$$$QuitOnError(requestMessage.LoadHttpRequestObject(..HttpRequest))
	$$$QuitOnError(..HttpRequest.Send("GET", $Get(..URLComponents("path")) _ requestMessage.%URL))
	$$$QuitOnError(responseMessage.LoadFromResponse(..HttpRequest.HttpResponse, "findPetsByStatus"))
	Quit sc
}
...
}
}

 

In this case, the retrieval of the list of available animals will be done in a slightly different way. It is a bit similar to using Business Operation directly:

Set requestMsg = ##class(petstoreclient.requests.findPetsByStatus).%New()
Set requestMsg.querystatus = "available"
Set httpClient = ##class(petstoreclient.HttpClient).%New("https://petstore3.swagger.io/api/v3","DefaultSSL")
Set sc = httpClient.GETFindPetsByStatus(requestMsg, .responseMsg)
Do ##class(%JSON.Formatter).%New().Format({}.%FromJSON(responseMsg.body))

Just like in the example with the interoperability framework, the response message contains a body property which is the raw content of the HTTP response.  

Looking at the "responseMsg" object, we can notice that it contains a "parsedResponse" property which in this case belongs to the type "petshopclient.msg.FindPetsByStatusResponse". It contains the serialized response in a persistent object model and can be used as storage for future transformation

HTTP request

So far, the presented case is perfect, because a swagger specification is available and allows us to generate a lot of code. This is obviously a great advantage. However, for some services, no specification may be available (e.g. connection to an old application which is no longer maintained).

In this case, it is necessary to write the code to make the HTTP requests manually. Here is how to retrieve the list of animals available on the pet store by writing the code for the http request manually: 

Set httprequest=##class(%Net.HttpRequest).%New()
Set httprequest.Https=1
Set httprequest.SSLConfiguration="default"
Set httprequest.Server="petstore.swagger.io"
Do httprequest.SetHeader("HTTP_ACCEPT","application/json")
Do httprequest.InsertParam("status","available")
Do httprequest.Get("/v2/pet/findByStatus")
Set availablePets = {}.%FromJSON(httprequest.HttpResponse.Data)
Set jsonObject = {}.%FromJSON(httprequest.HttpResponse.Data)
Do ##class(%JSON.Formatter).%New().Format(jsonObject)

 

 

Of course, this time there is no serialization of the response into a persistent object model. Yet, the ObjectScript language natively supports JSON thanks to the %DynamicObject that allows easy manipulation of the responses.

In the previous example, it was a simple HTTP GET request, but it can be more complex with a JSON POST or file upload. A little tip: don't hesitate to generate a client from a public specification like pet store in order to have the generated code as an example. It can save your time otherwise wasted on finding the key classes and their uses (%Net.MIMEPart, %Net.MIMEWriter).

Below there are two examples for your information.

Example of a POST request with a JSON message in the body: 

Set sc = $$$OK, pURL = "/v2/pet"
Set pHttpRequestIn=##class(%Net.HttpRequest).%New()
Set pHttpRequestIn.Https=1
Set pHttpRequestIn.SSLConfiguration="default"
Set pHttpRequestIn.Server="petstore.swagger.io"
Set pHttpRequestIn.ContentType = pRequest.consume
Do pHttpRequestIn.SetHeader("HTTP_ACCEPT", "application/json")
Set pet = {
  "id": 0,
  "category": {
"id": 0,
"name": "string"
  },
  "name": "doggie",
  "photoUrls": [
"string"
  ],
  "tags": [
{
  "id": 0,
  "name": "string"
}
  ],
  "status": "available"
}
Do pHttpRequestIn.EntityBody.Write(pet.%ToJSON())
Set sc = pHttpRequestIn.Send("POST", pURL)

 

Example of a POST request to upload a file:

Set sc = $$$OK, pURL = "/v2/pet/{petId}/uploadImage"
Set pHttpRequestIn=##class(%Net.HttpRequest).%New()
Set pHttpRequestIn.Https=1
Set pHttpRequestIn.SSLConfiguration="default"
Set pHttpRequestIn.Server="petstore.swagger.io"
Set petId = 1
Set pHttpRequestIn.ContentType = "multipart/form-data"
Set pURL = $Replace(pURL, "{petId}", petId)
Set fileToUpload = ##class(%Stream.FileBinary).%New()
Set sc=fileToUpload.LinkToFile("/file/path/to_upload.png")
Set valueStream = ##class(%Stream.GlobalBinary).%New()
Do valueStream.CopyFrom(fileToUpload)
Set mParts = ##class(%Net.MIMEPart).%New()
Set mimePart = ##class(%Net.MIMEPart).%New(valueStream)
Do mimePart.SetHeader("Content-Disposition", "form-data; name=""file""; filename=""file""")
Do mParts.Parts.Insert(mimePart)
Set mimeWriter = ##class(%Net.MIMEWriter).%New()
Do mimeWriter.OutputToStream(.stream)
Do mimeWriter.WriteMIMEBody(mParts)
Set pHttpRequestIn.EntityBody = stream
Set pHttpRequestIn.ContentType = "multipart/form-data; boundary=" _ mParts.Boundary
Set sc = pHttpRequestIn.Send("POST", pURL)

 

That's it for this article. I hope it will make it easy for you to learn about capturing data via HTTP.

Discussion (4)2
Log in or sign up to continue