今天看Volley和OkHttp的时候看见了https的相关东西,忽然想到上次老婆叫帮忙买票用google浏览器上12306的场景了。。。
为什么今天会去看这三个东西
最近有点清闲的感觉,有一定的时间去做自己想做的东西。于是就在网上搜索着网络库。出现大量关于Volley
,okhttp
的文章。其中volley
和okhttp
结合使用的文章太多了。首先我分别用了两个库来访问网络。
第一感觉是我懵逼了。原因是:都是访问网络的库,为什么要结合使用?实在也是没太明白。后面经过一段实践后终于有所明白。
1.volley
的post
方式
public void getNetWorkString() {
String strul = "http://www.baidu.com";
StringRequest stringRequest = new StringRequest(Request.Method.POST, strul, new Response.Listener() {
@Override
public void onResponse(String s) {
show.setText(s);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
Log.i("joker", volleyError.getMessage().toString());
}
}
) {
@Override
protected Map getParams() throws AuthFailureError {
Map map = new HashMap();
map.put("joker", "1");
map.put("chan", "2");
return super.getParams();
}
};
queue.add(stringRequest);
queue.start();
}
效果如下:
2.okhttp
的get
方式
public void getTest() {
String url = "https://www.baidu.com/";
final Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.i("joker", e.getMessage());
}
@Override
public void onResponse(final Response response) throws IOException {
final String res = response.body().string();
show.setText(res);
}
});
}
效果如下:
相信这个时候已经知道是为什么了。因为okhttp
的onResponse
和onFailure
并不在主线程。所以不能直接更新ui
。
现在改写代码为
public void getTest() {
String url = "https://www.baidu.com/";
final Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.i("joker", e.getMessage());
}
@Override
public void onResponse(final Response response) throws IOException {
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
show.setText(res);
}
});
}
});
}
就能正常的运行获取数据了。
这时候已经明白了一点为什么要用volley+okhttp
的方式了。因为okhttp
是最基本的网络库,需要封装一次才更实用。为volley
已经封装好了一些。而volley
的传输层实现又没okhttp
好用,所有就有现在的用okhttp
作为volley
的传输层的实现方式了。(以上只是自己的理解,有不对的地方希望指出来),下面来看看具体的实现:
首先是OkHttp2.0
已经不支持OkHttpStack
了。所有现在用OkUrlFactory
public class OkHttpStack extends HurlStack {
private final OkUrlFactory okUrlFactory;
public OkHttpStack() {
this(new OkUrlFactory(new OkHttpClient()));
}
public OkHttpStack(OkUrlFactory okUrlFactory) {
if (okUrlFactory == null) {
throw new NullPointerException("Client must not be null.");
}
this.okUrlFactory = okUrlFactory;
}
@Override
protected HttpURLConnection createConnection(URL url) throws IOException {
return okUrlFactory.open(url);
}
}
其中用到OkUrlFactory
需要导入对应jar
包
然后这样使用
requestQueue = Volley.newRequestQueue(getContext(), new OkHttpStack());
requestQueue.start();
到此okhttp
和volley
之前的迷惑我算是知道暂时明白了。
https的引出
百度百科是这样说的:
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司(Netscape)进行,并内置于其浏览器Netscape Navigator中,提供了身份验证与加密通讯方法。现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。
很多网站都是支持https
,像我们平时的百度可以用https:/www.baidu.com访问。一般情况下支持https
的网站基本都是CA
机构颁发的证书,默认情况下是可以信任的。但是有些网站是通过自签名的方式进行的,并不是CA机构去颁发的。使用自签名证书的网站,大家在使用浏览器访问的时候,一般都是报风险警告,其中12306购票网站就是这样做的,https://kyfw.12306.cn/otn/,点击进入12306的购票页面就能看到了。
下面我们试试用volley
和okhttp
访问12306(https://www.12306.cn)。
1.volley
public void getVNetWorkString() {
String strul = "https://www.12306.cn";
StringRequest stringRequest = new StringRequest(strul, new Response.Listener() {
@Override
public void onResponse(String s) {
show.setText(s);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
Log.i("joker", volleyError.getMessage().toString());
}
});
queue.add(stringRequest);
queue.start();
}
2.okhttp
public void getONetWorkString() {
OkHttpClient okHttpClient = new OkHttpClient();
String url = "https://www.12306.cn";
final Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.i("joker", e.getMessage());
}
@Override
public void onResponse(final com.squareup.okhttp.Response response) throws IOException {
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
show.setText(res);
}
});
}
});
}
运行程序会出现下面的信息:
03-29 14:01:42.856 12928-12928/com.jokerchan.volleytest I/joker: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
从中可以知道是证书不支持的原因。
网上常见的做法是信任所有证书,则是重写X509TrustManager
为空实现,代码如下:
1.忽略证书的方法
public void ignoreCard(OkHttpClient client) {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init
(
null,
new TrustManager[]{new AllX509TrustManager()},
new SecureRandom()
);
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
2.访问https
连接
public void getONetWorkString() {
String url = "https://www.12306.cn";
final Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.i("joker", e.getMessage());
}
@Override
public void onResponse(final com.squareup.okhttp.Response response) throws IOException {
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
show.setText(res);
}
});
}
});
}
3.调用步骤
ignoreCard(okHttpClient);
getONetWorkString();
但是这样做很不安全。下面我们来看看如何用证书的方式进行验证。
1.首先到12306
官方下载证书。进入首页就会看见。
2.下载完成后解压得到srca.cer
文件。
3.然后将他放在项目assets
文件夹下面。这里可以随意放在任何地方,只要等读取到。
4.代码实现
证书设置
public void setCard(OkHttpClient client, InputStream certificate) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
String certificateAlias = Integer.toString(0);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
SSLContext sslContext = SSLContext.getInstance("TLS");
final TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init
(
null,
trustManagerFactory.getTrustManagers(),
new SecureRandom()
);
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
访问
public void getONetWorkString() {
String url = "https://www.12306.cn";
final Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.i("joker", e.getMessage());
}
@Override
public void onResponse(final com.squareup.okhttp.Response response) throws IOException {
final String res = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
show.setText(res);
}
});
}
});
}
调用
try {
setCard(okHttpClient, getAssets().open("srca.cer"));
} catch (IOException e) {
e.printStackTrace();
}
getONetWorkString();
上面两种方式都实现了访问https
。
我们也可以把证书里面的内容获取出来写成一个String
。
用keytool -printcert -rfc -file srca.cer
命令就可以获取到证书里面的内容。
然后用String
类型来存放。然后如此使用就可以了。
setCard(okHttpClient, new Buffer().writeUtf8(cardInfo).inputStream());
getONetWorkString();
volley
默认是不支持https
的。既然有源码也可以自己尝试去改改。这里就不做过多解释了。
注意:其中
setHostnameVerifier
建议这样写
setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER)
,setHostnameVerifier
用来验证服务器证书上的域名是否和服务器的实际域名相匹配。(个人理解)