HTTP/2 Server Push
source link: https://blog.oio.de/2020/04/09/http-2-server-push/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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) .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,
is invoked one time for every push promise received. The response URL and the response content will be shown by calling the method
Function, CompletableFuture>> acceptor)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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK