Since CSDE expose its services via REST API, our ReactJs and SpringBoot#Backend server needs a way to call HTTPS endpoints.
Apache HttpClient 5.0
Although the java.net
package provides basic functionality for accessing resources via HTTP, it doesn't provide the full flexibility or functionality needed by many applications. HttpClient seeks to fill this void by providing an efficient, up-to-date, and feature-rich package implementing the client side of the most recent HTTP standards and recommendations.
build.gradle
dependencies {
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
//implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.2.1'
}
quickStart.java
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
ClassicHttpRequest httpGet = ClassicRequestBuilder
.get("http://httpbin.org/get")
.build();
// The underlying HTTP connection is still held by the response object
// to allow the response content to be streamed directly from the network
// socket.
// In order to ensure correct deallocation of system resources
// the user MUST call CloseableHttpResponse#close() from a finally clause.
// Please note that if response content is not fully consumed the underlying
// connection cannot be safely re-used and will be shut down and discarded
// by the connection manager.
httpclient.execute(httpGet, response -> {
System.out.println(response.getCode() + " " + response.getReasonPhrase());
final HttpEntity entity1 = response.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity1);
return null;
});
ClassicHttpRequest httpPost = ClassicRequestBuilder
.post("http://httpbin.org/post")
.setEntity(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("username", "vip"),
new BasicNameValuePair("password", "secret"))))
.build();
httpclient.execute(httpPost, response -> {
System.out.println(response.getCode() + " " + response.getReasonPhrase());
final HttpEntity entity2 = response.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity2);
return null;
});
}
Secure HTTPS endpoints
If we would to try to to GET some data from CSDE on https://localhost:8888/api/v1/virtualnode
we would fail with unable to find valid certification path to requested target
runtime exception. This is because we need to use SLL to implement HTPPS support.
This sample client is suitable only for testing purposes, since it does not perform any form of serve cert validation, and establishes encrypted connection with any server.
naiveSecureRestClient.java
final TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
final SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
final BasicHttpClientConnectionManager connectionManager =
new BasicHttpClientConnectionManager(socketFactoryRegistry);
try (CloseableHttpClient httpclient = HttpClients
.custom()
.setConnectionManager(connectionManager)
.build()) {
// Your HTTP handeling
}
Certification Authority
CA usage, is a common strategy for clients to ensure servers validity. In this project, CordApp uses self signed certificate, which our client shall use as its CA.
-
Obtain servers certificate with
openssl s_client -connect localhost:8888 2>&1 </dev/null | sed -ne '/BEGIN CERT/,/END CERT/p'
-
Add CA cert to TrustStore
keytool -importcert -keystore truststore.p12 -storepass [password] -file [certificate_file]
The command will create a JavaKeyStore in
PKCS12
format. -
Place
truststore.p12
among servers resources files:src/main/resources/keystore/truststore.p12
-
Use trust store to configure
SSLContext
:src/main/resources/application.properties
trust.store=classpath:keystore/truststore.p12 trust.store.password=test123
stc/test/java/djmil/cordacheckers/CordacheckersApplicationTests.java
@SpringBootTest class CordacheckersApplicationTests { @Value("${trust.store}") private Resource trustStore; @Value("${trust.store.password}") private String trustStorePassword; @Test void givenAcceptOnlyCACertificates_whenHttpsUrlIsConsumed_thenOk() throws GeneralSecurityException, IOException { final SSLContext sslContext = SSLContexts .custom() .loadTrustMaterial(trustStore.getURL(), trustStorePassword.toCharArray()) .build(); final SSLConnectionSocketFactory sslsf = ...; } }
RestTemplate
At this point, we shall be able to fetch data from HTTPS endpoint. HttpClient is a general-purpose library to communicate using HTTP. If we want to have higher level of abstraction dealing with JSON/XML objects, we have to use RestTemplate - it can bind data to custom domain types.
Pojo
package djmil.cordacheckers.pojo;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record virtualnode(List<virtualNodes> virtualNodes) { }
This simple Java record class is annotated with @JsonIgnoreProperties
from the Jackson JSON processing library to indicate that any properties not bound in this type should be ignored, and represent a top level POJO representation of JSON document returned from the CSDE API for https://localhost:8888/api/v1/virtualnode
endpoint.
For direct binding of some data to some custom type, you need to specify the variable name to be exactly the same as the key in JSON. In case the variable name and the key do not match - use @JsonProperty
annotation to specify the exact key of the JSON document.
Implementation example
Create Bean for RestTemplate under the @Configuration annotated class. You can even write a separate class and annotate with @Configuration
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Then, define RestTemplate with @Autowired or @Injected under your Service / Controller
@Autowired
private RestTemplate restTemplate;
All is ready to perform actual REST API request with help of RestTemplate. For this we can use multiple methods like exchange(), getForEntity(), getForObject().
// Request authorization header
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth("admin", "admin");
// String authStr = "username:password";
// String base64Creds = Base64.getEncoder().encodeToString(authStr.getBytes());
// headers.add("Authorization", "Basic " + base64Creds);
// Request
final HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<virtualnode> response = new RestTemplate(requestFactory)
.exchange("https://localhost:8888/api/v1/virtualnode",
HttpMethod.GET,
request,
virtualnode.class );
if (response.getStatusCode() == HttpStatus.OK &&
response.hasBody()) {
virtualnode vNode = response.getBody();
}
Configure RestTemplate to use HttpClient
The final bit of a puzzle, is that we need a way to tell HttpClient to use RestTemplate
CloseableHttpClient httpClient = HttpClientBuilder
//.create()
//.disableCookieManagement()
.setConnectionManager(connectionManager)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setReadTimeout(readTimeout);
factory.setConnectTimeout(connectTimeout);
RestTemplate restTemplate = new RestTemplate(factory);