Technical Articles
Access any REST service with the SAP Cloud SDK
Disclaimer: This blog post is only applicable for the SAP Cloud SDK version of at most 2.19.2. We plan to continuously migrate these blog posts into our List of Tutorials. Feel free to check out our updated Tutorials on the SAP Cloud SDK.
Note: This post is part of a series. For a complete overview visit the SAP Cloud SDK Overview.
We have already seen the SAP Cloud SDK providing capabilities for conveniently consuming OData and SOAP based API services. But your application may target a web service, which serves resources from an REST based endpoint, instead of OData or SOAP. If the REST based endpoint is well-defined, its specification can be declared as OpenAPI.
In this post you will learn how to use the SAP Cloud SDK together with a well-defined web service endpoint, driven by OpenAPI.
Contents
- OpenAPI
- RESTful API Description Languages
- OpenAPI Specification
- Features
- Basic structure
- Code generation
- SAP Cloud SDK
- Usage of HttpClient
- Spring RestTemplate
- OpenFeign
- Google WebClient
- Limitations and currently not-supported frameworks
- SAP API Business Hub
- Usage of HttpClient
- Appendix
OpenAPI and SAP Cloud SDK
With the SAP Cloud SDK you can ease the application development by using the already implemented interface between SAP Cloud Platform and your software, e.g. multi-tenancy, destination with prepared connectivity and authorization handling – with support for both platforms, CloudFoundry and Neo.
We will describe how you can generate a type-safe OpenAPI client using standard open-source tools and use the client together with the SAP Cloud SDK, which handles the connectivity and authentication. But before we go into more depth on this topic, let’s briefly recap our understanding of OpenAPI.
RESTful API Description Languages
Most developers have encountered REST (representational state transfer) based web services described with at least one of the popular description languages:
- Web Services Description Language (WSDL)
- RESTful API Modeling Language (RAML)
- Open Data Protocol (OData)
- OpenAPI
- …
RESTful API description languages are designed to provide a formal description of a web API that is useful both to a human and for automated machine processing. Its structured description can be used to generate accessible documentation for developers. Due to the tool-based formatting conventions, all generated files follow the same approved pattern. For the developer, this is easier to read than free-form documentation, often encountered in the business.
OpenAPI Specification
The OpenAPI specification is originally known as the Swagger Specification. The name change was introduced with version 3.0. It describes the machine-readable interface for characterizing, producing, consuming, and visualizing RESTful web services. Some tools are based on the older specification, Swagger, and others already migrated to the OpenAPI framework. The proposed tools in this post can be found for either framework.
With the frameworks themselves being language-agnostic, you can find tools for almost any programming language available. The work of all tools is based on the provided interface file, which acts as an API description format for your REST API. Given the interface file, you can use the various tools to generate code, automate documentation and prepare test cases.
One of the most used tools are code generation and interactive online documentation, e.g. Swagger UI. In this blog post, we will particularly look at code generation based on OpenAPI specifications.
Additional Features:
- With the declarative OpenAPI specification, clients can understand and consume services even without having the knowledge of or access to the backend implementation.
- It’s possible to have the OpenAPI interface files being audited, for convention checks or security vulnerabilities.
- The interface file allows creating a description for the API, including:
- Available endpoints (/users) and operations on each endpoint (GET /users, POST /users)
- Operation parameters as input and output for each operation
- Authentication methods
- Contact information, license and terms of use
- The API specifications can be written in YAML or JSON. Both formats are easy to learn, read and interpret.
Basic structure
In this guide, we only use YAML examples to demonstrate the OpenAPI interface, but JSON works just equally well. A sample OpenAPI 3.0 definition looks like the following.
openapi: 3.0.0
info:
title: Sample API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: http://api.example.com/v1
description: Optional server description, e.g. Main (production) server
- url: http://staging-api.example.com
description: Optional server description, e.g. Internal staging server for testing
paths:
/users:
get:
summary: Returns a list of users.
description: Optional extended description in CommonMark or HTML.
responses:
'200': # status code
description: A JSON array of user names
content:
application/json:
schema:
type: array
items:
type: string
Note: In our use case of consuming a service, we expect the OpenAPI file as above to be downloadable from the service host. We do not expect to write or edit such files ourselves. This example merely serves as illustration to reference the general structure of the file.
The structure is defined in several sections:
- At the beginning of the file, we find the metadata:
- Every API definition must include the version of the OpenAPI Specification that this definition is based on,
openapi: 3.0.0
- Compound
info
section, which contains API information liketitle
,version
anddescription
(optional)
- Every API definition must include the version of the OpenAPI Specification that this definition is based on,
servers
specifies the API servers with baseURL
and optionaldescription
paths
defines individual endpoints of in your API, and the HTTP methods (operations) supported by these endpoints. An operation definition includes parameters (if any), request body (if any), possible response status codes (such as 200 OK or 404 Not Found) and response contents.- In addition, the OpenAPI specification supports the following authentication methods:
- HTTP authentication: Basic, Bearer, and so on.
- API key as a header or query parameter or in cookies
- OAuth 2
- OpenID Connect Discovery
Code generation
By using the OpenAPI generator you can easily create executable code from an OpenAPI file. Java is only one of the various supported languages to generate source code to. As our guide focuses on application development with Java version 8, we highly recommend running the generator as convenient Maven plugin.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>RELEASE</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
<generatorName>java</generatorName>
<configOptions>
<java8>true</java8>
<dateLibrary>jave8</dateLibrary>
<library>resttemplate</library>
<sourceFolder>src/gen/java/main</sourceFolder>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
Note: Change the wildcard RELEASE in plugin version
to the latest version available in the Maven central repository. This will ensure consistency and reproducibility among multiple builds. Also change the path definition in inputSpec
to your preferred api file. We highly recommend placing the OpenAPI file into the resources directory of your application.
In the plugin settings, notice the emphasize on the Java version and distinct value of library
. This is our minimal setup recommendation to ensure library support with SAP Cloud SDK. Of course, there are many other parameters which can be set, to customize the generated code. Please find the official documentationonline.
During the project build process, the generator is run with the default Maven lifecycle phase generate-sources
. In return, the generated code will vanish during the phase clean
. Hence by running the full build routine, a fresh code generation is triggered automatically:
mvn clean install
SAP Cloud SDK
The SAPCloud SDK simplifies application development, as it provides out-of-the-box capabilities, such as abstractions of the underlying cloud platform implementation of SAP Cloud Platform, fault-tolerance and cache management. You will find many tutorials, project templates, and courses.
The library, which can be easily retrieved from the public Maven central, can be introduced to any java project. To utilize its main features, all it takes is creating the request based HttpClient
and propagate it to the framework which calls an OpenAPI.
The prepared HttpClient
already contains proxy settings and authentication headers, automatically resolved from the platform abstraction:
// create a new HttpClient for SCP destination called: MyDestination
final String DESTINATION_NAME = "MyDestination";
final HttpClient httpClient = HttpClientAccessor.getHttpClient(DESTINATION_NAME);
// ... manually run a HttpRequest with the instance
You can either call the REST endpoints yourself by taking the HttpClient
on hand like above, or by using one of the libraries, suggested by the OpenAPI code generator.
Use the HttpClient for libraries suggested by the OpenAPI framework
For the following code examples, the HttpClient
prepared by Cloud SDK needs to be injected to the automatically generated ApiClient
framework. With the generated object you can query all API actions as described in the OpenAPI interface.
All library templates not only require an adjustment of nested REST library to feature the HttpClient
, but also a modification of ApiClient
. Here the base URL of the called REST service must be set at runtime, to ensure proper multi-tenancy and compliance of the registered destination.
Spring RestTemplate (recommended)
The ApiClient
is changed to feature the correct destination-dependent endpoint path. For authorization and destination-dependent server URL, the instance of RestTemplate
is created. The HttpClient
is injected to the latter object.
public ApiClient createApiClient() throws URISyntaxException
{
// resolve destination
final Destination destination = DestinationAccessor.getDestination(DESTINATION_NAME);
// instantiate RestTemplate and ApiClient
final RestTemplate restTemplate = createRestTemplate();
final ApiClient apiClient = new ApiClient(restTemplate);
// set root of API Client base path
final URI uri = destination.getUri();
final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
apiClient.setBasePath(path.toString());
return apiClient;
}
public RestTemplate createRestTemplate()
{
// create new HttpClient for destination
final HttpClient httpClient = HttpClientAccessor.getHttpClient(DESTINATION_NAME);
// instantiate template with prepared HttpClient, featuring repeated response reading
final RestTemplate restTemplate = new RestTemplate();
final HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setHttpClient(httpClient);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory));
return restTemplate;
}
OpenFeign
The HttpClient
is injected to the Feign Builder by using the client modifier function of Feign Builder. As opposed to the other libraries, Feign works with interfaces, hence there is no implementation required for ApiClient
.
public Feign.Builder createFeignBuilder() {
final HttpClient httpClient = HttpClientAccessor.getHttpClient(DESTINATION_NAME);
return Feign.builder()
.encoder(new FormEncoder(new JacksonEncoder(getObjectMapper())))
.decoder(new JacksonDecoder(getObjectMapper()))
.logger(new Slf4jLogger())
.client(new ApacheHttpClient(httpClient));
}
public <T extends ApiClient.Api> T buildApi(Class<T> apiClass) {
// resolve destination
final Destination destination = DestinationAccessor.getDestination(DESTINATION_NAME);
// set root of API Client base path
final URI uri = destination.getUri();
final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
Feign.Builder feignBuilder = createFeignBuilder();
return feignBuilder.target(apiClass, path.toString());
}
Google API Client
A custom ApiClient
is created, by applying service path and the wrapped HttpClient
to the constructor.
public ApiClient createHttpClient() {
// set root of API Client base path
final Destination destination = DestinationAccessor.getDestination(DESTINATION);
final URI uri = destination.getUri();
final URI path = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null);
// set HttpClient
final HttpClient httpClient = HttpClientAccessor.getHttpClient(destination);
final ApacheHttpTransport apacheHttpTransport = new ApacheHttpTransport(httpClient);
final HttpRequestInitializer initializer = null; // optional credentials
final ObjectMapper objectMapper = null; // optional Jackson ObjectMapper
return new ApiClient(path.toString(), apacheHttpTransport, initializer, objectMapper);
}
Limitations and not yet supported frameworks
Since the SAP Cloud SDK is currently only supporting the synchronous HttpClient
of Apache as interface for web requests, some frameworks may not work with injecting the client:
- OkHttp
- Jersey
- VertX
- WebClient
If you want to use the Cloud SDK feature set but you have one of the unsupported library templates in place, you should consider migrating to a supported template instead. The template can be easily changed by modifying the code generator plugin configuration. Due to the consistent architecture of the generated code, a switch of the library template can be achieved with little effort. We recommend the plugin settings for code generation as described above.
SAP API Business Hub
The set of APIs officially supported by SAP solutions can be browsed in the SAP API Business Hub. Its services are declared as either ODATA, SOAP or REST. If you are interested in OData and SOAP, please take a look at what the Cloud SDK can provide as additional functionality.
In this guide, we focus on REST APIs. And if a service is labeled as such, we can expect a well maintained and downloadable OpenAPI 2.0 / Swagger file.
Each item can be clicked on. The “API reference” page will open. In here, you will find multiple documented endpoints and most of them are interactive. They can be accessed by the Swagger UI like interface and tested against a sandbox system:
The meta information about an API is stored in the “Details” page. Here you find the available URLs of the API, depending on your target landscape. Also details about authentication and links to in-depth documentation are referenced on this page.
Most importantly you will find the “Download Specification” button, which let’s you download the OpenAPI file to the API. The specification can be downloaded as JSON or YAML. Both of them can be parsed by the code generator, as described above.
Please feel encouraged to experiment with the OpenAPI code generator and a common API from the SAP API Business Hub – like “Monitoring v2”. Use the code generator settings as previously described above and try to inject the HttpClient
of SAP Cloud SDK.
Appendix
Please find the following notes.
- Broken letter casing in APIs provided by SAP API Business HubIf you encounter broken letter casing, bad HTTP method prefices (e.g.
gETSomething
), you can most likely fix the interface file. You can either do this manually by hand or automate this step as part of the Maven build, e.g.:<plugin> <groupId>com.google.code.maven-replacer-plugin</groupId> <artifactId>replacer</artifactId> <version>1.5.3</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>replace</goal> </goals> </execution> </executions> <configuration> <file>${project.basedir}/src/main/resources/api.yaml</file> <outputFile>${project.basedir}/target/api.yaml.fixed</outputFile> <regex>true</regex> <replacements> <replacement> <token>operationId: GET(\w)</token> <value>operationId: get$1</value> </replacement> <replacement> <token>operationId: POST(\w)</token> <value>operationId: post$1</value> </replacement> <replacement> <token>operationId: PUT(\w)</token> <value>operationId: put$1</value> </replacement> <replacement> <token>operationId: DELETE(\w)</token> <value>operationId: delete$1</value> </replacement> <replacement> <token>operationId: OPTIONS(\w)</token> <value>operationId: options$1</value> </replacement> <replacement> <token>operationId: TRACE(\w)</token> <value>operationId: trace$1</value> </replacement> <replacement> <token>operationId: HEAD(\w)</token> <value>operationId: head$1</value> </replacement> <replacement> <token>operationId: CONNECT(\w)</token> <value>operationId: connect$1</value> </replacement> </replacements> </configuration> </plugin>
Note: The plugin configuration above does not overwrite the original
api.yaml
. Instead a new fixed copy is created as./target/api.yaml.fixed
. You will need to change your code generator plugin accordingly, such that it points to the fixed file.
Hi,
Great Blog Alexander.
Can you share some insights on how to handle Rest API's via oAuth in NEO , to be called from a service in Cloud Foundry assuming Destination is maintained with Client Credentials and SCOPE in CF system.
Is it possible to call API's in NEO without having to fetch the Token, or still the Token has to be fetched and then API call again.
Does Cloud SDK has some mechanism to get and call API with Token based on the Destination maintained in CF.
Regards,
Rajiv
Hi Rajiv,
Please take a look at this step-by-step guide on how to "Consume REST Service API with SAP Cloud SDK". From step 7 (and onwards), you can see how to create OAuth Client Credentials for a service subscribed in Neo. It's also shown how to declare the destination in Cloud Foundry in order to establish a connection.
I'm not sure what you are meaning with "scope" though. This tutorial only explains the workflow for Client Credentials and services provided by the SAP API Business Hub. If you consider User Token propagation, this will be something different and requires the configuration of Identity Provider for user mapping.
The token fetching happens automatically via destination service on Cloud Foundry. That's nothing you have to deal with yourself in your application. The Cloud SDK handles the token requests internally and applies the headers to the HttpClient. As long as you use the classes provided by our library, the token (configured in your destination) will be added by default.
Best regards
Alex
Hi Alexander,
Do you know if it is possible to inject the httpClient from the cloud SDK to a WebClient (Spring managed async calls) ? If so, can you elaborate on how to do this?
Thanks
Claudia
Hello Claudia,
Unfortunately you have spotted a typo. The tutorial mentioned “Google WebClient” as supported, but actually “Google API Client” was meant. In fact WebClient is not supported, because its API is not able to consume the prepared Apache HttpClient from the Cloud SDK. Since we are limited to that type, only synchronous HTTP requests are possible. If you want to introduce async behavior to your code, we recommend using the resilience features of Cloud SDK, where you can queue asynchronous tasks, e.g. for wrapping a HttpClient request. Please find the Command#queue in Cloud SDK 2 and ResilienceDecorator#queueCallable and ResilienceDecorator#queueSupplier in Cloud SDK 3.
I’m sorry for the confusion. However If you still want to prepare your custom client, we suggest to use the features of the new Destination API of Cloud SDK 3.
Best regards
Alexander
Hi Alex,
thank you for your prompt reply. Essentially you are saying I could still use my custom Spring WebClient if I extract the headers from the destination as you showed above. Would this work if my service uses oauth2?
Thank you!
Claudia
Yes, it should work as expected. Please don't forget to forward the other information, provided by HttpDestination, to your Spring WebClient. For example proxy configuration, trust store and key store. Just explore the Getters of this interface and try to include the data.
Hi Alex,
I forwarded only the authorization header from the Destination and it worked!
Thanks so much!
Claudia