HTTP/1.1 is one of the most successful protocols widely used on the web today. For the past 15 years, HTTP/1.1 had been beneficial for growth of the internet but there were subtle problems with the protocol which have started to show up of late. For example, a web application which is resource intensive has a higher chance of slowing down because only one outstanding request is allowed per TCP connection by HTTP/1.1.
HTTP/2 is a replacement for HTTP/1.1 specification. Even though it is a replacement, the core functionalities, the status codes, etc remain the same. Thus, it is not an entire rework of existing HTTP/1.1 specification. Through HTTP/2, the IETF HTTP Working Group aims at providing a more optimized version of the protocol, with better end-user privacy, client and source resource allocation.
HTTP/2 Client Support In Java 9
Till Java 1.8, the language provided support for HTTP/1.1 alone. As a result, several java developers had to depend on other clients such as Jetty’s ALPN (Application Layer Protocol Negotiation) for writing client code that interracted with servers that supported HTTP/2 protocol. A JDK Enhancement Proposal (JEP 110) was created to define a new HTTP Client API that implements HTTP/2 and WebSockets.
The aim of this proposal is to replace legacy HttpURLConnection which has several problems such as, undocumented behaviours, being too abstract, support for legacy protocols, difficulty in maintenance etc. HttpURLConnection also supports only one thread per request/response, which can be a real overhead in case of large applications.
As an initial step, the new APIs are delivered as incubator modules, which means that a non-final version of the API is released under jdk.incubator.http package. The API specifications may be finalized completely or removed entirely from the development kit in future releases. Expectation from developer community is that this will be achieved by the time JDK 10 is released and the incubator APIs, if successful will be having final versions available under java.httpclient package.
What’s new in the jdk.incubator.http package?
There are a couple of important classes that are involved in http/2 based client programming.
- HttpClient – Container to configure information common to multiple requests.
- HttpRequest – An http request that shall be sent to the server.
- HttpResponse – An http response received from the server. This will essentially contain a set of headers and response codes that give information about the result of the http request sent.
Let us see the things in action now. Here is a simple example on how to successfully execute a GET request using the new HTTP/2 supported client APIs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package http2client; import java.io.IOException; import java.net.URI; import jdk.incubator.http.HttpClient; import jdk.incubator.http.HttpRequest; import jdk.incubator.http.HttpResponse; public class Http2ClientExample { public static void main(String[] args) throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create("https://www.google.com")).GET().build(); HttpResponse.BodyHandler responseBodyHandler = HttpResponse.BodyHandler.asString(); HttpResponse response = client.send(request, responseBodyHandler); System.out.println("Status code = " + response.statusCode()); String body = response.body().toString(); System.out.println(body); } } |
This should give the following output:
1 2 3 4 5 6 7 |
Status code = 302 <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>302 Moved</TITLE></HEAD><BODY> <H1>302 Moved</H1> The document has moved <A HREF="https://www.google.co.in/?gfe_rd=cr&ei=v6prWa24Es2L8QfZ-bvgBQ">here</A>. </BODY></HTML> |
Note: Simply running the above program in eclipse may generate NoClassDefFoundError. To run the above program from eclipse IDE without encountering the said error at runtime, we need to add vm argument to use incubator module for http/2 support. In the Run Configurations, add the following as VM arguments:
1 |
--add-modules jdk.incubator.httpclient |
Saving Response Body Locally
In the above example we are printing the body of the response to console. This may not be needed always. Sometimes, we may want to save the response into a file for future processing. This can be achieved using HttpResponse.BodyHandler.asFile() function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package http2client; import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import jdk.incubator.http.HttpClient; import jdk.incubator.http.HttpRequest; import jdk.incubator.http.HttpResponse; public class Http2ClientExample { public static void main(String[] args) throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create("https://www.google.com")).GET().build(); Path test = Paths.get(System.getProperty("user.dir"), "testFile.html"); HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandler.asFile(test)); System.out.println(response.statusCode()); } } |
The status code 302 will be printed in above case. We can also see that a new file called testFile.html is created in our project base dir which contains the response body.
Sending Data From A File
Sometimes, we may have a big list of key value pairs that are required to create a new entity at the HTTP server end. It would be really beneficial for us if we have the data in a file and post the file contents over the web. HTTP/2 based http client package has HttpRequest.BodyProcessor which allows us to do so.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package http2client; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Paths; import jdk.incubator.http.HttpClient; import jdk.incubator.http.HttpRequest; import jdk.incubator.http.HttpResponse; public class PostHttp2Client { public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8080/employees/")) .POST(HttpRequest.BodyProcessor.fromFile(Paths.get("C://postData.txt"))) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.discard(null)); System.out.println(response.statusCode()); } } |
After successful creation of a new employee entity at server end, status code of 204 No Content should be printed as output of the above program.
Asynchronous HTTP Request
When we fire a request from client end, we don’t really want the client to stop everything and wait for the response for HTTP request before proceeding further. This type of requesting is known as asynchronous HTTP Request.
Using CompletableFuture, we can perform asynchronous http request and check the response. HttpClient has a method sendAsync() which helps us to send these types of request to the server. If the request is waiting for a long time and the server supports HTTP/2, we can go ahead and cancel it.
Let us learn that with an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package http2client; import java.net.URI; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import jdk.incubator.http.HttpClient; import jdk.incubator.http.HttpRequest; import jdk.incubator.http.HttpResponse; public class AsyncRequestExample { public static void main(String[] args) throws InterruptedException, ExecutionException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8080/services")) .GET() .build(); CompletableFuture<HttpResponse<String>> response = client.sendAsync(request, HttpResponse.BodyHandler.asString()); Thread.sleep(3000); if(response.isDone()) { System.out.println(response.get().statusCode()); System.out.println(response.get().body()); } else { response.cancel(true); System.out.println("Request is taking more time, hence cancelling it"); } } } |
The above example will cancel requests that are waiting for a long time to get response from server. This is a better way of utilization of resources both at the client and server end.
Authentication Using HTTP/2 Client APIs
HTTP/2 provides a cleaner way to deal with authentication mechanism. While building the HTTPClient instance itself we can add authentication parameters to it.
Let us see how basic authentication can be done in simple steps using HTTP/2 Client APIs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package http2client; import java.net.URI; import jdk.incubator.http.HttpClient; import jdk.incubator.http.HttpRequest; import jdk.incubator.http.HttpResponse; import java.io.IOException; import java.net.Authenticator; import java.net.PasswordAuthentication; public class AuthenticationWithHttp2Client { private static String USERNAME = "admin"; private static String PWD = "test123"; public static void main(String[] args) throws IOException, InterruptedException { HttpClient client = HttpClient.newBuilder() .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(USERNAME, PWD.toCharArray()); } }) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://localhost:8080/login")) .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString()); System.out.println(response.statusCode()); System.out.println(response.body()); } } |
Thanks for sharing this post!! Well written!!
Glad that you liked the post.