看了很多关于HTTP/2的优势、性能如何如何提升的文章,可是对于Android客户端开发来说,Android API要是不支持,也是白搭啊。度娘说,要客户端支持HTTP/2,首先要支持ALPNALPN是什么,这里有介绍Introducing ALPN

而要Android支持HTTP/2,首先离不开Java的支持。从公开资料得知,HTTP/2ALPN被包含了在Java9的新特性中,而Java9已经跳票到2017年7月。。。隔壁的iOS从9开始就提供API支持了。。。蓝瘦,香菇

等等,我们知道Android API中用于完成网络请求的URLConnection已经在Andriod 4.x时代被替换成了基于OkHttp的实现,而不再是原本的Java封装,那OkHttp能支持不就可以了吗。

 刚进入OkHttp官网首页,就看到了硕大的HTTP/2字样,一阵狂喜,查看changelog,发现从Version 1.5.0开始就支持ALPN了,可是仅限于Android4.4+

不管怎样总算看到一线希望,只是用于封装URLConnection的是哪个版本呢,能不能支持HTTP/2呢,动手写个demo试试吧。

 

——————————–高端大气上档次的分割线—————————-

要测试HTTP/2,首先要有个server,自己动手,听说nginx支持HTTP/2的部署,那就你了

一、安装nginx

http://nginx.org/en/download.htmlnginx已经为windows编译好,直接下载就可以用。

进入nginx目录

start nginx

度娘说,HTTP/2需要基于TLS,也就是先得部署HTTPS,部署HTTPS需要签名,据说有免费的签名可以申请,不过也可以自签名:

二、生成证书

OpenSSL下载,安装,

nginx根目录下新建ssl文件夹(名字可以自己定),

#此步用于生成私钥,会提示输入密码,密码后面步骤需要用到;enzo.key为私钥的名字,文件名可自己定
openssl genrsa -des3 -out enzo.key 1024
#此步用于生成csr证书,enzo.key为上一步骤生成的私钥名,enzo.csr为证书,证书文件名可自定
#在此步过程中,会交互式输入一系列的信息(所在国家、城市、组织等),其中Common Name一项代表nginx服务访问用到的域名,我这里是本地测试,所以可以随意定一个jason.com,并在本地host文件中将此域名映射为127.0.0.1
openssl req -new -key enzo.key -out enzo.csr
#此步用于去除访问密码,如果不执行此步,在配置了ssl后,nginx启动会要求输入密码
#enzo.key为需要密码的key,enzo-np.key为去除访问密码的key文件
#操作过程中会要求输入密码,密码为生成key文件时的密码
openssl rsa -in enzo.key -out enzo-np.key
#此步用于生成crt证书
#enzo.crt为第2步生成的csr证书名称,enzo.crt为要生成的证书名称
openssl x509 -req -days 366 -in enzo.csr -signkey enzo-np.key -out enzo.crt

 经过以上几个步骤,证书生成完毕,ssl文件夹下的enzo.crtenzo-np.key为我们后续要使用的文件。

注:在执行openssl命令时,可能会出现提示找不到openssl配置文件,

需要手动将C:\OpenSSL-Win64\bin\openssl.cfg重命名为openssl.cnf并copy到 C:\Program Files\Common Files\SSL\

三、部署HTTPS 和 HTTP/2

打开nginx目录下conf\nginx.conf文件,找到HTTPS server的配置,将配置项前面的注释符号去掉,然后修改如下:

server {
        listen       443 ssl http2;
        server_name  front;


        ssl_certificate      ./ssl/enzo.crt;
        ssl_certificate_key  ./ssl/enzo-np.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        #ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }

四、配置重定向

http的请求重定向到https,只需要在http对应server配置中添加rewrite

server {
        listen       80;
        server_name enzo.com;
        rewrite ^(.*) https://$server_name$1 permanent;
}

 由于指定了虚拟域名,还需要在hosts中将enzo.com指向本机127.0.0.1

五、加载nginx配置

D:\http2\nginx-1.11.4>nginx -s reload
nginx: [emerg] the "http2" parameter requires ngx_http_v2_module in D:\http2\nginx-1.11.4/conf/nginx.conf:98

 什么情况,缺少参数???马上google,原来是官方编译的时候没有带上--with-http_v2_module

六、重新编译nginx

编译过程参见nginx源码编译  

七、重启打开nginx服务

重新放入证书,配置文件,配置hosts,进入nginx/sbin:

cd /usr/local/nginx/sbin
sudo ./nginx

 打开chrome,输入https://enzo.com

欧也

硕大的h2,看到没,部署成功了。

八、测试URLConnection

写个Demo,测一下URLConnection,因为是HTTPS,所以需要一个SSLContext,将前面生成的证书enzo.crt放到assets目录:

public SSLContext getSSLContext() {
	    try {
			// 生成SSLContext对象
			SSLContext sslContext = SSLContext.getInstance("TLS");
			// 从assets中加载证书
			InputStream inStream = getAssets().open("enzo.crt");

			// 证书工厂this
			CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
			Certificate cer = cerFactory.generateCertificate(inStream);
 
			// 密钥库
			KeyStore kStore = KeyStore.getInstance("PKCS12");//如果是运行在PC端,这里需要将PKCS12替换成JKS
			kStore.load(null, null);
			kStore.setCertificateEntry("trust", cer);// 加载证书到密钥库中
 
			// 密钥管理器
			KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
			keyFactory.init(kStore, null);// 加载密钥库到管理器
 
			// 信任管理器
			TrustManagerFactory tFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tFactory.init(kStore);// 加载密钥库到信任管理器
 
			// 初始化
			sslContext.init(keyFactory.getKeyManagers(), tFactory.getTrustManagers(), new SecureRandom());
			return sslContext;
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		} catch (KeyManagementException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

然后:

public void testURLConnection() {
		try {
			String path = "https://enzo.com";
			// 新建一个URL对象
			URL url = new URL(path);
			
			// 打开一个HttpsURLConnection连接
			SSLContext sc = getSSLContext();
                        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
			HttpsURLConnection conn = (HttpsURLConnection)  url.openConnection();
			
	                // 设置连接超时时间
			conn.setConnectTimeout(5 * 1000);
			// 开始连接
			conn.connect();
			// 判断请求是否成功
			if (conn.getResponseCode() == 200) {
				// 获取返回的数据
				byte[] data = readStream(conn.getInputStream());
				Log.i("---wyf---", "Get方式请求成功,返回数据如下:");
				Log.i("---wyf---", new String(data, "UTF-8"));
			} else {
				Log.i("---wyf---", "Get方式请求失败:"+conn.getResponseCode());
			}
			// 关闭连接
			conn.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

找一个已root的4.4的手机,确保跟server处于同一个局域网,在手机的hostsenzo.com指向server地址,运行demo,只见nginxaccess.log中给你来了这么一行:

"GET / HTTP/1.1" 200 612 "-" "Dalvik/1.6.0 (Linux; U; Android 4.4.4; M463C Build/KTU84P)"

 被降级到HTTP/1.1了,那5.x的手机呢:

"GET / HTTP/1.1" 200 612 "-" "Dalvik/2.1.0 (Linux; U; Android 5.1.1; NX529J Build/LMY47V)"

 好吧,看来URLConnection是指望不上了

九、测试OkHttp

那么直接上OkHttp,下了个最新的3.4.1

public void testOkHttp() {
		try {
			String path = "https://enzo.com";
			// 新建一个URL对象
			URL url = new URL(path);
			
			// 打开一个OkHttpClient连接
			SSLContext sc = getSSLContext();
			List<Protocol> protocols = new ArrayList<Protocol>();
			protocols.add(Protocol.HTTP_1_1);
			protocols.add(Protocol.HTTP_2);
			OkHttpClient client = new OkHttpClient.Builder().protocols(protocols)
			        .sslSocketFactory(sc.getSocketFactory())
			        .build();
			
			Request request = new Request.Builder().url(url).build();
		    Response response = client.newCall(request).execute();
		    if (response.isSuccessful()) {
		    	Log.i("---wyf---", "Get方式请求成功,返回数据如下:");
				Log.i("---wyf---", response.body().string());
				Log.d("---wyf---", "Protocol: " + response.protocol());
		    } else {
		    	Log.i("---wyf---", "Get方式请求失败:"+response);
		    }
	
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

 4.4:

"GET / HTTP/1.1" 200 612 "-" "okhttp/3.4.1"

5.x:

"GET / HTTP/2.0" 200 729 "-" "okhttp/3.4.1"

又见HTTP/2!

所以OkHttp & Android 5.0+ 是可以实现HTTP/2访问的。

写在最后:

Introducing ALPN的最后面,介绍了“How to build ALPN”,把jetty ALPN实现编译到OpenJDK 7/8里面去,也许也是个办法,但考虑到替换OpenJDK不太现实,所以没有进一步尝试。