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.

Network architecture

Please refer to the following documentation section for an overview of system network architecture.

Network security and identity and access management

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/ping", pingService()),
      restCall(Method.POST, "/hello/api/ping", pingServiceByPOST()),
      restCall(Method.GET, "/hello/api/ping_ws_count", pingServiceByWebSocketCount),
      restCall(Method.GET, "/hello/api/ping_ws_echo", pingServiceByWebSocketEcho),
      restCall(Method.GET, "/hello/api/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.

Service endpoint access control

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

Indirect clients

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()