探索HTTP/2: 初試HTTP/2
目前支持HTTP/2的服務(wù)器端與客戶端實現(xiàn)已有不少,探索HTTP/2系列的第二篇就分別以Jetty和curl作為服務(wù)器端和客戶端,描述了HTTP/2測試環(huán)境的搭建過程。本文還將使用這個測試環(huán)境去展示Jetty在實現(xiàn)HTTP/2時的一個局限和一個Bug。(2016.09.22最后更新)1. HTTP/2的實現(xiàn)
目前已經(jīng)有眾多的服務(wù)器端和客戶端實現(xiàn)了對HTTP/2的支持。在服務(wù)器端,著名的Apache httpd從2.4.17版,Nginx從1.9.5版,開始支持HTTP/2。在客戶端,主流的瀏覽器,如Chrome,F(xiàn)ireFox和IE,的最新版均支持HTTP/2,但它們都只支持運行在TLS上的HTTP/2(即h2)。使用Java語言實現(xiàn)的,則有Jetty和Netty,它們都實現(xiàn)了服務(wù)器端和客戶端。此處有一份HTTP/2實現(xiàn)的列表:https://github.com/http2/http2-spec/wiki/Implementations
另外,還有一些工具支持對HTTP/2的分析與調(diào)試,如curl和WireShark。這里也有一份此類工具的列表:https://github.com/http2/http2-spec/wiki/Tools
2. 服務(wù)器端
作為Java程序員,選用一款使用Java語言編寫的開源HTTP/2服務(wù)器端實現(xiàn)似乎是很自然的結(jié)果。實際上,在日后的研究中,我們也需要查看服務(wù)器端的源代碼。這對于深入地理解HTTP/2,并發(fā)現(xiàn)實現(xiàn)中可能的問題,具有現(xiàn)實意義。
本文選擇Jetty的最新版本9.3.11作為服務(wù)器端。Jetty是一個成熟的Servlet容器,這為開發(fā)Web應(yīng)用程序提供了極大便利。而本文第1節(jié)中提到的Netty是一個傳輸層框架,它專注于網(wǎng)絡(luò)程序。可以使用Netty去開發(fā)一個Servlet容器,但這顯然不如直接使用Jetty方便。
安裝和配置Jetty是一件很容易的事情,具體過程如下所示。
假設(shè)此時已經(jīng)下載并解壓好了Jetty 9.3.11的壓縮文件,目錄名為jetty-9.3.11。在其中創(chuàng)建一個test-base子目錄,作為將要創(chuàng)建的Jetty Base的目錄。
$ cd jetty-9.3.11
$ mkdir test-base
$ cd test-base
在創(chuàng)建Base時,加入支持http,https,http2(h2),http2c(h2c)和deploy的模塊。$ mkdir test-base
$ cd test-base
$ java -jar ../start.jar --add-to-startd=http,https,http2,http2c,deploy
ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: alpn
+ ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
+ ALPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
+ http://github.com/jetty-project/jetty-alpn
+ http://openjdk.java.net/legal/gplv2+ce.html
Proceed (y/N)? y
INFO: server initialised (transitively) in ${jetty.base}\start.d\server.ini
INFO: http initialised in ${jetty.base}\start.d\http.ini
INFO: ssl initialised (transitively) in ${jetty.base}\start.d\ssl.ini
INFO: alpn initialised (transitively) in ${jetty.base}\start.d\alpn.ini
INFO: http2c initialised in ${jetty.base}\start.d\http2c.ini
INFO: https initialised in ${jetty.base}\start.d\https.ini
INFO: deploy initialised in ${jetty.base}\start.d\deploy.ini
INFO: http2 initialised in ${jetty.base}\start.d\http2.ini
DOWNLOAD: http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.5.v20150921/alpn-boot-8.1.5.v20150921.jar to ${jetty.base}\lib\alpn\alpn-boot-8.1.5.v20150921.jar
DOWNLOAD: https://raw.githubusercontent.com/eclipse/jetty.project/master/jetty-server/src/test/config/etc/keystore?id=master to ${jetty.base}\etc\keystore
MKDIR: ${jetty.base}\webapps
INFO: Base directory was modified
注意,在上述過程中,會根據(jù)當(dāng)前環(huán)境變量中使用的Java版本(此處為1.8.0_60)去下載一個對應(yīng)的TLS-ALPN實現(xiàn)jar文件(此處為alpn-boot-8.1.5.v20150921.jar),該jar會用于對h2的支持。當(dāng)啟動Jetty時,該jar會被Java的Bootstrap class loader加載到類路徑中。ALERT: There are enabled module(s) with licenses.
The following 1 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: alpn
+ ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
+ ALPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
+ http://github.com/jetty-project/jetty-alpn
+ http://openjdk.java.net/legal/gplv2+ce.html
Proceed (y/N)? y
INFO: server initialised (transitively) in ${jetty.base}\start.d\server.ini
INFO: http initialised in ${jetty.base}\start.d\http.ini
INFO: ssl initialised (transitively) in ${jetty.base}\start.d\ssl.ini
INFO: alpn initialised (transitively) in ${jetty.base}\start.d\alpn.ini
INFO: http2c initialised in ${jetty.base}\start.d\http2c.ini
INFO: https initialised in ${jetty.base}\start.d\https.ini
INFO: deploy initialised in ${jetty.base}\start.d\deploy.ini
INFO: http2 initialised in ${jetty.base}\start.d\http2.ini
DOWNLOAD: http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.5.v20150921/alpn-boot-8.1.5.v20150921.jar to ${jetty.base}\lib\alpn\alpn-boot-8.1.5.v20150921.jar
DOWNLOAD: https://raw.githubusercontent.com/eclipse/jetty.project/master/jetty-server/src/test/config/etc/keystore?id=master to ${jetty.base}\etc\keystore
MKDIR: ${jetty.base}\webapps
INFO: Base directory was modified
創(chuàng)建一個最簡單的Web應(yīng)用,使它在根目錄下包含一個文本文件index,內(nèi)容為"HTTP/2 Test"。再包含一個簡單的Servlet,代碼如下:
package test;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 5222793251610509039L;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Test");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
web.xml主要是定義了一個Servlet,具體內(nèi)容如下:import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 5222793251610509039L;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().println("Test");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false" version="3.1">
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>test.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
</web-app>
該應(yīng)用的部署路徑為jetty-9.3.11/test-base/webapps/test.war。在該WAR文件所在的目錄下,創(chuàng)建一個test.xml,其內(nèi)容如下所示:<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false" version="3.1">
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>test.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/</Set>
<Set name="war"><SystemProperty name="jetty.base" default="."/>/webapps/test.war</Set>
</Configure>
啟動Jetty服務(wù)器,使用默認(rèn)的HTTP和HTTPS端口,分別為8080和8443。<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/</Set>
<Set name="war"><SystemProperty name="jetty.base" default="."/>/webapps/test.war</Set>
</Configure>
$ java -jar ../start.jar
2016-09-15 21:15:51.190:INFO:oejs.Server:main: jetty-9.3.11.v20160721
2016-09-15 21:15:51.237:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/] at interval 1
2016-09-15 21:15:52.251:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /test.war, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-09-15 21:15:52.313:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@4520ebad{/test.war,file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/test.war/,AVAILABLE}{D:\http2\jetty\jetty-9.3.11\test-base\webapps\test.war}
2016-09-15 21:15:52.391:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-09-15 21:15:52.391:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@711f39f9{/,file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/test.war/,AVAILABLE}{/test.war}
2016-09-15 21:15:52.532:INFO:oejs.AbstractConnector:main: Started ServerConnector@1b68ddbd{HTTP/1.1,[http/1.1, h2c, h2c-17, h2c-16, h2c-15, h2c-14]}{0.0.0.0:8080}
2016-09-15 21:15:52.735:INFO:oejus.SslContextFactory:main: x509=X509@e320068(jetty,h=[jetty.eclipse.org],w=[]) for SslContextFactory@1f57539(file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore,file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore)
2016-09-15 21:15:52.735:INFO:oejus.SslContextFactory:main: x509=X509@76f2b07d(mykey,h=[],w=[]) for SslContextFactory@1f57539(file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore,file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore)
2016-09-15 21:15:53.234:INFO:oejs.AbstractConnector:main: Started ServerConnector@4b168fa9{SSL,[ssl, alpn, h2, h2-17, h2-16, h2-15, h2-14, http/1.1]}{0.0.0.0:8443}
2016-09-15 21:15:53.249:INFO:oejs.Server:main: Started @3940ms
根據(jù)上述日志可知,Jetty啟用了Web應(yīng)用test.war,還啟動了兩個ServerConnector,一個支持h2c,另一個支持h2。值得注意的是,這兩個ServerConnector還分別支持h2c-17, h2c-16, h2c-15, h2c-14和h2-17, h2-16, h2-15, h2-14。這是因為,HTTP/2在正式發(fā)布之前,先后發(fā)布了18個草案,其編號為00-17。所以,這里的h2c-XX和h2-XX指的就是第XX號草案。2016-09-15 21:15:51.190:INFO:oejs.Server:main: jetty-9.3.11.v20160721
2016-09-15 21:15:51.237:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/] at interval 1
2016-09-15 21:15:52.251:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /test.war, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-09-15 21:15:52.313:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@4520ebad{/test.war,file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/test.war/,AVAILABLE}{D:\http2\jetty\jetty-9.3.11\test-base\webapps\test.war}
2016-09-15 21:15:52.391:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-09-15 21:15:52.391:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@711f39f9{/,file:///D:/http2/jetty/jetty-9.3.11/test-base/webapps/test.war/,AVAILABLE}{/test.war}
2016-09-15 21:15:52.532:INFO:oejs.AbstractConnector:main: Started ServerConnector@1b68ddbd{HTTP/1.1,[http/1.1, h2c, h2c-17, h2c-16, h2c-15, h2c-14]}{0.0.0.0:8080}
2016-09-15 21:15:52.735:INFO:oejus.SslContextFactory:main: x509=X509@e320068(jetty,h=[jetty.eclipse.org],w=[]) for SslContextFactory@1f57539(file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore,file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore)
2016-09-15 21:15:52.735:INFO:oejus.SslContextFactory:main: x509=X509@76f2b07d(mykey,h=[],w=[]) for SslContextFactory@1f57539(file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore,file:///D:/http2/jetty/jetty-9.3.11/test-base/etc/keystore)
2016-09-15 21:15:53.234:INFO:oejs.AbstractConnector:main: Started ServerConnector@4b168fa9{SSL,[ssl, alpn, h2, h2-17, h2-16, h2-15, h2-14, http/1.1]}{0.0.0.0:8443}
2016-09-15 21:15:53.249:INFO:oejs.Server:main: Started @3940ms
3. 客戶端
其實最方便的客戶端就是瀏覽器了。只要使用的FireFox或Chrome版本不是太老,肯定都已經(jīng)支持了HTTP/2,而且這一功能是默認(rèn)打開的。也就是說,當(dāng)使用FireFox去訪問前面所部署的Web應(yīng)用時,就是在使用HTTP/2,但你不會感覺到這種變化。使用FireFox提供的Developer Tools中的Network工具查看服務(wù)器端的響應(yīng),會發(fā)現(xiàn)HTTP版本為HTTP/2.0。但此處希望這個客戶端能夠提供更為豐富的與服務(wù)器端進(jìn)行交互的功能,那么瀏覽器就并不合適了。
Jetty也實現(xiàn)了支持HTTP/2的客戶端,但這個客戶端是一個API,需要編寫程序去訪問HTTP/2服務(wù)器端。而且,目前該API的設(shè)計抽象層次較低,需要應(yīng)用程序員對HTTP/2協(xié)議,比如各種幀,有較深入的了解。這對于初涉HTTP/2的開發(fā)者來說,顯然很不合適。本文選擇使用C語言編寫的一個工具,其實也是HTTP/2的客戶端實現(xiàn)之一,curl。
curl在支持HTTP/2時,實際上是使用了nghttp2的C庫,所以需要先安裝nghttp2。另外,為了讓curl支持h2,就必須要有TLS-ALPN的支持。那么,一般地還需要安裝OpenSSL 1.0.2+。
網(wǎng)絡(luò)上關(guān)于在Linux下安裝支持HTTP/2的curl的資源有很多,過程并不難,但有點兒繁,要安裝的依賴比較多,本文就不贅述了。如果是使用Windows,筆者比較推薦通過Cygwin來安裝和使用curl。在Windows中安裝Cygwin非常簡單,在Cygwin中執(zhí)行各種命令時,感覺上就如同在使用Linux,盡管它并不是一個虛擬機(jī)。通過Cygwin安裝curl,它會自動地安裝所需的各種依賴程序和庫。
在筆者的機(jī)器上,通過查看curl的版本會出現(xiàn)如下信息:
curl 7.50.2 (x86_64-unknown-cygwin) libcurl/7.50.2 OpenSSL/1.0.2h zlib/1.2.8 libidn/1.29 libpsl/0.14.0 (+libidn/1.29) libssh2/1.7.0 nghttp2/1.14.0
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: Debug IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets Metalink PSL
由上可知,筆者使用的curl版本是7.50.2,nghttp2版本是1.14.0,而OpenSSL版本是1.0.2h。Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: Debug IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets Metalink PSL
4. 第一次嘗試
在第一次嘗試中,只需要簡單地訪問第2節(jié)中部署的Web應(yīng)用中的靜態(tài)文本文件index,以感受下h2c,完整命令如下:
$ curl -v --http2 http://localhost:8080/index
在輸出中包含有如下的內(nèi)容:...
> GET /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
>
...
< HTTP/1.1 101 Switching Protocols
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
...
< HTTP/2 200
< server: Jetty(9.3.11.v20160721)
< last-modified: Wed, 14 Sep 2016 12:52:32 GMT
< content-length: 11
< accept-ranges: bytes
<
...
HTTP/2 Test
">"是客戶端發(fā)送的請求,"<"是服務(wù)器端發(fā)送的響應(yīng),而"*"是curl對當(dāng)前過程的說明。結(jié)合本系列第一篇文章中所簡述的HTTP 2協(xié)議,可以有以下的基本理解。> GET /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
>
...
< HTTP/1.1 101 Switching Protocols
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
...
< HTTP/2 200
< server: Jetty(9.3.11.v20160721)
< last-modified: Wed, 14 Sep 2016 12:52:32 GMT
< content-length: 11
< accept-ranges: bytes
<
...
HTTP/2 Test
[1]客戶端發(fā)起了一個HTTP/1.1的請求,其中攜帶有Upgrade頭部,要求服務(wù)器端升級到HTTP/2(h2c)。
> GET /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
>
[2]服務(wù)器端同意升級,返回響應(yīng)"101 Switching Protocols",然后客戶端收到了101響應(yīng),HTTP/2連接進(jìn)行確認(rèn)。> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
>
< HTTP/1.1 101 Switching Protocols
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
[3]服務(wù)器端響應(yīng)最終結(jié)果。狀態(tài)行中出現(xiàn)的HTTP版本為HTTP/2,狀態(tài)代碼為200,且后面沒有跟著"OK"。最后輸出了index文件的內(nèi)容"HTTP/2 Test"。* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
< HTTP/2 200
< server: Jetty(9.3.11.v20160721)
< last-modified: Wed, 14 Sep 2016 12:52:32 GMT
< content-length: 11
< accept-ranges: bytes
<
...
HTTP/2 Test
< server: Jetty(9.3.11.v20160721)
< last-modified: Wed, 14 Sep 2016 12:52:32 GMT
< content-length: 11
< accept-ranges: bytes
<
...
HTTP/2 Test
5. 一個局限
這次,在發(fā)起的請求中包含體部,命令如下:
$ curl -v --http2 -d "body" http://localhost:8080/index
在輸出中包含有如下的內(nèi)容:...
> POST /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
...
< HTTP/1.1 200 OK
< Last-Modified: Wed, 14 Sep 2016 12:52:32 GMT
< Accept-Ranges: bytes
< Content-Length: 11
...
HTTP/2 Test
和第4節(jié)中的輸出進(jìn)行比較,會發(fā)現(xiàn)缺少了"101 Switching Protocols"那一段,而且最終響應(yīng)狀態(tài)行中出現(xiàn)的HTTP版本是HTTP/1.1。這就說明服務(wù)器端不同意升級,后面繼續(xù)使用HTTP/1.1。剛剛部署的Jetty未做任何改變怎么會突然不支持HTTP/2了呢?或者這是curl的問題?其實,這是因為Jetty服務(wù)器端在實現(xiàn)h2c時不支持請求中包含體部。另外,Apache httpd也有同樣的問題。如果是使用h2,則沒有這個限制。這背后的原因超出了本文的范疇,不作表述。> POST /index HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.2
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
> Content-Length: 4
> Content-Type: application/x-www-form-urlencoded
>
...
< HTTP/1.1 200 OK
< Last-Modified: Wed, 14 Sep 2016 12:52:32 GMT
< Accept-Ranges: bytes
< Content-Length: 11
...
HTTP/2 Test
6. 一個Bug
在這次嘗試中,測試一下兩端對100-continue的支持。如果請求中使用了頭部"Expect: 100-continue",那么正常地該請求要有體部。但由于在第5節(jié)中介紹的問題,此時不能再使用h2c,而只能使用h2。另外,這次不訪問靜態(tài)文件,而是訪問Servlet(此處為/test)。完整命令如下:
$ curl -vk --http2 -H "Expect: 100-continue" -d "body" https://localhost:8443/test
在輸出的最后出現(xiàn)了如下信息:curl: (92) HTTP/2 stream 1 was not closed cleanly: CANCEL (err 8)
這其實是Jetty的一個Bug,正在開發(fā)中的9.3.12已經(jīng)修復(fù)了它。7. 小結(jié)
HTTP/2依然算是新潮的技術(shù),對各家的實現(xiàn),無論是服務(wù)器端,客戶端,還是分析工具,都要持有一份懷疑態(tài)度。這些實現(xiàn)和工具都是程序,都有可能存在bug。而且協(xié)議對許多細(xì)節(jié)沒有作出規(guī)定,各家都會發(fā)揮自己的想像力。比如,Apache httpd和Jetty在實現(xiàn)服務(wù)器端推送時,其方式就不盡相同。
在開發(fā)自己的HTTP/2實現(xiàn)或應(yīng)用的時候,需要同時使用已有的不同服務(wù)器端和客戶端去部署多套測試環(huán)境進(jìn)行對比分析。