Server Push là một tính năng mới giúp cải tiến về mặt performance cho ứng dụng web được định nghĩa trong chuẩn HTTP/2. Nó giúp chúng ta có thể đẩy các hình ảnh, tập tin CSS, tập tin Javascript và những resource khác về client trước khi request từ client được xử lý hoàn tất. Nhờ vậy, ngay khi response kết quả từ server về client, những resource này đã có sẵn trong cache và có thể sử dụng luôn. Server Push được bao gồm trong release của Java Servlet 4.x. Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về Server Push trong Java hoạt động như thế nào các bạn nhé!
Đầu tiên, mình sẽ tạo một project cho ứng dụng web trong Eclipse để làm ví dụ như sau:
Để thấy rõ lợi ích của Server Push trong Java Servlet 4.x, giờ mình sẽ tạo một Servlet bình thường trước:
với nội dung như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.huongdanjava.serverpush; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/hello") public class HelloServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Setting up the content type of web page resp.setContentType("text/html"); // Writing the message on the web page PrintWriter out = resp.getWriter(); out.println("<img src='/java-server-push/logo.png'>"); out.println("<h1>Hello World from Huong Dan Java</h1>"); } } |
Trong HelloServlet trên, như các bạn thấy, ngoài dòng chữ “Hello World from Huong Dan Java”, mình còn return về cho client một hình ảnh là logo của Hướng Dẫn Java. Logo này nằm ở thư mục WebContent của project:
Lúc này, nếu chạy ứng dụng với Tomcat 9 trở lên, các bạn sẽ thấy kết quả như sau:
Nếu các bạn sử dụng Developer Tools của trình duyệt Chrome bằng cách chọn More Tools, sau đó chọn Developer Tools, rồi chọn tab Networks. Các bạn sẽ thấy như sau:
Bây giờ, chúng ta sẽ sử dụng tính năng Server Push của Java Servlet 4.x xem sao nhé các bạn.
Trong Java Servlet 4.x, interface PushBuilder được thêm vào để hiện thực tính năng Server Push của HTTP/2. Nội dung của interface này như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public interface PushBuilder { public PushBuilder method(String method); public PushBuilder queryString(String queryString); public PushBuilder sessionId(String sessionId); public PushBuilder setHeader(String name, String value); public PushBuilder addHeader(String name, String value); public PushBuilder removeHeader(String name); public PushBuilder path(String path); public void push(); public String getMethod(); public String getQueryString(); public String getSessionId(); public Set getHeaderNames(); public String getHeader(String name); public String getPath(); } |
Chúng ta có thể lấy được đối tượng PushBuilder trong Servlet của chúng ta từ đối tượng HttpServletRequest bằng cách sử dụng method newPushBuilder() của đối tượng này như sau:
1 |
req.newPushBuilder(); |
Sau khi đã có đối tượng PushBuilder, chúng ta có thể sử dụng nó để đẩy tất cả các resource trong ứng dụng web của chúng ta như hình ảnh, tập tin CSS, tập tin Javascript về client.
Đầu tiên, các bạn cần khai báo đường dẫn của resource mà chúng ta muốn đẩy về client trước bằng phương thức path() của đối tượng PushBuilder như sau:
1 2 |
req.newPushBuilder() .path("/java-server-push/logo.png"); |
Các bạn có thể gọi nhiều lần phương thức path() để push nhiều resources cùng một lúc cũng được.
Sau khi đã khai báo tất cả các resource cần push thì các bạn hãy gọi phương thức push() của đối tượng PushBuilder để bắt đầu đẩy resource về client.
1 2 3 |
req.newPushBuilder() .path("/java-server-push/logo.png") .push(); |
Sau khi đã push resource xong, các bạn có thể tiếp tục xử lý để return về toàn bộ nội dung của trang web. Ví dụ HelloServlet của mình có thể viết lại như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.huongdanjava.serverpush; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/hello") public class HelloServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.newPushBuilder() .path("/java-server-push/logo.png") .push(); // Setting up the content type of web page resp.setContentType("text/html"); // Writing the message on the web page PrintWriter out = resp.getWriter(); out.println("<img src='/java-server-push/logo.png'>"); out.println("<h1>Hello World from Huong Dan Java</h1>"); } } |
Trước khi chạy lại ví dụ của chúng ta với Server Push, mình xin nói thêm một điều với các bạn là Server Push chỉ hoạt động với các kết nối bảo mật như HTTPS connection và nếu client disable Server Push thì Server Push cũng không hoạt động được các bạn ạ. Trong những trường hợp này, nếu các bạn sử dụng đối tượng HttpServletRequest để lấy đối tượng PushBuilder thì kết quả sẽ bị null.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
SEVERE: Servlet.service() for servlet [com.huongdanjava.serverpush.HelloServlet] in context with path [/java-server-push] threw exception java.lang.NullPointerException at com.huongdanjava.serverpush.HelloServlet.doGet(HelloServlet.java:20) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:501) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:754) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1376) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) |
Do đó, để chạy được ví dụ, các bạn phải cấu hình Tomcat hỗ trợ HTTP/2 với giao thức HTTPS. Để làm được điều này, các bạn hãy mở tập tin server.xml trong project Servers:
rồi thêm một Connector với nội dung như sau:
1 2 3 4 5 6 7 |
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="/Users/Khanh/Documents/tomcat_https" keystorePass="123456"> <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/> </Connector> |
Xem thêm hướng dẫn Cấu hình Tomcat hỗ trợ HTTP/2 nhé các bạn.
Bây giờ, các bạn hãy chạy lại ví dụ để xem kết quả nhé.
Kết quả:
Như các bạn thấy, lúc này, protocol là h2 (HTTP/2) và nếu các bạn để ý cột Initiator của tập tin logo.png, các bạn sẽ thấy nó đang sử dụng Server Push đó các bạn. Ví dụ của mình chỉ sử dụng một hình ảnh nhỏ nên các bạn sẽ không thấy sự khác biệt, nhưng đối với một trang web thực tế, có hàng trăm hàng ngàn resources, các bạn sẽ thấy rõ lợi ích của Server Push.