Skip to content

Sending HTTP requests

Introduction

Noumena Cloud supports integration with external HTTP services through a dedicated HTTP bridge. This guide will help you set up the HTTP bridge and integrate it with your Noumena Cloud application.

servicesHTTP.png

Getting Started

To get started with the HTTP bridge, you need to enable it in your Noumena Cloud application. Once enabled, you can configure the Authentication methods to be added to the HTTP bridge and implement the necessary logic in NPL to send HTTP requests and handle responses.

Dependencies

npl-connectors-library needs to be added as a dependency to the NPL project.

Here is an example of how to add the dependency and unpack the library using Maven:

<!-- pom.xml -->
<!-- add dependency -->
<dependencies>
  <!--...-->
  <dependency>
    <groupId>com.noumenadigital.contrib</groupId>
    <artifactId>npl-connectors-library</artifactId>
    <version>1.0.19</version>
  </dependency>
</dependencies>
<!-- unpack library -->
<build>
  <plugins>
    <!--...-->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-npl-contrib</id>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <phase>generate-sources</phase>
          <configuration>
            <outputDirectory>${project.build.directory}/contrib</outputDirectory>
            <includeGroupIds>com.noumenadigital.contrib</includeGroupIds>
            <includeArtifactIds>npl-connectors-library</includeArtifactIds>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
        <execution>
          <id>unzip-contrib-libs</id>
          <goals>
            <goal>unpack-dependencies</goal>
          </goals>
          <phase>generate-sources</phase>
          <configuration>
            <outputDirectory>${project.build.directory}/classes</outputDirectory>
            <includeArtifactIds>npl-connectors-library</includeArtifactIds>
            <fileMappers>
              <fileMapper implementation="org.codehaus.plexus.components.io.filemappers.RegExpFileMapper">
                <pattern>npl-connectors-library-.*/connector</pattern>
                <replacement>connector</replacement>
              </fileMapper>
            </fileMappers>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Configuring the HTTP Bridge

HTTP bridge can be enabled in your Noumena Cloud application by navigating to the services section in the Noumena Cloud portal and toggling the HTTP bridge service on. This will allow you to use the HTTP bridge for interacting with HTTP APIs directly from your NPL applications.

Once the HTTP bridge is enabled, it can be configured to connect to different APIs. At the moment, the bridge only supports APIs with the JSON request and response format.

Authentication configuration can be set for each API directly through the Noumena Cloud portal.

httpConfig.png

Example of the configuration file:

nyala.de:
  auth_type: api_key_with_hmac
  api_key: xxx
  api_secret: xxx

# This is to support the token-fetching auth flow, as an example.
# This can be setup in https://developer.spotify.com/dashboard.
spotify.com:
  auth_type: bearer_token
  token_url: https://accounts.spotify.com/api/token
  # Request headers.
  headers:
    Content-Type: application/x-www-form-urlencoded
  body:
    client_id: xxxx
    client_secret: xxxx
    grant_type: client_credentials

Using the HTTP Bridge in NPL

To use the HTTP bridge in your NPL applications, you need to implement the necessary logic to send HTTP requests and handle responses. You will then be able to read responses from the HTTP bridge and process them in your NPL applications.

To facilitate the integration with external HTTP services, NPL data types and functions can be generated from the OpenAPI specification of the HTTP service you want to integrate with.

HTTP requests and responses

Once the HTTP bridge is configured, you can use it in your NPL applications. The HTTP bridge provides a set of APIs that can be used to interact with external HTTP services using the standard NPL notify-resume pattern. It provides the following types of messages:

  • HttpRequestExecutionMessage: Used to perform a HTTP request to an external HTTP service.
struct HttpRequest {
    method: HttpMethod,
    url: Text,
    data: HttpData
};

struct HttpResponse {
    executionStatus: ExecutionStatus,
    statusCode: Number,
    data: HttpData
};

notification HttpRequestExecutionMessage(httpRequest: HttpRequest) returns HttpResponse;

HTTP request execution

permission[worker] executeWithData(request: HttpRequest) {
    notify HttpRequestExecutionMessage(request) resume responseCallback;
}

@api
permission[worker] responseCallback(res: NotifyResult<HttpResponse>) | pending {
    match (res) {
        is NotifySuccess<HttpResponse> -> match (res.result.executionStatus) {
            ExecutionStatus.Success -> this.httpRequestExecuted[worker](res.result)
            ExecutionStatus.Failure -> this.httpRequestFailed[worker](res.result)
        }
        // This will never be called -- the HTTP service response, including errors, is passed via NotifySuccess.
        is NotifyFailure -> {}
    };
};

NPL code Generation

The HTTP bridge supports code generation for NPL applications. You can generate NPL code from the OpenAPI specification of the HTTP service you want to integrate with using the openapi-npl-codegen Maven plugin. This plugin will generate NPL data types based on the OpenAPI specification that can be used in the NPL applications.

Example of the Maven plugin configuration for generating NPL code:

<plugin>
    <groupId>com.noumenadigital.contrib</groupId>
    <artifactId>openapi-npl-codegen</artifactId>
    <version>1.0.20</version>
    <executions>
        <execution>
            <id>npl-datatypes-generation</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <openApiSpecPath>${project.build.sourceDirectory}/integrations/nyala/openapi.json</openApiSpecPath>
                <outputDataTypesPath>${project.build.sourceDirectory}/integrations/nyala/generated/datatypes.npl</outputDataTypesPath>
                <nplPackageName>integrations.nyala.generated</nplPackageName>
            </configuration>
        </execution>
    </executions>
</plugin>

Example of the generated NPL code:

struct CountryDto {
    id: Optional<Text>,
    iso: Optional<Text>,
    isoCode3: Optional<Text>,
    name: Optional<Text>
}

function serializeCountryDto(r: CountryDto) returns HttpData -> {
    var ret = listOf<Pair<Text, HttpData>>();
    if (r.id.isPresent()) {
        ret = ret.with(Pair("id", serialize(r.id.getOrFail())));
    };

    if (r.iso.isPresent()) {
        ret = ret.with(Pair("iso", serialize(r.iso.getOrFail())));
    };

    if (r.isoCode3.isPresent()) {
        ret = ret.with(Pair("isoCode3", serialize(r.isoCode3.getOrFail())));
    };

    if (r.name.isPresent()) {
        ret = ret.with(Pair("name", serialize(r.name.getOrFail())));
    };

    return ret;
}

function deserializeCountryDto(data: HttpData) returns Optional<CountryDto> -> {
    return match (data) {
        is List<Pair<Text, HttpData>> -> optionalOf(CountryDto(
            id = if (getter(data, "id").isPresent()) { deserializeText(getter(data, "id").getOrFail()); } else { None(); },
            iso = if (getter(data, "iso").isPresent()) { deserializeText(getter(data, "iso").getOrFail()); } else { None(); },
            isoCode3 = if (getter(data, "isoCode3").isPresent()) { deserializeText(getter(data, "isoCode3").getOrFail()); } else { None(); },
            name = if (getter(data, "name").isPresent()) { deserializeText(getter(data, "name").getOrFail()); } else { None(); }
        ))
        else -> None()
    };
}

function deserializeListOfCountryDto(data: HttpData) returns List<CountryDto> -> {
    return match (data) {
        is List<HttpData> -> data.map(function(v: HttpData) -> deserializeCountryDto(v).getOrFail())
        else -> listOf<CountryDto>()
    };
}

Response can be parsed using the generated NPL code dedicated for the API response. For example, if you have a response of the CustomerAccountDtoListApiResponse type, you can deserialize it as follows:

permission[http_connector] getCustomerListSucceeded(response: HttpResponse) {
    var parsedResponse: CustomerAccountDtoListApiResponse = deserializeCustomerAccountDtoListApiResponse(response.data).getOrFail();
    if (parsedResponse.data.isPresent()) {
        var customerData = parsedResponse.data.getOrFail();
        // Process customer data.
    } else {
        error(
            "Nyala API: failed to fetch a list of customers. Error codes: " + parsedResponse.errorMessageCodes.toText()
        );
        // Process API error.
    };
}