HTTP/2 Server Push

Welcome to the last blog post of the HTTP blog post series, namely Server Push!

The HTTP client API which came with JDK 11 supports HTTP/2 as a default but also HTTP/1.1. An interesting feature of HTTP/2 is the server push capability. This means that the Web server is allowed to push information to the client before the client requests it. This all can happen if the URLs are provided over the same host name and protocol.

In other words, while in HTTP/1.1 the browser triggers a request to get an HTML page and has to send one request for each referenced resource, in HTTP/2 there is no need for an explicit request from the browser for the referenced resources of an HTML page.

In order to have a better understanding of this concept I will show you an example.

Firstly, I will create a Spring Boot application, which helps us with the server side:

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

Then I create a simple index.html file, which references a CSS file with a background color:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>
  <body>
    <div class="color">Hello, this is a http push example!</div>
  </body>
</html>

The style.css file:

.color {
  background-color: red;
}

Now I will show you the controller class with two methods that react on GET:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PushController {

  @GetMapping(path = "/index.html", produces = "text/html")
  public ResponseEntity<String> serviceWithPush(PushBuilder pushBuilder) {
    String indexHtml = loadResource("index.html");
    if (pushBuilder != null) {
      pushBuilder.path("/style.css").push();
    }
    return new ResponseEntity<String>(indexHtml, HttpStatus.OK);
  }

  @GetMapping(path = "/style.css", produces = "text/css")
  public ResponseEntity<String> resourceToPush() {
    String styleCss = loadResource("style.css");
    return new ResponseEntity<String>(styleCss, HttpStatus.OK);
  }

  public String loadResource(String name) {
    // ...
  }
}

The annotations Controller, GetMapping, ResponseEntity and HttpStatus are from the Spring Framework. Spring will automatically detect the @Controller annotation and will create a controller Spring bean.

The @GetMapping annotation maps HTTP GET requests on particular handler methods. This annotation represents the shortcut for @RequestMapping(method = RequestMethod.GET).

We can see that it is used in both methods in order to map the HTTP GET requests to HTML and CSS files.

Both methods serviceWithPush(PushBuilder pushBuilder) and resourceToPush() load the served resources as a String by some mechanism which is omitted from this example for the sake of brevity.

The HTTP response is represented by a ResponseEntity. The response status can be defined programmatically by returning it with various status codes such as ACCEPTED, BAD_REQUEST, CREATED, NOT_FOUND, NOT_MODIFIED, OK, PROCESSING etc. In this example we use HttpStatus.OK.

Let’s move on to the main class:

public class Http2PushMain {

  public static void main(String[] args) throws Exception {
    Executor executor = ForkJoinPool.commonPool();

    HttpClient httpClient = HttpClient.newBuilder()
            .sslContext(SSLHelper.createSSLContext())
            .executor(executor)
            .build();

    HttpRequest mainRequest = HttpRequest
            .newBuilder()
            .version(Version.HTTP_2)
            .uri(URI.create("https://localhost:8081/index.html"))
            .build();

    Collection<Future<HttpResponse<String>>> futures = ConcurrentHashMap.newKeySet();

    CompletableFuture<HttpResponse<String>> response =
            httpClient.sendAsync(mainRequest, BodyHandlers.ofString(),
                    new PushPromiseHandler<String>() {
                      @Override
                      public void applyPushPromise(HttpRequest initiatingRequest,
                                                   HttpRequest pushPromiseRequest,
                                                   Function<BodyHandler<String>, CompletableFuture<HttpResponse<String>>> acceptor) {
                        System.out.println("Resource per server push: " + pushPromiseRequest.uri());
                        futures.add(acceptor.apply(BodyHandlers.ofString()));
                      }
                    });
    futures.add(response);

    futures.forEach(f -> {
      try {
        HttpResponse<String> httpResponse = f.get();
        System.out.println("Response URL: " + httpResponse.uri());
        System.out.println("Response Content: " + httpResponse.body());
      } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
      }
    });
  }
}

In the first part we define the httpClient and the mainRequest and we use the asynchronous request mechanism which will return the response as a CompletableFuture<HttpResponse<String>>. If you want to find out more about this you can read my first two articles about HttpClient: (synchronous mechanism,  asynchronous mechanism).

The HTTP/2 feature is supported by using the PushPromiseHandler interface. For each resource the server sends a push promise, which is accepted by calling the specific acceptor function. This refers to the CompletableFuture that completes the response of the promise.

The method applyPushPromise(HttpRequest initiatingRequest, HttpRequest pushPromiseRequest,
Function, CompletableFuture>> acceptor)
is invoked one time for every push promise received. The response URL and the response content will be shown by calling the method httpResponse.uri() respectively httpResponse.body().

Let’s see what the result looks like. First of all you should run the DemoApplication class as a Spring Boot App to start the server and then the Http2PushMain class as a Java application.

The output is:

If you go to https://localhost:8081/index.html you can see the result:

I hope you enjoyed the blog post.

Views All Time
1057
Views Today
4
Short URL for this post: https://blog.oio.de/NMI0z
This entry was posted in Java Basics and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *