@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sni); getSupportActionBar().setTitle(R.string.sni_scene); // 初始化httpdns httpdns = HttpDns.getService(getApplicationContext(), MainActivity.accountID); // 预解析热点域名 httpdns.setPreResolveHosts(new ArrayList<>(Arrays.asList("dou.bz", "www.douban.com"))); new Thread( new Runnable() { @Override public void run() { SNIActivity.this.recursiveRequest("https://dou.bz/23o8PS", null); } }) .start(); }
public void recursiveRequest(String path, String reffer) { URL url = null; try { url = new URL(path); conn = (HttpsURLConnection) url.openConnection(); // 同步接口获取IP String ip = httpdns.getIpByHostAsync(url.getHost()); if (ip != null) { // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置 Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!"); String newUrl = path.replaceFirst(url.getHost(), ip); conn = (HttpsURLConnection) new URL(newUrl).openConnection(); // 设置HTTP请求头Host域 conn.setRequestProperty("Host", url.getHost()); } conn.setConnectTimeout(30000); conn.setReadTimeout(30000); conn.setInstanceFollowRedirects(false); TlsSniSocketFactory sslSocketFactory = new TlsSniSocketFactory(conn); conn.setSSLSocketFactory(sslSocketFactory); conn.setHostnameVerifier( new HostnameVerifier() { /* * 关于这个接口的说明,官方有文档描述: * This is an extended verification option that implementers can provide. * It is to be used during a handshake if the URL's hostname does not match the * peer's identification hostname. * * 使用HTTPDNS后URL里设置的hostname不是远程的主机名(如:m.taobao.com),与证书颁发的域不匹配, * Android HttpsURLConnection提供了回调接口让用户来处理这种定制化场景。 * 在确认HTTPDNS返回的源站IP与Session携带的IP信息一致后,您可以在回调方法中将待验证域名替换为原来的真实域名进行验证。 * */ @Override public boolean verify(String hostname, SSLSession session) { String host = conn.getRequestProperty("Host"); if (null == host) { host = conn.getURL().getHost(); } return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session); } }); int code = conn.getResponseCode(); // Network block if (needRedirect(code)) { // 临时重定向和永久重定向location的大小写有区分 String location = conn.getHeaderField("Location"); if (location == null) { location = conn.getHeaderField("location"); } if (!(location.startsWith("http://") || location.startsWith("https://"))) { // 某些时候会省略host,只返回后面的path,所以需要补全url URL originalUrl = new URL(path); location = originalUrl.getProtocol() + "://" + originalUrl.getHost() + location; } recursiveRequest(location, path); } else { // redirect finish. DataInputStream dis = new DataInputStream(conn.getInputStream()); int len; byte[] buff = new byte[4096]; StringBuilder response = new StringBuilder(); while ((len = dis.read(buff)) != -1) { response.append(new String(buff, 0, len)); } Log.d(TAG, "Response: " + response.toString()); } } catch (MalformedURLException e) { Log.w(TAG, "recursiveRequest MalformedURLException"); } catch (IOException e) { Log.w(TAG, "recursiveRequest IOException"); } catch (Exception e) { Log.w(TAG, "unknow exception"); } finally { if (conn != null) { conn.disconnect(); } } }