创徏一个最单的Web应用Q它在根目录下包含一个文本文件indexQ内容ؓ(f)"HTTP/2 Test"?/span>再包含一个简单的ServletQ代码如下:(x)
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主要是定义了(jin)一个ServletQ具体内容如下:(x)
<?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>
该应用的部v路径为jetty-9.3.11/test-base/webapps/test.war。在该WAR文g所在的目录下,创徏一个test.xmlQ其内容如下所C:(x)
<?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服务器,使用默认的HTTP和HTTPS端口Q分别ؓ(f)8080?443?/span>
$ 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
Ҏ(gu)上述日志可知QJetty启用?jin)Web应用test.warQ还启动?jin)两个ServerConnectorQ一个支持h2cQ另一个支持h2。值得注意的是Q这两个ServerConnectorq分别支持h2c-17, h2c-16, h2c-15, h2c-14和h2-17, h2-16, h2-15, h2-14。这是因为,HTTP/2在正式发布之前,先后发布?8个草案,其编号ؓ(f)00-17。所以,q里的h2c-XX和h2-XX指的是WXX可案?/span>
3. 客户?/span>
其实最方便的客L(fng)是览器了(jin)。只要用的FireFox或Chrome版本不是太老,肯定都已l支持了(jin)HTTP/2Q而且q一功能是默认打开的。也是_(d)当用FireFox去访问前面所部v的Web应用Ӟ是在用HTTP/2Q但你不?x)感觉到q种变化。用FireFox提供的Developer Tools中的Network工具查看服务器端的响应,?x)发现HTTP版本为HTTP/2.0。但此处希望q个客户端能够提供更Z富的与服务器端进行交互的功能Q那么浏览器ƈ不合适了(jin)?br /> Jetty也实C(jin)支持HTTP/2的客L(fng)Q但q个客户端是一个APIQ需要编写程序去讉KHTTP/2服务器端。而且Q目前该API的设计抽象层ơ较低,需要应用程序员对HTTP/2协议Q比如各UQ有较深入的?jin)解。这对于初涉HTTP/2的开发者来_(d)昄很不合适。本文选择使用C语言~写的一个工P其实也是HTTP/2的客L(fng)实现之一Qcurl?/span>
curl在支持HTTP/2Ӟ实际上是使用?jin)nghttp2的C库,所以需要先安装nghttp2。另外,Z(jin)让curl支持h2Q就必须要有TLS-ALPN的支持。那么,一般地q需要安装OpenSSL 1.0.2+?/span>
|络上关于在Linux下安装支持HTTP/2的curl的资源有很多Q过Eƈ不难Q但有点儿繁Q要安装的依赖比较多Q本文就不赘qC(jin)。如果是使用WindowsQ笔者比较推荐通过Cygwin来安装和使用curl。在Windows中安装Cygwin非常单,在Cygwin中执行各U命令时Q感觉上如同在使用LinuxQ尽它q不是一个虚拟机。通过Cygwin安装curlQ它?x)自动地安装所需的各U依赖程序和库?/span>
在笔者的机器上,通过查看curl的版本会(x)出现如下信息Q?/span>
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
׃可知Q笔者用的curl版本?.50.2Qnghttp2版本?.14.0Q而OpenSSL版本?.0.2h?/span>
4. W一ơ尝?/span>
在第一ơ尝试中Q只需要简单地讉KW?节中部v的Web应用中的?rn)态文本文件indexQ以感受下h2cQ完整命令如下:(x)
$ curl -v --http2 http://localhost:8080/index
在输Z包含有如下的内容Q?/span>
...
> 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
">"是客L(fng)发送的hQ?<"是服务器端发送的响应Q?*"是curl对当前过E的说明?/span>l合本系?a href="http://www.aygfsteel.com/jiangshachina/archive/2016/09/19/431811.html">W一文?/a>中所q的HTTP 2协议Q可以有以下的基本理解?/span>
[1]客户端发起了(jin)一个HTTP/1.1的请求,其中携带有Upgrade头部Q要求服务器端升U到HTTP/2(h2c)?/span>
> 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]服务器端同意升Q返回响?101 Switching Protocols"Q然后客L(fng)收到?01响应QHTTP/2q接q行认?/span>
< HTTP/1.1 101 Switching Protocols
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
[3]服务器端响应最l结果。状态行中出现的HTTP版本为HTTP/2Q状态代码ؓ(f)200Q且后面没有跟着"OK"。最后输Z(jin)index文g的内?HTTP/2 Test"?/span>
< 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
5. 一个局?/span>
q次Q在发v的请求中包含体部Q命令如下:(x)
$ curl -v --http2 -d "body" http://localhost:8080/index
在输Z包含有如下的内容Q?/span>
...
> 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节中的输?gu)行比较,会(x)发现缺?jin)"101 Switching Protocols"那一D,而且最l响应状态行中出现的HTTP版本是HTTP/1.1。这p明服务器端不同意升Q后面(h)l用HTTP/1.1。刚刚部|的Jetty未做M改变怎么?x)突然不支持HTTP/2?jin)呢Q或者这是curl的问题?其实Q?/span>q是因ؓ(f)Jetty服务器端在实现h2c时不支持h中包含体部。另外,Apache httpd也有同样的问题。如果是使用h2Q则没有q个限制。这背后的原因超Z(jin)本文的范_(d)不作表述?/span>
6. 一个Bug
在这ơ尝试中Q测试一下两端对100-continue的支持。如果请求中使用?jin)头?Expect: 100-continue"Q那么正常地该请求要有体部。但׃在第5节中介绍的问题,此时不能再用h2cQ而只能用h2。另外,q次不访问静(rn)态文Ӟ而是讉KServlet(此处?test)。完整命令如下:(x)
$ curl -vk --http2 -H "Expect: 100-continue" -d "body" https://localhost:8443/test
在输出的最后出C(jin)如下信息Q?/span>
curl: (92) HTTP/2 stream 1 was not closed cleanly: CANCEL (err 8)
q其实是Jetty的一?a >BugQ正在开发中?.3.12已经修复?jin)它?/span>
7. 结
HTTP/2依然是新潮的技术,对各家的实现Q无论是服务器端Q客L(fng)Q还是分析工P都要持有一份怀疑态度。这些实现和工具都是E序Q都有可能存在bug。而且协议对许多细节没有作?gu)定,各家都?x)发挥自己的想像力。比如,Apache httpd和Jetty在实现服务器端推送时Q其方式׃相同?br /> 在开发自qHTTP/2实现或应用的时候,需要同时用已有的不同服务器端和客L(fng)去部|多套测试环境进行对比分析?br />