参考
HttpURLConnection 源码解析图解,对照如下图片解析会更加清晰。(和本章节内容实际有出入,但是可以作为参考)
这里有个非常诡异的现象,采用debug模式在URL中不是很准确,起码URL构造方法断点打了就没有走,还有其他的很多不是自己想要的东西(志不在此,我们先不管)
URL创建
URL url = new URL(urlStr);
public URL(String spec) throws MalformedURLException {
this(null, spec);
}
public URL(URL context, String spec) throws MalformedURLException {
this(context, spec, null);
}
public URL(URL context, String spec, URLStreamHandler handler)
throws MalformedURLException
{
//基础类型存储于栈中:重新创建一个orginal变量,对spec的操作不影响spec
//如果这里是对象类型,那么栈中创建的是original的引用变量,指向堆中的真实对象(和spec的引用变量指向同一个对象),spec里的对象发生变化必然会影响到orginal
String original = spec;//把spec复制一份出来保留为原始值,然后在对其进行操作
//limit表示spec地址的长度,i相当于一个操作在start~limit区间的始终向前滑动的游标,c表示i滑动到limit位置的字符串对应ascii码值
//理解一下:例如spec = "https://xxx",首先我们需要找到protocaol协议,i需要滑动到‘:’然后停下,那么c对应的值就是':'的ascii码值,这样做的目的就是通过i的滑动找到protocol协议为https。
int i, limit, c;//i,c用于解析URL地址
int start = 0;//有效的开始位置,比如spec给的地址前面有很多空格,那么需要先处理掉,start的值就是从有效的字符开始算起
String newProtocol = null;//用于存储spec的协议类型,最终会赋值给全局变量protocol
boolean aRef=false;//这里用于判断是否存在#:一个承接上下文的引用(表示不熟)
boolean isRelative = false;//判断当前传入的地址是否全路径地址,否则为全路径地址
// Check for permission to specify a handler
if (handler != null) {//这里的handler == null
//SecurityManager在Java中被用来检查应用程序是否能访问一些有限的资源,下面单独提一下该知识点
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkSpecifyHandler(sm);
}
}
try {
limit = spec.length();
//如果地址末尾存在空格,逐一去除
while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
limit--; //eliminate trailing whitespace
}
//如果地址前面有多余的空格,逐一去除
while ((start < limit) && (spec.charAt(start) <= ' ')) {
start++; // eliminate leading whitespace
}
//用于判断spec的地址前四个字符是否是url:,如果是start前移4位
if (spec.regionMatches(true, start, "url:", 0, 4)) {
start += 4;
}
//如果进过以上处理的地址的首字符是#,这里判断是引用字符,aRef = true
if (start < spec.length() && spec.charAt(start) == '#') {
/* we're assuming this is a ref relative to the context URL.
* This means protocols cannot start w/ '#', but we must parse
* ref URL's like: "hello:there" w/ a ':' in them.
*/
aRef=true;
}
//以下目的:找到有效的网络协议,比如http,https;并且start继续前移用于查找URL地址的后面部分
//这里必须对URL地址有所了解,否则这里看起来确实费劲
for (i = start ; !aRef && (i < limit) &&
((c = spec.charAt(i)) != '/') ; i++) {
if (c == ':') {
String s = spec.substring(start, i).toLowerCase();
if (isValidProtocol(s)) {
newProtocol = s;
start = i + 1;
}
break;
}
}
// Only use our context if the protocols match.
protocol = newProtocol;
//如果context URL不为空,新获取的协议为空,首先忽略context中的协议大小写,然后将context 中的handler赋值并返回
//这里的context是通过构造方法传递的,这里是null
if ((context != null) && ((newProtocol == null) ||
newProtocol.equalsIgnoreCase(context.protocol))) {
// inherit the protocol handler from the context
// if not specified to the constructor
if (handler == null) {
handler = context.handler;
}
// If the context is a hierarchical URL scheme and the spec
// contains a matching scheme then maintain backwards
// compatibility and treat it as if the spec didn't contain
// the scheme; see 5.2.3 of RFC2396
if (context.path != null && context.path.startsWith("/"))
newProtocol = null;
//以下赋值协议,主机host,端口port,路径path等
//isRelative = true可以看出是相对路径,context是上一次赋值,这里可以直接获取
if (newProtocol == null) {
protocol = context.protocol;
authority = context.authority;
userInfo = context.userInfo;
host = context.host;
port = context.port;
file = context.file;
path = context.path;
isRelative = true;
}
}
if (protocol == null) {
throw new MalformedURLException("no protocol: "+original);
}
// Get the protocol handler if not specified or the protocol
// of the context could not be used
//核心代码:handler = getURLStreamHandler(protocol))
if (handler == null &&
(handler = getURLStreamHandler(protocol)) == null) {
throw new MalformedURLException("unknown protocol: "+protocol);
}
this.handler = handler;
//目的,如果包含上下文#引用,则从start开始,找到第一个#的下标,并且将#后面的字符串赋值给ref,表示引用;limit(地址长度只算i前面的)
//#目的
i = spec.indexOf('#', start);
if (i >= 0) {
ref = spec.substring(i + 1, limit);
limit = i;
}
/*
* Handle special case inheritance of query and fragment
* implied by RFC2396 section 5.2.2.
*/
if (isRelative && start == limit) {//如果是相对路径,并且start已经前移到头
query = context.query;
if (ref == null) {
ref = context.ref;
}
}
//下面详细讲
handler.parseURL(this, spec, start, limit);
} catch(MalformedURLException e) {
throw e;
} catch(Exception e) {
MalformedURLException exception = new MalformedURLException(e.getMessage());
exception.initCause(e);
throw exception;
}
}
代码中有详细注释,这里注释的比较详细,个人感觉第一既然看了,就看彻底一点;第二,详细的理解肯定有助于对全局意思的理解。以上还有几点补充
1 handler不为空情况下是否具有权限
SecurityManager类,安全管理器,这篇文章有介绍,有兴趣可以去看
// Check for permission to specify a handler
if (handler != null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkSpecifyHandler(sm);
}
}
...
/*
* Checks for permission to specify a stream handler.
*/
private void checkSpecifyHandler(SecurityManager sm) {
sm.checkPermission(SecurityConstants.SPECIFY_HANDLER_PERMISSION);
}
从Java API的角度去看,应用程序的安全策略是由安全管理器去管理的。安全管理器决定应用是否可以执行某项操作。这些操作具体是否可以执行的依据,是看其能否对一些比较重要的系统资源进行访问,而这项验证由存取控制器进行管控。
这里用到了两处,spec.charAt(start) == ‘#’)这个我确实不了解这样的写法,网上也没找到;另外一种
i = spec.indexOf('#', start);
if (i >= 0) {
ref = spec.substring(i + 1, limit);
limit = i;
}
体现一种特殊的作用:例如:表示相应的位置
http://www.example.com/index.html#print
就代表网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至可视区域。
3 URL核心之getURLStreamHandler(protocol))
handler的创建,也是URL请求核心代码之一:获取URLStreamHandler 对象
//一个缓存,以协议为key存储协议对应的URLStreamHandler对象
static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
/**
* Returns the Stream Handler.
* @param protocol the protocol to use
*/
static URLStreamHandler getURLStreamHandler(String protocol) {
URLStreamHandler handler = handlers.get(protocol);
if (handler == null) {//缓存中不存在
boolean checkedWithFactory = false;
// Use the factory (if any)
if (factory != null) {//判断创建URLSteamHandler的工厂可存在,如果存在,使用工厂创建
//这里显然factory == null
handler = factory.createURLStreamHandler(protocol);
checkedWithFactory = true;
}
// Try java protocol handler
if (handler == null) {
// Android-changed: Android doesn't need AccessController.
// Remove unnecessary use of reflection for sun classes
/*
packagePrefixList
= java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
protocolPathProp,""));
if (packagePrefixList != "") {
packagePrefixList += "|";
}
// REMIND: decide whether to allow the "null" class prefix
// or not.
packagePrefixList += "sun.net.www.protocol";
*/
//获取jvm系统属性, 检查java.protocol.handler.pkgs包中有没有,有就创建
final String packagePrefixList = System.getProperty(protocolPathProp,"");
//将获取到的属性以“|”分割开
StringTokenizer packagePrefixIter =
new StringTokenizer(packagePrefixList, "|");
while (handler == null &&
packagePrefixIter.hasMoreTokens()) {
String packagePrefix =
packagePrefixIter.nextToken().trim();
try {
String clsName = packagePrefix + "." + protocol +
".Handler";
Class<?> cls = null;
try {//如果找到了,通过反射生成handler类
ClassLoader cl = ClassLoader.getSystemClassLoader();
// BEGIN Android-changed: Fall back to thread's contextClassLoader.
// http://b/25897689
cls = Class.forName(clsName, true, cl);
} catch (ClassNotFoundException e) {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
if (contextLoader != null) {
cls = Class.forName(clsName, true, contextLoader);
}
// END Android-changed: Fall back to thread's contextClassLoader.
}
if (cls != null) {
handler =
(URLStreamHandler)cls.newInstance();
}
} catch (ReflectiveOperationException ignored) {
}
}
}
// BEGIN Android-added: Custom built-in URLStreamHandlers for http, https.
// Fallback to built-in stream handler.
if (handler == null) {//以上如果都没有处理好handler的终生大事,那就继续到这里了
try {//createBuiltinHandler最终值得托付,创建handler(你认为我这么较真的人下面会没有对该方法的注解???)
handler = createBuiltinHandler(protocol);
} catch (Exception e) {
throw new AssertionError(e);
}
}
// END Android-added: Custom built-in URLStreamHandlers for http, https.
synchronized (streamHandlerLock) {//定义了一个锁,目的是确保里面内容的安全性
//以下两个目的,首先我再去确认一下你这个URLStreamHandler 是否已经被缓存,被工厂创建,如果有这个虽然创建了,但是还是用handler2
URLStreamHandler handler2 = null;
// Check again with hashtable just in case another
// thread created a handler since we last checked
handler2 = handlers.get(protocol);
if (handler2 != null) {
return handler2;
}
// Check with factory if another thread set a
// factory since our last check
if (!checkedWithFactory && factory != null) {
handler2 = factory.createURLStreamHandler(protocol);
}
if (handler2 != null) {
// The handler from the factory must be given more
// importance. Discard the default handler that
// this thread created.
handler = handler2;
}
// Insert this handler into the hashtable
if (handler != null) {
handlers.put(protocol, handler);
}
}
}
return handler;
}
(1)System.getProperty
通过 JVM 启动参数 -D java.protocol.handler.pkgs 来设置 URLStreamHandler 实现类的包路径,例如 -D java.protocol.handler.pkgs=com.acme.protocol , 代表处理实现类皆在这个包下。如果需要多个包的话,那么使用“ |” 分割。比如 -D java.protocol.handler.pkgs=com.acme.protocol|com.acme.protocol2 。 SUN 的 JDK 内部实现类均是在 sun.net.www.protocol. 包下,不必设置。 路径下的协议实现类,采用先定义先选择的原则 。
看下如下代码的意思:
// Android-changed: Android doesn't need AccessController.
// Remove unnecessary use of reflection for sun classes
/*
packagePrefixList
= java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
protocolPathProp,""));
if (packagePrefixList != "") {
packagePrefixList += "|";
}
// REMIND: decide whether to allow the "null" class prefix
// or not.
packagePrefixList += "sun.net.www.protocol";
*/
final String packagePrefixList = System.getProperty("java.protocol.handler.pkgs","");
StringTokenizer packagePrefixIter =
new StringTokenizer(packagePrefixList, "|");
我们可以自己试下,并没有找到java.protocol.handler.pkgs包下的类名
(2)createBuiltinHandler
在Android新版源码中(据说是4.4开始)底层的Http协议请求使用的是Okhttp。
// BEGIN Android-added: Custom built-in URLStreamHandlers for http, https.
/**
* Returns an instance of the built-in handler for the given protocol, or null if none exists.
*/
private static URLStreamHandler createBuiltinHandler(String protocol)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
URLStreamHandler handler = null;
if (protocol.equals("file")) {
handler = new sun.net.www.protocol.file.Handler();
} else if (protocol.equals("ftp")) {
handler = new sun.net.www.protocol.ftp.Handler();
} else if (protocol.equals("jar")) {
handler = new sun.net.www.protocol.jar.Handler();
} else if (protocol.equals("http")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpHandler").newInstance();
} else if (protocol.equals("https")) {
handler = (URLStreamHandler)Class.
forName("com.android.okhttp.HttpsHandler").newInstance();
}
return handler;
}
我们来看下URLStreamHandler源码:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public final class HttpHandler extends URLStreamHandler {
@Override protected URLConnection openConnection(URL url) throws IOException {
return new OkHttpClient().open(url);
}
@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
if (url == null || proxy == null) {
throw new IllegalArgumentException("url == null || proxy == null");
}
return new OkHttpClient().setProxy(proxy).open(url);
}
@Override protected int getDefaultPort() {
return 80;
}
}
注意:这里com.android.okhttp.HttpsHandler包下,为什么最终定位的是com.squareup.okhttp,因为这里做了包名转换:
因为jarjar-rules的存在,其实路径为/external/okhttp/jarjar-rules.txt,内容如下:
rule com.squareup.** com.android.@1
rule okio.** com.android.okio.@1
4 URL地址解析:handler.parseURL(this, spec, start, limit);
protected void parseURL(URL u, String spec, int start, int limit) {
// These fields may receive context content if this was relative URL
//这里除了protocal能获取到,我估计其他字段都是null或者默认值,这句话的目的是如果承接之前的信息,如果已经存在那么直接赋值过来即可
String protocol = u.getProtocol();
String authority = u.getAuthority();
String userInfo = u.getUserInfo();
String host = u.getHost();
int port = u.getPort();
String path = u.getPath();
String query = u.getQuery();
// This field has already been parsed
String ref = u.getRef();
boolean isRelPath = false;//判断地址是否真实有效
boolean queryOnly = false;//仅仅是查询,我说这个是代码冗余可信?高级代码不过尔尔
// BEGIN Android-changed: App compat
boolean querySet = false;//url地址中是否存在搜索条件
// END Android-changed: App compat
// FIX: should not assume query if opaque
// Strip off the query part
if (start < limit) {
int queryStart = spec.indexOf('?');
queryOnly = queryStart == start;
if ((queryStart != -1) && (queryStart < limit)) {//有搜索条件
query = spec.substring(queryStart+1, limit);//搜索条件防止query中
if (limit > queryStart)
limit = queryStart;//这里start~limit之间就是去头去尾的地址了,例如www.baidu.com,去除了http://和?name=xxx
spec = spec.substring(0, queryStart);// http://www.baidu.com
// BEGIN Android-changed: App compat
querySet = true;
// END Android-changed: App compat
}
}
int i = 0;
// Parse the authority part if any
// BEGIN Android-changed: App compat
// boolean isUNCName = (start <= limit - 4) &&
// (spec.charAt(start) == '/') &&
// (spec.charAt(start + 1) == '/') &&
// (spec.charAt(start + 2) == '/') &&
// (spec.charAt(start + 3) == '/');
boolean isUNCName = false;//判断是否局域网
// END Android-changed: App compat
if (!isUNCName && (start <= limit - 2) && (spec.charAt(start) == '/') &&
(spec.charAt(start + 1) == '/')) {//两个刚表示http://这里的两个/
start += 2;
// BEGIN Android-changed: Check for all hostname termination chars. http://b/110955991
/*
i = spec.indexOf('/', start);
if (i < 0 || i > limit) {
i = spec.indexOf('?', start);
if (i < 0 || i > limit)
i = limit;
}
*/
LOOP: for (i = start; i < limit; i++) {
switch (spec.charAt(i)) {
case '/': // Start of path
case '\\': // Start of path - see https://url.spec.whatwg.org/#host-state
case '?': // Start of query
case '#': // Start of fragment
break LOOP;
}
}
// END Android-changed: Check for all hostname termination chars. http://b/110955991
//下面都是在找URL信息
host = authority = spec.substring(start, i);
int ind = authority.indexOf('@');
if (ind != -1) {
if (ind != authority.lastIndexOf('@')) {
// more than one '@' in authority. This is not server based
userInfo = null;
host = null;
} else {
userInfo = authority.substring(0, ind);
host = authority.substring(ind+1);
}
} else {
userInfo = null;
}
if (host != null) {
// If the host is surrounded by [ and ] then its an IPv6
// literal address as specified in RFC2732
if (host.length()>0 && (host.charAt(0) == '[')) {
if ((ind = host.indexOf(']')) > 2) {
String nhost = host ;
host = nhost.substring(0,ind+1);
if (!IPAddressUtil.
isIPv6LiteralAddress(host.substring(1, ind))) {
throw new IllegalArgumentException(
"Invalid host: "+ host);
}
port = -1 ;
if (nhost.length() > ind+1) {
if (nhost.charAt(ind+1) == ':') {
++ind ;
// port can be null according to RFC2396
if (nhost.length() > (ind + 1)) {
port = Integer.parseInt(nhost.substring(ind+1));
}
} else {
throw new IllegalArgumentException(
"Invalid authority field: " + authority);
}
}
} else {
throw new IllegalArgumentException(
"Invalid authority field: " + authority);
}
} else {
ind = host.indexOf(':');
port = -1;
if (ind >= 0) {
// port can be null according to RFC2396
if (host.length() > (ind + 1)) {
// BEGIN Android-changed: App compat
// port = Integer.parseInt(host.substring(ind + 1));
char firstPortChar = host.charAt(ind+1);
if (firstPortChar >= '0' && firstPortChar <= '9') {
port = Integer.parseInt(host.substring(ind + 1));
} else {
throw new IllegalArgumentException("invalid port: " +
host.substring(ind + 1));
}
// END Android-changed: App compat
}
host = host.substring(0, ind);
}
}
} else {
host = "";
}
if (port < -1)
throw new IllegalArgumentException("Invalid port number :" +
port);
start = i;
// If the authority is defined then the path is defined by the
// spec only; See RFC 2396 Section 5.2.4.
// BEGIN Android-changed: App compat
// if (authority != null && authority.length() > 0)
// path = "";
path = null;
if (!querySet) {
query = null;
}
// END Android-changed: App compat
}
if (host == null) {
host = "";
}
// Parse the file path if any
if (start < limit) {
// Android-changed: Check for all hostname termination chars. http://b/110955991
// if (spec.charAt(start) == '/') {
if (spec.charAt(start) == '/' || spec.charAt(start) == '\\') {
path = spec.substring(start, limit);
} else if (path != null && path.length() > 0) {
isRelPath = true;
int ind = path.lastIndexOf('/');
String seperator = "";
if (ind == -1 && authority != null)
seperator = "/";
path = path.substring(0, ind + 1) + seperator +
spec.substring(start, limit);
} else {
String seperator = (authority != null) ? "/" : "";
path = seperator + spec.substring(start, limit);
}
}
// BEGIN Android-changed: App compat
//else if (queryOnly && path != null) {
// int ind = path.lastIndexOf('/');
// if (ind < 0)
// ind = 0;
// path = path.substring(0, ind) + "/";
//}
// END Android-changed: App compat
if (path == null)
path = "";
// BEGIN Android-changed
//if (isRelPath) {
if (true) {
// END Android-changed
// Remove embedded /./
while ((i = path.indexOf("/./")) >= 0) {
path = path.substring(0, i) + path.substring(i + 2);
}
// Remove embedded /../ if possible
i = 0;
while ((i = path.indexOf("/../", i)) >= 0) {
// BEGIN Android-changed: App compat
/*
* Trailing /../
*/
if (i == 0) {
path = path.substring(i + 3);
i = 0;
// END Android-changed: App compat
/*
* A "/../" will cancel the previous segment and itself,
* unless that segment is a "/../" itself
* i.e. "/a/b/../c" becomes "/a/c"
* but "/../../a" should stay unchanged
*/
// Android-changed: App compat
// if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
} else if (i > 0 && (limit = path.lastIndexOf('/', i - 1)) >= 0 &&
(path.indexOf("/../", limit) != 0)) {
path = path.substring(0, limit) + path.substring(i + 3);
i = 0;
} else {
i = i + 3;
}
}
// Remove trailing .. if possible
while (path.endsWith("/..")) {
i = path.indexOf("/..");
if ((limit = path.lastIndexOf('/', i - 1)) >= 0) {
path = path.substring(0, limit+1);
} else {
break;
}
}
// Remove starting .
if (path.startsWith("./") && path.length() > 2)
path = path.substring(2);
// Remove trailing .
if (path.endsWith("/."))
path = path.substring(0, path.length() -1);
// Android-changed: App compat: Remove trailing ?
if (path.endsWith("?"))
path = path.substring(0, path.length() -1);
}
setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
}
以上代码没啥具体技术含金量,主要是针对URL地址解析,然后调用setURL方法,
protected void setURL(URL u, String protocol, String host, int port,
String authority, String userInfo, String path,
String query, String ref) {
if (this != u.handler) {
throw new SecurityException("handler for url different from " +
"this handler");
}
// ensure that no one can reset the protocol on a given URL.
u.set(u.getProtocol(), host, port, authority, userInfo, path, query, ref);
}
调用URL的set方法初始化解析所得的值
/**
* Sets the fields of the URL. This is not a public method so that
* only URLStreamHandlers can modify URL fields. URLs are
* otherwise constant.
*
* @param protocol the name of the protocol to use
* @param host the name of the host
@param port the port number on the host
* @param file the file on the host
* @param ref the internal reference in the URL
*/
void set(String protocol, String host, int port,
String file, String ref) {
synchronized (this) {
this.protocol = protocol;
this.host = host;
authority = port == -1 ? host : host + ":" + port;
this.port = port;
this.file = file;
this.ref = ref;
/* This is very important. We must recompute this after the
* URL has been changed. */
hashCode = -1;
hostAddress = null;
int q = file.lastIndexOf('?');
if (q != -1) {
query = file.substring(q+1);
path = file.substring(0, q);
} else
path = file;
}
}
URLStreamHandler作用
/**
* The abstract class {@code URLStreamHandler} is the common
* superclass for all stream protocol handlers. A stream protocol
* handler knows how to make a connection for a particular protocol
* type, such as {@code http} or {@code https}.
* <p>
* In most cases, an instance of a {@code URLStreamHandler}
* subclass is not created directly by an application. Rather, the
* first time a protocol name is encountered when constructing a
* {@code URL}, the appropriate stream protocol handler is
* automatically loaded.
*
* @author James Gosling
* @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
* @since JDK1.0
*/
如上URLStreamHandler.java类头部代码,翻译一下它是是常见的所有流协议处理程序的超类(流协议有以下类型),在大多数情况下,URLStreamHandler的实例子类不是由应用程序直接创建的,而是首次在构建协议时(new URL)时实例化
1.HttpsHandler
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public final class HttpsHandler extends URLStreamHandler {
@Override protected URLConnection openConnection(URL url) throws IOException {
return new OkHttpClient().open(url);
}
@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
if (url == null || proxy == null) {
throw new IllegalArgumentException("url == null || proxy == null");
}
return new OkHttpClient().setProxy(proxy).open(url);
}
@Override protected int getDefaultPort() {
return 443;
}
}
和http非常相似,唯一的区别是端口号http默认是80,https默认是443
2.JarHandler
以下是jdk1.8中的源码,不同版本略有区别
重点看parseURL方法:解析URL并且通过URL.set初始化URL中的相关信息
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package sun.net.www.protocol.jar;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import sun.net.www.ParseUtil;
public class Handler extends URLStreamHandler {
private static final String separator = "!/";
public Handler() {
}
protected URLConnection openConnection(URL var1) throws IOException {
return new JarURLConnection(var1, this);
}
private static int indexOfBangSlash(String var0) {
for(int var1 = var0.length(); (var1 = var0.lastIndexOf(33, var1)) != -1; --var1) {
if (var1 != var0.length() - 1 && var0.charAt(var1 + 1) == '/') {
return var1 + 1;
}
}
return -1;
}
protected boolean sameFile(URL var1, URL var2) {
if (var1.getProtocol().equals("jar") && var2.getProtocol().equals("jar")) {
String var3 = var1.getFile();
String var4 = var2.getFile();
int var5 = var3.indexOf("!/");
int var6 = var4.indexOf("!/");
if (var5 != -1 && var6 != -1) {
String var7 = var3.substring(var5 + 2);
String var8 = var4.substring(var6 + 2);
if (!var7.equals(var8)) {
return false;
} else {
URL var9 = null;
URL var10 = null;
try {
var9 = new URL(var3.substring(0, var5));
var10 = new URL(var4.substring(0, var6));
} catch (MalformedURLException var12) {
return super.sameFile(var1, var2);
}
return super.sameFile(var9, var10);
}
} else {
return super.sameFile(var1, var2);
}
} else {
return false;
}
}
protected int hashCode(URL var1) {
int var2 = 0;
String var3 = var1.getProtocol();
if (var3 != null) {
var2 += var3.hashCode();
}
String var4 = var1.getFile();
int var5 = var4.indexOf("!/");
if (var5 == -1) {
return var2 + var4.hashCode();
} else {
URL var6 = null;
String var7 = var4.substring(0, var5);
try {
var6 = new URL(var7);
var2 += var6.hashCode();
} catch (MalformedURLException var9) {
var2 += var7.hashCode();
}
String var8 = var4.substring(var5 + 2);
var2 += var8.hashCode();
return var2;
}
}
protected void parseURL(URL var1, String var2, int var3, int var4) {
String var5 = null;
String var6 = null;
int var7 = var2.indexOf(35, var4);
boolean var8 = var7 == var3;
if (var7 > -1) {
var6 = var2.substring(var7 + 1, var2.length());
if (var8) {
var5 = var1.getFile();
}
}
boolean var9 = false;
if (var2.length() >= 4) {
var9 = var2.substring(0, 4).equalsIgnoreCase("jar:");
}
var2 = var2.substring(var3, var4);
if (var9) {
var5 = this.parseAbsoluteSpec(var2);
} else if (!var8) {
var5 = this.parseContextSpec(var1, var2);
int var10 = indexOfBangSlash(var5);
String var11 = var5.substring(0, var10);
String var12 = var5.substring(var10);
ParseUtil var13 = new ParseUtil();
var12 = var13.canonizeString(var12);
var5 = var11 + var12;
}
this.setURL(var1, "jar", "", -1, var5, var6);
}
private String parseAbsoluteSpec(String var1) {
Object var2 = null;
boolean var3 = true;
int var6;
if ((var6 = indexOfBangSlash(var1)) == -1) {
throw new NullPointerException("no !/ in spec");
} else {
try {
String var4 = var1.substring(0, var6 - 1);
new URL(var4);
return var1;
} catch (MalformedURLException var5) {
throw new NullPointerException("invalid url: " + var1 + " (" + var5 + ")");
}
}
}
private String parseContextSpec(URL var1, String var2) {
String var3 = var1.getFile();
int var4;
if (var2.startsWith("/")) {
var4 = indexOfBangSlash(var3);
if (var4 == -1) {
throw new NullPointerException("malformed context url:" + var1 + ": no !/");
}
var3 = var3.substring(0, var4);
}
if (!var3.endsWith("/") && !var2.startsWith("/")) {
var4 = var3.lastIndexOf(47);
if (var4 == -1) {
throw new NullPointerException("malformed context url:" + var1);
}
var3 = var3.substring(0, var4 + 1);
}
return var3 + var2;
}
}
**注意:**这里HttpHandler,HttpsHandler是在android源码目录下:com.android.okhttp;File,Ftp,Jar是在jdk目录下:sun.net.www.protocol。
URL作用
先自行查看URL.java注解(太长不贴代码),主要三个作用:1.对传递过来的URL相关信息存储;2.根据url传递的协议实例化对应的流协议。
getURLStreamHandler()获取流协议中:
这里流协议的创建首先判断缓存中是否存在,如果有则直接复制;还有通过工厂模式创建,都没有再实例化;这里为了防止因为多线程并行导致创建(流协议有且仅有一个)多个的问题,采取了对象锁措施,并且在锁内在“问”一遍,缓存中是否存在?工厂是否创建过?如果没有,在实现赋值并且加入到缓存中