PushBuilder


在瀏覽器要請求伺服器時,會經過〈握手協議(Handshaking)〉建立一條 TCP 連線,預設情況下,該次連線進行一次 HTTP 請求與回應,而後關閉 TCP 連線。

因此,瀏覽器在某次 HTTP 請求得到了個 HTML 回應後,若 HTML 中需要 CSS 檔案,瀏覽器必須再度建立連線,發出 HTTP 請求取得 CSS 檔案,而後連線關閉,若 HTML 中還需要有 JavaScript,瀏覽器又要建立連線,發出 HTTP 請求得到回應之後關閉連線 … 此過程重複直到必要的資源都下載完成,每次的請求回應都需要一條連線,在需要對網站效能進行最佳化、對使用者介面的高回應性場合上,著實是很大的負擔。

雖然 HTTP/1.1 支援管線化(Pipelining),可以在一次的 TCP 連線中,多次對伺服端發出請求,而不用等待伺服端回應,然而,伺服端必須依請求的順序進行回應,如果有某個回應需時較久,之後的回應也就會被延遲,造成 HOL(Head of line)阻塞的問題。

為了加快網頁相關資源的下載,有許多減少請求的招式因應而生,像是合併圖片、CSS、JavaScript,直接將圖片編碼為 BASE64 內插至 HTML 之中,或者是 Domain Sharding 等…

HTTP/2 支援伺服器推送(Server Push),也就是在一次的請求中,允許伺服端主動推送必要的 CSS、JavaScript、圖片等資源到瀏覽器,不用瀏覽器後續再對資源發出請求。

Servlet 4.0 規範中制訂了對 HTTP/2 的支援,在伺服端推送上,提供了 PushBuilder,讓 Servlet 在必要的時候可以主動推送資源。例如:

package cc.openhome;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Optional;

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("/push")
public class Push extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                        throws ServletException, IOException {
        Optional.ofNullable(request.newPushBuilder())
                .ifPresent(pushBuilder -> {
                    pushBuilder.path("avatars/caterpillar.jpg")
                               .addHeader("content-type", "image/jpg")
                               .push();
                });

        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<body>");
        out.println("<img src='avatars/caterpillar.jpg'>");
        out.println("</body>");
        out.println("</html>");
    }
}

你可以透過 HttpServletRequestnewPushBuilder() 取得 PushBuilder 實例,如果 HTTP/2 不可用(瀏覽器或伺服器不支援的情況),那麼 newPushBuilder() 會傳回 null,若能取得 PushBuilder,就可以使用 path()addHeader() 等方式,加入主動推送的資源,然後呼叫 push() 進行推送。

在 Tomcat 9 中,若要啟用 HTTP/2 支援,必須在 server.xml 中設定 Connector,你必須準備好憑證,找到 server.xml 中的這些註解:

<!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
     This connector uses the APR/native implementation which always uses
     OpenSSL for TLS.
     Either JSSE or OpenSSL style configuration may be used. OpenSSL style
     configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
           maxThreads="150" SSLEnabled="true" >
    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
    <SSLHostConfig>
        <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                     certificateFile="conf/localhost-rsa-cert.pem"
                     certificateChainFile="conf/localhost-rsa-chain.pem"
                     type="RSA" />
    </SSLHostConfig>
</Connector>
-->

<Connetor> 的註解去除,設定好你的憑證,重新啟動 Tomcat,就可以用支援 HTTP/2 的瀏覽器測試看看是否可取得 PushBuilder