java http 2客户端_探索HTTP/2: 初试HTTP/2(原)

本文介绍了如何使用Jetty和curl搭建HTTP/2测试环境,展示了Java HTTP/2客户端Jetty的局限性和一个Bug。通过创建简单的Web应用和Servlet,探讨了HTTP/2的服务器端和客户端实现,包括Jetty的启动和配置,以及curl的使用。在测试过程中,遇到了POST请求时的局限性问题和与100-continue期望响应相关的Bug。
摘要由CSDN通过智能技术生成

探索HTTP/2: 初试HTTP/2目前支持HTTP/2的服务器端与客户端实现已有不少,探索HTTP/2系列的第二篇就分别以Jetty和curl作为服务器端和客户端,描述了HTTP/2测试环境的搭建过程。本文还将使用这个测试环境去展示Jetty在实现HTTP/2时的一个局限和一个Bug。(2016.09.22最后更新)

1. HTTP/2的实现

目前已经有众多的服务器端和客户端实现了对HTTP/2的支持。在服务器端,著名的Apache httpd从2.4.17版,Nginx从1.9.5版,开始支持HTTP/2。在客户端,主流的浏览器,如Chrome,FireFox和IE,的最新版均支持HTTP/2,但它们都只支持运行在TLS上的HTTP/2(即h2)。使用Java语言实现的,则有Jetty和Netty,它们都实现了服务器端和客户端。此处有一份HTTP/2实现的列表:https://github.com/http2/http2-spec/wiki/Implementations

另外,还有一些工具支持对HTTP/2的分析与调试,如curl和WireShark。这里也有一份此类工具的列表:https://github.com/http2/http2-spec/wiki/Tools

2. 服务器端

作为Java程序员,选用一款使用Java语言编写的开源HTTP/2服务器端实现似乎是很自然的结果。实际上,在日后的研究中,我们也需要查看服务器端的源代码。这对于深入地理解HTTP/2,并发现实现中可能的问题,具有现实意义。

本文选择Jetty的最新版本9.3.11作为服务器端。Jetty是一个成熟的Servlet容器,这为开发Web应用程序提供了极大便利。而本文第1节中提到的Netty是一个传输层框架,它专注于网络程序。可以使用Netty去开发一个Servlet容器,但这显然不如直接使用Jetty方便。

安装和配置Jetty是一件很容易的事情,具体过程如下所示。

假设此时已经下载并解压好了Jetty 9.3.11的压缩文件,目录名为jetty-9.3.11。在其中创建一个test-base子目录,作为将要创建的Jetty Base的目录。

$ cd jetty-9.3.11$ mkdir test-base$ cd test-base在创建Base时,加入支持http,https,http2(h2),http2c(h2c)和deploy的模块。

$ java -jar ../start.jar --add-to-startd=http,https,http2,http2c,deployALERT: There are enabled module(s) with licenses.The following1module(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 licenseModule: 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.htmlProceed (y/N)? yINFO: server          initialised (transitively) in ${jetty.base}\start.d\server.iniINFO: http            initialised in ${jetty.base}\start.d\http.iniINFO: ssl             initialised (transitively) in ${jetty.base}\start.d\ssl.iniINFO: alpn            initialised (transitively) in ${jetty.base}\start.d\alpn.iniINFO: http2c          initialised in ${jetty.base}\start.d\http2c.iniINFO: https           initialised in ${jetty.base}\start.d\https.iniINFO: deploy          initialised in ${jetty.base}\start.d\deploy.iniINFO: http2           initialised in ${jetty.base}\start.d\http2.iniDOWNLOAD: 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.jarDOWNLOAD: https://raw.githubusercontent.com/eclipse/jetty.project/master/jetty-server/src/test/config/etc/keystore?id=master to ${jetty.base}\etc\keystoreMKDIR: ${jetty.base}\webappsINFO: Base directory was modified    注意,在上述过程中,会根据当前环境变量中使用的Java版本(此处为1.8.0_60)去下载一个对应的TLS-ALPN实现jar文件(此处为alpn-boot-8.1.5.v20150921.jar),该jar会用于对h2的支持。当启动Jetty时,该jar会被Java的Bootstrap class loader加载到类路径中。

创建一个最简单的Web应用,使它在根目录下包含一个文本文件index,内容为"HTTP/2 Test"。再包含一个简单的Servlet,代码如下:

packagetest;importjava.io.IOException;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;publicclassTestServletextendsHttpServlet {privatestaticfinallongserialVersionUID=5222793251610509039L;@OverridepublicvoiddoGet(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {response.getWriter().println("Test");}@OverridepublicvoiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {doGet(request, response);}}web.xml主要是定义了一个Servlet,具体内容如下:

<?xml  version="1.0" encoding="UTF-8"?>indextesttest.TestServlettest/test/*该应用的部署路径为jetty-9.3.11/test-base/webapps/test.war。在该WAR文件所在的目录下,创建一个test.xml,其内容如下所示:

<?xml  version="1.0"  encoding="ISO-8859-1"?>Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">//webapps/test.war启动Jetty服务器,使用默认的HTTP和HTTPS端口,分别为8080和8443。

$ java -jar ../start.jar2016-09-15 21:15:51.190:INFO:oejs.Server:main: jetty-9.3.11.v201607212016-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 12016-09-15 21:15:52.251:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /test.war, did not find org.eclipse.jetty.jsp.JettyJspServlet2016-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.JettyJspServlet2016-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    根据上述日志可知,Jetty启用了Web应用test.war,还启动了两个ServerConnector,一个支持h2c,另一个支持h2。值得注意的是,这两个ServerConnector还分别支持h2c-17, h2c-16, h2c-15, h2c-14和h2-17, h2-16, h2-15, h2-14。这是因为,HTTP/2在正式发布之前,先后发布了18个草案,其编号为00-17。所以,这里的h2c-XX和h2-XX指的就是第XX号草案。

3. 客户端

其实最方便的客户端就是浏览器了。只要使用的FireFox或Chrome版本不是太老,肯定都已经支持了HTTP/2,而且这一功能是默认打开的。也就是说,当使用FireFox去访问前面所部署的Web应用时,就是在使用HTTP/2,但你不会感觉到这种变化。使用FireFox提供的Developer Tools中的Network工具查看服务器端的响应,会发现HTTP版本为HTTP/2.0。但此处希望这个客户端能够提供更为丰富的与服务器端进行交互的功能,那么浏览器就并不合适了。

Jetty也实现了支持HTTP/2的客户端,但这个客户端是一个API,需要编写程序去访问HTTP/2服务器端。而且,目前该API的设计抽象层次较低,需要应用程序员对HTTP/2协议,比如各种帧,有较深入的了解。这对于初涉HTTP/2的开发者来说,显然很不合适。本文选择使用C语言编写的一个工具,其实也是HTTP/2的客户端实现之一,curl。

curl在支持HTTP/2时,实际上是使用了nghttp2的C库,所以需要先安装nghttp2。另外,为了让curl支持h2,就必须要有TLS-ALPN的支持。那么,一般地还需要安装OpenSSL 1.0.2+。

网络上关于在Linux下安装支持HTTP/2的curl的资源有很多,过程并不难,但有点儿繁,要安装的依赖比较多,本文就不赘述了。如果是使用Windows,笔者比较推荐通过Cygwin来安装和使用curl。在Windows中安装Cygwin非常简单,在Cygwin中执行各种命令时,感觉上就如同在使用Linux,尽管它并不是一个虚拟机。通过Cygwin安装curl,它会自动地安装所需的各种依赖程序和库。

在笔者的机器上,通过查看curl的版本会出现如下信息:

curl7.50.2(x86_64-unknown-cygwin) libcurl/7.50.2OpenSSL/1.0.2h zlib/1.2.8libidn/1.29libpsl/0.14.0(+libidn/1.29) libssh2/1.7.0nghttp2/1.14.0Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftpFeatures: 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。

4. 第一次尝试

在第一次尝试中,只需要简单地访问第2节中部署的Web应用中的静态文本文件index,以感受下h2c,完整命令如下:

$ curl -v --http2 http://localhost:8080/index在输出中包含有如下的内容:

...> 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/2Test">"是客户端发送的请求,"

[1]客户端发起了一个HTTP/1.1的请求,其中携带有Upgrade头部,要求服务器端升级到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]服务器端同意升级,返回响应"101 Switching Protocols",然后客户端收到了101响应,HTTP/2连接进行确认。

...HTTP/2Test

5. 一个局限

这次,在发起的请求中包含体部,命令如下:

$ curl -v --http2 -d"body"http://localhost:8080/index在输出中包含有如下的内容:

...> 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>

...

6. 一个Bug

在这次尝试中,测试一下两端对100-continue的支持。如果请求中使用了头部"Expect: 100-continue",那么正常地该请求要有体部。但由于在第5节中介绍的问题,此时不能再使用h2c,而只能使用h2。另外,这次不访问静态文件,而是访问Servlet(此处为/test)。完整命令如下:

$ curl -vk --http2 -H"Expect: 100-continue"-d"body"https://localhost:8443/test在输出的最后出现了如下信息:

curl: (92) HTTP/2stream1was not closed cleanly: CANCEL (err8)这其实是Jetty的一个Bug,正在开发中的9.3.12已经修复了它。

7. 小结

HTTP/2依然算是新潮的技术,对各家的实现,无论是服务器端,客户端,还是分析工具,都要持有一份怀疑态度。这些实现和工具都是程序,都有可能存在bug。而且协议对许多细节没有作出规定,各家都会发挥自己的想像力。比如,Apache httpd和Jetty在实现服务器端推送时,其方式就不尽相同。

在开发自己的HTTP/2实现或应用的时候,需要同时使用已有的不同服务器端和客户端去部署多套测试环境进行对比分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值