Service interfaces
The HeartAI system implements a variety of service endpoints as internal and external interfaces. Internal service interfaces are discoverable from hosts within the HeartAI domain and are deployed across internal virtual subnetworks. External service interfaces are discoverable from hosts within the HeartAI domain and from resolvable private network extension. The HeartAI network does not expose service endpoints to the public internet - All network resolution occurs internal to the HeartAI network or through private network extension.
Please refer to the following documentation section for an overview of system network architecture.
Please refer to the following documentation sections
for an overview of these approaches.
Service descriptors
Lagom provides abstractions for specifying service interface endpoints through the use of service descriptors. The Lagom ServiceDescriptor trait allows the specification of service endpoint resource pathing and provides automated methods for generating an access control list.
The ServiceDescriptor
implementation for the example HelloWorldService
is shown following:
override final def descriptor: Descriptor = {
import Service._
named("hello-world")
.withCalls(
restCall(Method.GET, "/hello/api/public/ping", pingService()),
restCall(Method.POST, "/hello/api/public/ping", pingServiceByPOST()),
restCall(Method.GET, "/hello/api/public/ping_ws_count", pingServiceByWebSocketCount),
restCall(Method.GET, "/hello/api/public/ping_ws_echo", pingServiceByWebSocketEcho),
restCall(Method.GET, "/hello/api/public/hello/:id", this.helloPublic _),
restCall(Method.GET, "/hello/api/secure/hello/:id", this.helloSecure _),
restCall(Method.POST, "/hello/api/secure/greeting/:id", this.updateGreetingMessage _))
.withTopics(
topic(HelloWorldServiceAPI.GREETING_MESSAGES_CHANGED_TOPIC, greetingUpdatedTopic _)
.addProperty(
KafkaProperties.partitionKeyStrategy,
PartitionKeyStrategy[Greeting](_.id)))
.withAutoAcl(true)
}
In the production environment, these resource paths are mapped to OpenShift Routes, and are exposable internally to the HeartAI domain or through private network extension. For the HelloWorldService
production environment deployment, this Route is exposed as:
https://hello.prod.apps.aro.sah.heartai.net/[port]:[resource]
For example,
https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping
where the port is implied as port 443
and the resource is hello/api/public/ping
.
Example: HeartAI Hello World service - Ping service endpoint
The following example command will request with HTTP GET at the pingService()
service endpoint of the HeartAI HelloWorldService
production environment:
Request
curl -i https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping
Response
HTTP/1.1 200 OK
Date: Mon, 12 Jul 2021 02:24:27 GMT
Content-Type: application/json
Content-Length: 217
Set-Cookie: 5986eb34c84b3f6448c727496d958b60=822550baf33ca32a1a3d66b80439c9df; path=/; HttpOnly; Secure; SameSite=None
Cache-control: private
{
"msg":"Hello from class net.heartai.hello_world.HelloWorldServiceIMPL!",
"service":"class net.heartai.hello_world.HelloWorldServiceIMPL",
"id_ping":"07c6e254-a10f-4181-92d6-6174fbe9c4e4",
"timestamp_ping":1626056667079
}
API specification
The HeartAI API specification is developed with Postman and may be found at:
Handshaking to WebSocket connection
Many service endpoints of HeartAI system services have corresponding endpoints that allow the HTTP protocol to handshake to the WebSocket protocol. The WebSocket protocol is preferred for connections that may last for longer than one synchronous exchange of data, and is particularly suitable for real-time data streaming. With this approach, a client may establish a WebSocket connection with a system service, and transfer a stream of data to both the client or the service.
The following WebSocket service endpoint of the HelloWorldService
transfers an incremental counter to the client every second:
wss://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping_ws_count
Message bus endpoints
In addition to service interfaces, users and clients may interface with the HeartAI system message buses that are provided with Apache Kafka and Strimzi. A suggested mechanism for this approach is with Kafka Connect.
Access control
Service endpoints are securable with access control mechanisms, providing access only to users, or clients on behalf of users, that have corresponding security permissions to access a protected resource. The approach for securing service endpoints is through JWT access tokens that are requestable through the HeartAI integrated identity and access management platform.
Please refer to the following documentation for more information about HeartAI service endpoint access control:
Service clients
Users of the HeartAI system may access these service interfaces through direct or indirect clients.
The following are some examples of clients:
Direct clients
- A direct call with curl or wget.
- Direct use of the R package httr or the Python package http.client.
- Internal invocation of HeartAI system service endpoints.
Indirect clients
- Database management software such as Adminer and pgAdmin.
- Web-based dashboard software such as Shiny.
- Business intelligence tools such as Microsoft Power BI.
Example service clients
The following examples show service requests to the HelloWorldService
service endpoint pingService()
with a variety of clients:
C# - RestSharp
Request
var client = new RestClient("https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
cURL
Request
curl --location --request GET 'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping'
Dart - http
Request
var request = http.Request('GET', Uri.parse('https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping'));
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
}
else {
print(response.reasonPhrase);
}
Go Native
Request
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping"
method := "GET"
client := &http.Client {
}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
return
}
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
HTTP
Request
GET /hello/api/public/ping HTTP/1.1
Host: hello.prod.apps.aro.sah.heartai.net
Java - OkHttp
Request
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping")
.method("GET", null)
.build();
Response response = client.newCall(request).execute();
Java - Unirest
Request
Unirest.setTimeouts(0, 0);
HttpResponse<String> response = Unirest.get("https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping")
.asString();
JavaScript - Fetch
Request
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
JavaScript - jQuery
Request
var settings = {
"url": "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping",
"method": "GET",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
console.log(response);
});
JavaScript - XHR
Request
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("GET", "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping");
xhr.send();
libcurl
Request
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(curl, CURLOPT_URL, "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
struct curl_slist *headers = NULL;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
res = curl_easy_perform(curl);
}
curl_easy_cleanup(curl);
Nodejs - Axios
Request
var axios = require('axios');
var config = {
method: 'get',
url: 'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping',
headers: { }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
Nodejs - Native
Request
var https = require('follow-redirects').https;
var fs = require('fs');
var options = {
'method': 'GET',
'hostname': 'hello.prod.apps.aro.sah.heartai.net',
'path': '/hello/api/public/ping',
'headers': {
},
'maxRedirects': 20
};
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
res.on("error", function (error) {
console.error(error);
});
});
req.end();
Nodejs - Request
Request
var request = require('request');
var options = {
'method': 'GET',
'url': 'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping',
'headers': {
}
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
Nodejs - Unirest
Request
var unirest = require('unirest');
var req = unirest('GET', 'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping')
.end(function (res) {
if (res.error) throw new Error(res.error);
console.log(res.raw_body);
});
Objective-C - NSURLSession
Request
#import <Foundation/Foundation.h>
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"%@", error);
dispatch_semaphore_signal(sema);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSError *parseError = nil;
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
NSLog(@"%@",responseDictionary);
dispatch_semaphore_signal(sema);
}
}];
[dataTask resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
OCaml - Cotthp
Request
open Lwt
open Cohttp
open Cohttp_lwt_unix
let reqBody =
let uri = Uri.of_string "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping" in
Client.call `GET uri >>= fun (_resp, body) ->
body |> Cohttp_lwt.Body.to_string >|= fun body -> body
let () =
let respBody = Lwt_main.run reqBody in
print_endline (respBody)
PHP - cURL
Request
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
PHP - HTTP_Request2
Request
<?php
require_once 'HTTP/Request2.php';
$request = new HTTP_Request2();
$request->setUrl('https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping');
$request->setMethod(HTTP_Request2::METHOD_GET);
$request->setConfig(array(
'follow_redirects' => TRUE
));
try {
$response = $request->send();
if ($response->getStatus() == 200) {
echo $response->getBody();
}
else {
echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
$response->getReasonPhrase();
}
}
catch(HTTP_Request2_Exception $e) {
echo 'Error: ' . $e->getMessage();
}
PHP - pecl_http
Request
<?php
$client = new http\Client;
$request = new http\Client\Request;
$request->setRequestUrl('https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping');
$request->setRequestMethod('GET');
$request->setOptions(array());
$client->enqueue($request)->send();
$response = $client->getResponse();
echo $response->getBody();
PowerShell - RestMethod
Request
<?php
$client = new http\Client;
$request = new http\Client\Request;
$request->setRequestUrl('https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping');
$request->setRequestMethod('GET');
$request->setOptions(array());
$client->enqueue($request)->send();
$response = $client->getResponse();
echo $response->getBody();
Python - http.client
Request
import http.client
conn = http.client.HTTPSConnection("hello.prod.apps.aro.sah.heartai.net")
payload = ''
headers = {}
conn.request("GET", "/hello/api/public/ping", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
Python - Requests
Request
import requests
url = "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping"
payload={}
headers = {}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
R httr
Request
library(httr)
url = "http://localhost:14000/phocqus/pathology/api/ping"
res = GET(url)
content(res)
Ruby Net::HTTP
Request
require "uri"
require "net/http"
url = URI("https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Get.new(url)
response = https.request(request)
puts response.read_body
Shell - Httpie
Request
http --follow --timeout 3600 GET 'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping'
Shell - wget
Request
wget --no-check-certificate --quiet \
--method GET \
--timeout=0 \
--header '' \
'https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping'
Swift - URLSession
Request
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
var semaphore = DispatchSemaphore (value: 0)
var request = URLRequest(url: URL(string: "https://hello.prod.apps.aro.sah.heartai.net/hello/api/public/ping")!,timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
print(String(data: data, encoding: .utf8)!)
semaphore.signal()
}
task.resume()
semaphore.wait()