268. HTTP/2 server push

Besides multiplexing, another powerful feature of HTTP/2 is its server push capability.

Mainly, in the traditional approach (HTTP/1.1), a browser triggers a request for getting an HTML page and parses the received markup to identify the referenced resources (for example, JS, CSS, images, and so on). To fetch these resources, the browser sends additional requests (one request for each referenced resource). On the other hand, HTTP/2 sends the HTML page and the referenced resources without explicit requests from the browser. So, the browser requests the HTML page and receives the page and everything else that's needed for displaying the page.

The HTTP Client API supports this HTTP/2 feature via the PushPromiseHandler interface. The implementation of this interface must be given as the third argument of the send() or sendAsync() method.

PushPromiseHandler relies on three coordinates, as follows:

  • The initiating client send request (initiatingRequest)
  • The synthetic push request (pushPromiseRequest)
  • The acceptor function, which must be successfully invoked to accept the push promise (acceptor)

A push promise is accepted by invoking the given acceptor function. The acceptor function must be passed a non-null BodyHandler, which is used to handle the promise's response body. The acceptor function will return a CompletableFuture instance that completes the promise's response.

Based on this information, let's look at an implementation of PushPromiseHandler:

private static final List<CompletableFuture<Void>>
asyncPushRequests = new CopyOnWriteArrayList<>();
...
private static HttpResponse.PushPromiseHandler<String>
pushPromiseHandler() {

return (HttpRequest initiatingRequest,
HttpRequest pushPromiseRequest,
Function<HttpResponse.BodyHandler<String> ,
CompletableFuture<HttpResponse<String>>> acceptor) -> {
CompletableFuture<Void> pushcf =
acceptor.apply(HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(
" Pushed resource body: " + b));

asyncPushRequests.add(pushcf);

System.out.println(" Just got promise push number: " +
asyncPushRequests.size());
System.out.println(" Initial push request: " +
initiatingRequest.uri());
System.out.println("Initial push headers: " +
initiatingRequest.headers());
System.out.println("Promise push request: " +
pushPromiseRequest.uri());
System.out.println("Promise push headers: " +
pushPromiseRequest.headers());
};
}

Now, let's trigger a request and pass this PushPromiseHandler to sendAsync():

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://http2.golang.org/serverpush"))
.build();

client.sendAsync(request,
HttpResponse.BodyHandlers.ofString(), pushPromiseHandler())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(" Main resource: " + b))
.join();

asyncPushRequests.forEach(CompletableFuture::join);

System.out.println(" Fetched a total of " +
asyncPushRequests.size() + " push requests");

If we want to return a push promise handler that accumulates push promises, and their responses, into the given map, then we can rely on the PushPromiseHandler.of() method, as follows:

private static final ConcurrentMap<HttpRequest,
CompletableFuture<HttpResponse<String>>> promisesMap
= new ConcurrentHashMap<>();

private static final Function<HttpRequest,
HttpResponse.BodyHandler<String>> promiseHandler
= (HttpRequest req) -> HttpResponse.BodyHandlers.ofString();

public static void main(String[] args)
throws IOException, InterruptedException {

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://http2.golang.org/serverpush"))
.build();

client.sendAsync(request,
HttpResponse.BodyHandlers.ofString(), pushPromiseHandler())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(" Main resource: " + b))
.join();

System.out.println(" Push promises map size: " +
promisesMap.size() + " ");

promisesMap.entrySet().forEach((entry) -> {
System.out.println("Request = " + entry.getKey() +
", Response = " + entry.getValue().join().body());
});
}

private static HttpResponse.PushPromiseHandler<String>
pushPromiseHandler() {

return HttpResponse.PushPromiseHandler
.of(promiseHandler, promisesMap);
}

In both solutions of the preceding solutions, we have used a BodyHandler of the String type via ofString(). This is not very useful if the server pushes binary data as well (for example, images). So, if we are dealing with binary data, we need to switch to BodyHandler of the byte[] type via ofByteArray(). Alternatively, we can send the pushed resources to disk via ofFile(), as shown in the following solution, which is an adapted version of the preceding solution:

private static final ConcurrentMap<HttpRequest,
CompletableFuture<HttpResponse<Path>>>
promisesMap = new ConcurrentHashMap<>();

private static final Function<HttpRequest,
HttpResponse.BodyHandler<Path>> promiseHandler
= (HttpRequest req) -> HttpResponse.BodyHandlers.ofFile(
Paths.get(req.uri().getPath()).getFileName());

public static void main(String[] args)
throws IOException, InterruptedException {

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://http2.golang.org/serverpush"))
.build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(
Path.of("index.html")), pushPromiseHandler())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(" Main resource: " + b))
.join();

System.out.println(" Push promises map size: " +
promisesMap.size() + " ");

promisesMap.entrySet().forEach((entry) -> {
System.out.println("Request = " + entry.getKey() +
", Response = " + entry.getValue().join().body());
});
}

private static HttpResponse.PushPromiseHandler<Path>
pushPromiseHandler() {

return HttpResponse.PushPromiseHandler
.of(promiseHandler, promisesMap);
}

The preceding code should save the pushed resources in the application classpath, as shown in the following screenshot:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.180.43