JazzHttpClient - quality-manager/onboarding GitHub Wiki
JazzHttpClient (Common HTTP Client)
Background
In November of 2017, Michael Triantafelow started work on upgrading the Apache HTTP Clients in use from their 3.x variants (which are no longer supported) to 4.x. Initial investigation suggested that the number of duplicate HTTP clients in the QM code base, and their poor design, meant that this change would be very invasive (therefore risky) and would require a great number of duplicate changes.
As a result, Mike started work on consolidating the various HTTP clients into a single, common HTTP client API called JazzHttpClient.
Design Goals
The JazzHttpClient API, should be generically useful to any client that needs to send HTTP requests to a Jazz-based application (like a QM server). It should not contain features that are more specialized than that (e.g. Reportable REST API support). Clients with such features could (and should) be built on top of the JazzHttpClient.
The API should encapsulate the complexity of such an interaction. Learning how to properly complete the various types of Jazz authentication handshakes is difficult and time consuming. Interpreting the various error codes and messages are likewise. This client should remove that burden from the user. It should be intuitive and well documented. It should be an API that supports discoverability. Above all, it should drastically reduce the boiler plate code needed to complete simple and common types of HTTP calls to Jazz servers.
Design and Usage
There are four components that make up the current implementation of the JazzHttpClient API. While all four of these components reside in the com.ibm.rqm.http.client
bundle, they are conceptually separate components. They are designed to be independently developable and deployable with no circular references between them. It is this design that makes this API so flexible and maintainable, so it should be honored, we just didn't see a good enough reason to take on the added cost of physically separating them into separate bundles with how little code there is in each right now.
com.ibm.rqm.http
The innermost component (which has no dependencies on anything but Java) is the com.ibm.rqm.http
component. This component contains classes which help reduce boiler plate code related with any and all HTTP related activities. This mostly means providing constants and utility methods, but the flagship class in this component is the Url
class.
The issues with Java's built-in URL
and URI
classes are well documented. With Url
we begin the process of building a good alternative. Url
is inspired by similar classes that can be found in OkHttp and in the Android SDK. It is immutable, buildable, and provides a number of convenience methods for common URL related tasks. It is very new and has lots of room to grow. We'd also expect that other more specific types of URLs (e.g. a ReportableRestApiUrl class) could be built upon it.
com.ibm.rqm.http.client
One level less abstract is the com.ibm.rqm.http.client
component. Where com.ibm.rqm.http
is useful to anyone that is doing anything with HTTP, this component is only useful to those making HTTP requests to a Jazz-based application. There are three main classes to be concerned with in this component.
Request and Response
First, let's look at Request
and Response
as a pair. These two represent (not surprisingly) the HTTP request to be sent to the server and the HTTP response that is received from the server. Requests are basically immutable POJO objects that can be constructed using the Builder Pattern.
Request request = Request.builder()
.post()
.url("https://foobar.com:9443/qm/endpoint")
.header("Accept", "application/xml")
.body(new FileRequestBody(MimeTypes.APPLICATION_XML, StandardCharsets.UTF_8, Paths.get("/Users/Me/Desktop/file.xml")))
.build();
You can also build upon existing requests to create a new request that is only slightly different from an existing one.
Request request_2 = request.buildUpon()
.header("Accept", "application/json")
.build();
Response
s are NOT simple POJO objects and are NOT immutable. As much as we'd like to have that symmetry with these two, a Response
needs to be able to perform much more complicated behaviors---not the least of which is to be closed. Response
is actually a thin abstraction over the underlying HTTP library's response object with a few convenience methods.
For example: the below code makes use of two convenience methods to write the body to std out if the response code was 200 (OK).
if (response.isOk())
System.out.println(response.readBodyAsString());
Response
also has some convenience methods that are specific to Jazz-based apps. In this example Response
will test for the Jazz header that communicates authentication success or failure.
if (response.isAuthenticationFailed())
System.out.println("Invalid username or password");
Remember that a Response
uses non-memory resources (i.e. a socket) and therefore---like with all non-memory resources---it must be closed when you are done using it to avoid resource leaks. Luckily, Response
is auto-closeable. Use this pattern to make sure your responses always get closed.
try (Response response = httpClient.execute(request)) {
// do whatever you need to do with the response here
}
JazzHttpClient
The JazzHttpClient
is an abstraction over the underlying HTTP client which also adds useful default configuration and convenient behaviors like automatic Jazz authentication handling which are useful to clients that want to send HTTP requests to Jazz-based apps.
JazzHttpClient
is immutable. To create one you will need to use its builder.
JazzHttpClient client = JazzHttpClients.builder()
.disableSslCertificateVerification()
.credentials("qmadmin", "qmadmin")
.build();
The builder offers a number of convenient configuration options which should cut down on boilerplate code. For example, the above disableSslCertificateVerficiation()
call will allow this client to be used with servers that use self-signed certificates (NOTE: this is only recommended for test, do not disable SSL in production!).
You may notice that JazzHttpClient
does not have any kind of "authenticate" method. That's because this client handles Jazz based authentication automatically. Clients configure their authentication strategy when the HTTP client is constructed (see above). If and when a request is challenged for authentication, the JazzHttpClient
will authenticate at that time.
// not authenticated here
client.execute(request) // tries the request, authenticates, then retries the request
// still authenticated (probably)
Like with Response
, instances of JazzHttpClient
must be closed to avoid resource leaks. This class is also auto-closable, so the recommended pattern is much the same. If we combine both of these we get something that looks like the below.
try (JazzHttpClient client = JazzHttpClients.builder()
.disableSslCertificateVerification()
.credentials("qmadmin", "qmadmin")
.build()) {
try (Response response = client.execute(Request.builder()
.post()
.url("https://foobar.com:9443/qm/endpoint")
.header("Accept", "application/xml")
.body(new FileRequestBody(MimeTypes.APPLICATION_XML, StandardCharsets.UTF_8, Paths.get("/Users/Me/Desktop/file.xml")))
.build())) {
System.out.println(response.readBodyAsString());
}
}
That might take a bit of effort to parse and understand if you haven't worked with this API before. Let's take a look at an execution flow diagram to help us understand it.
For simplicity sake, in the above diagram we treat builders and the objects they build as the same object. We also treat JazzHttpClient and whatever class that implements it as the same object. Remember that the calls to close()
which happen at the end are implicit thanks to Java 7's try-with-resources
block.