看了很多关于HTTP/2
的优势、性能如何如何提升的文章,可是对于Android
客户端开发来说,Android API
要是不支持,也是白搭啊。度娘说,要客户端支持HTTP/2
,首先要支持ALPN
(ALPN
是什么,这里有介绍Introducing ALPN)
而要Android
支持HTTP/2
,首先离不开Java
的支持。从公开资料得知,HTTP/2
和ALPN
被包含了在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+
:
- New: Use ALPN on Android platforms that support it (4.4+)
不管怎样总算看到一线希望,只是用于封装URLConnection
的是哪个版本呢,能不能支持HTTP/2
呢,动手写个demo试试吧。
——————————–高端大气上档次的分割线—————————-
要测试HTTP/2
,首先要有个server,自己动手,听说nginx
支持HTTP/2
的部署,那就你了
一、安装nginx
到http://nginx.org/en/download.html,nginx
已经为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.crt
和enzo-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处于同一个局域网,在手机的hosts
将enzo.com
指向server地址,运行demo,只见nginx
的access.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
不太现实,所以没有进一步尝试。