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.
Including Query String Parameters in the URI of an HTTP Request
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();
A query string at the end of a request URI is not the only way to include additional information for a request. It is common for API providers to require that values related to authentication or authorization be included in either the header or body of a request that you send to them, the latter typically only used when a “POST” of “PUT” request is being built.
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.
Including Header Values in an HTTP Request
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 The
GitHub REST API provides URIs for accessing information stored by
GitHub. In the example below, a “GET” request for information about
the open source “MIT” license is built, and the header variable named
Accept is set to "application/vnd.github.v3.text-match+json"
to indicate that the body of the response that GitHub sends back
conforms to a specific format or media type:
String license = "mit";
String location = "https://api.github.com/licenses/" + license;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(location))
.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.
Including a JSON Body in an HTTP POST Request
When building a “POST” request, you likely need to include something
in the body of the request. In addition to setting some information
using headers, the example below uses the BodyPublishers.ofString(String) method to include
JSON data stored in a String in the body of the request that
is being built:
String json = "...";
String location = ...;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(location))
.header("Accept", "application/vnd.github.v3.text-match+json")
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(json))
.build();
The value of the Content-Type header in the example above
indicates what kind of data is stored in the String that is
included in the request body. The example above also illustrates that
multiple headers can be set for a request since values are provided
for both the Accept and Content-Type headers.
Including Form Data in an HTTP POST Request
If the body of a “POST” request is used to store form data (e.g., data that a user has entered into a form), then you typically need to do the following when building the HTTP request:
set the request’s
Content-Typeheader to"application/x-www-form-urlencoded";construct a query string for the form data with URL-encoded values, then include it in body of the the “POST” request using the BodyPublishers.ofString(String) method, just like in the last example.
Although the form data is being represented using a query string in
this situation, it is important to emphasize that it is included only
in the body of the “POST” request and not in the URI. Also, do not
include the ? at the beginning of the query string as that is only
included when a query string appears at the end of a URI. here is
a quick example:
// URL-encoded form data values
String name = URLEncoder.encode("...", StandardCharsets.UTF_8);
String age = URLEncoder.encode("...", StandardCharsets.UTF_8);
// form data query string
String formData = String.format("name=%s&age=%s", name, age);
String location = ...;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(location))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(BodyPublishers.ofString(formData))
.build();
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 response is the
content of the response. The return
type of |
|
The headers of an HTTP response are additional variables and values that the server stored in the response separately from the status code and body. Headers in a response are typically used to provide more context about the body of the response or inform the client how long they should wait before sending subsequent requests. |
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