16.2. Java’s java.net.http
Library¶
Java (versions >=11) ships with java.net.http, a package that provides classes and interfaces that understand HTTP. It enables Java programs to access web content without worrying about the low-level details of the data exchange we described earlier. In this reading, we are going to focus on these three types:
Class |
Description |
---|---|
Represents an HTTP request. |
|
Used to send HTTP requests and receive their HTTP responses. |
|
Represents an HTTP response. |
Some complete code examples that involve all three types are included near the end of the chapter.
16.2.1. HttpRequest
¶
The HttpRequest class provided by java.net.http allows Java
programs to “build” HTTP request messages that can be sent using an
HTTP client. Instead of providing a public constructor, the authors of
HttpRequest
decided to use the builder pattern — HttpRequest
objects are constructed by building them using an
HttpRequest.Builder
object returned from
HttpRequest.newBuilder()
. The authors’ use of the builder pattern
prevents the construction of incomplete request objects and provides a
nice alternative to potentially complex looking constructor calls.
Most of the methods provided by HttpRequest.Builder
merely update
the request information stored in the builder object, then just return a
reference to builder object itself so that you can update it further via
additional method calls. Once all the request information is specified,
the build()
method is called to construct the actual HttpRequest
object. The following two code snippets build a request object using the
exact same statement — the second snippet distributes the statement
across several lines to make it easier to read:
HttpRequest request = HttpRequest.newBuilder().uri(location).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(location) // sets the HttpRequest's URI
.build(); // builds and returns an HttpRequest
The HTTP specification requires that requests indicate their purpose and
expectations regarding a successful result by setting a request method
value. By default, HttpRequest.Builder
builds “GET” requests that
ask an HTTP server to include the requested content in the body of the
response message that is sent back to the client. The following two code
snippets build the same “GET” request:
HttpRequest request = HttpRequest.newBuilder()
.uri(location) // sets the HttpRequest's URI
.GET() // sets the HttpRequest's request method to GET
.build(); // builds and returns an HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(location) // sets the HttpRequest's URI
.build(); // builds and returns an HttpRequest
Other request method values are outside the scope of this reading;
however, readers who are interested should note that
HttpRequest.Builder
does include Java methods to specify a different
request method value if “GET” is not what you need.
Here is an example that builds an HttpRequest
for an image:
URI location = URI.create("http://csweb.cs.uga.edu/~mec/cs1302/gui/pikachu.png");
HttpRequest request = HttpRequest.newBuilder()
.uri(location) // sets this HttpRequest's request URI
.build(); // builds and returns an HttpRequest.
Note
The classes and interfaces in java.net.http
use the URI
class to represent location / address information for web
content. You are likely familiar with the concept of a URL; all
URLs are also URIs.
Note
The cs1302.web/cs1302.web.Example1
program provided in this
chapter’s examples demonstrates how to
build a request for an image and create a JavaFX Image
object
using the data included in the body of the associated response.
Some HTTP servers host Application Programming Interfaces (APIs) that we
can interact with using HTTP requests — instead of a URI referring to a
“page” or “file”, it refers to structured “data” that our program might
leverage to accomplish some goal. For example, the GitHub REST API
provides URIs for accessing information stored by GitHub. Since GitHub
supports many open source projects, their API provides a URI for
structured data about open source software licenses. The example below
builds an HttpRequest
to get a license (in this case, the MIT
license) using the GitHub REST API. According to GitHub’s API
documentation, they recommend setting the “Accept” header when building
a request — headers are one way to provide an HTTP server with more
information about a request. Here is the code:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/licenses/MIT"))
.header("Accept", "application/vnd.github.v3.text-match+json")
.build();
Note
The cs1302.web/cs1302.web.Example2
program provided in this
chapter’s examples
demonstrates how to build a request for license data and use the
Google Gson library to parse the JSON-formatted string included
in the body of the associated response.
Some HTTP servers also let you specify request metadata using a
special query string included near the end of the request
URI. Special care must be taken when including a query string in a URI
so that the metadata values are encoded properly using a combination
of URLEncoder.encode
and StandardCharsets.UTF_8
. The example
below builds an HttpRequest
that queries the iTunes Search API for
up to 5 records related to “Daft Punk”.
String term = URLEncoder.encode("daft punk", StandardCharsets.UTF_8); // "daft+punk"
String limit = URLEncoder.encode("5", StandardCharsets.UTF_8); // "5"
String query = String.format("?term=%s&limit=%s", term, limit); // "?term=daft+punk&limit=5"
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://itunes.apple.com/search" + query))
.build();
Note
The cs1302.web/cs1302.web.Example3
program provided in this
chapter’s examples
demonstrates how to build a request for the iTunes Search API and
use the Google Gson library to parse the JSON-formatted string
included in the body of the associated response.
16.2.2. HttpClient
¶
The HttpClient class provided by java.net.http includes a send
method to send an HTTP request message (described by an
HttpRequest
object) and return the corresponding HTTP response
message (described as an HttpResponse<T>
object). The
HttpClient
class also uses the builder pattern for object
creation.
Here is a quick example that builds an HttpClient
with preferred,
modern settings:
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // uses HTTP protocol version 2 where possible
.followRedirects(HttpClient.Redirect.NORMAL) // always redirects, except from HTTPS to HTTP
.build(); // builds and returns an HttpClient
Since a single HttpClient
object can be used to send multiple
requests, you are encouraged to only create one HttpClient
object
for your program, unless a specific need to do otherwise arises — you
might do this by defining a static constant:
public static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // uses HTTP protocol version 2 where possible
.followRedirects(HttpClient.Redirect.NORMAL) // always redirects, except from HTTPS to HTTP
.build(); // builds and returns an HttpClient
Once built, an HttpClient object’s send
method can be called to
send an HttpRequest
; when doing so, an
HttpResponse.BodyHandler<T>
must also be supplied so that the
HttpClient
object knows how to construct the HttpResponse<T>
object it creates for the response message. The
HttpResponse.BodyHandlers
class provides some static methods to
create create commonly used HttpResponse.BodyHandler<T>
objects:
Method |
Response Type |
Response Body Type |
---|---|---|
|
|
|
|
|
|
In the example below, we access a copy of The Adventures of Sherlock
Holmes by Arthur Conan Doyle that is hosted by Project Gutenberg — when
we send the request, we use BodyHandlers.ofString()
to inform the
client that we want it to interpret the body of the response (i.e., the
response content) as a string.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.gutenberg.org/files/1661/1661-0.txt"))
.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, BodyHandlers.ofString());
String body = response.body();
Note
The cs1302.web/cs1302.web.Example0
program provided in this
chapter’s examples is a
rewritten version of the code above so that you can see it
alongside the required exception handling.
16.2.3. HttpResponse<T>
¶
If an HTTP response message is received, then the program must decide what to do based on the information contained in that response message. This information can be accessed by calling methods on the associated HttpResponse object. Here are some typical examples:
|
Details & Notes |
---|---|
|
The HTTP “status code” integer
indicates whether the response should
be considered successful or not. A
value of |
|
The body of an HTTP request is the
content of the response. The return
type of |
Here is a generic method that you can use to throw an exception if the
status code of a supplied response is not 200
(OK) — you can see
it used in several of the code examples provided in this chapter’s
examples section:
/**
* Throw an {@link java.io.IOException} if the HTTP status code of the
* {@link java.net.http.HttpResponse} supplied by {@code response} is
* not {@code 200 OK}.
* @param <T> response body type
* @param response response to check
* @see <a href="https://httpwg.org/specs/rfc7231.html#status.200">[RFC7232] 200 OK</a>
*/
private static <T> void ensureGoodResponse(HttpResponse<T> response) throws IOException {
if (response.statusCode() != 200) {
throw new IOException(response.toString());
} // if
} // ensureGoodResponse