private final String protocol; //协议
private final String username; //用户名
private final String password; //密码
private final String host; //主机号
private final int port; //端口(默认值为0)
private final String path; //路径(是host后面, "/" 到 "?" 之间的字符串值,而interface接口信息是参数"interface"的值)
private final Map<String, String> parameters; //参数键值对:存的是url中的参数键值,即?与&分隔的键值对
private final Map<String, Map<String, String>> methodParameters; //方法级别的参数设置,如<"sayHello2, ">
private volatile transient String full; //带有完整信息的url字符串
private transient String serviceKey; //服务对应的键:格式如:group/path:version
public URL(String protocol,
String username,
String password,
String host,
int port,
String path,
Map<String, String> parameters,
Map<String, Map<String, String>> methodParameters) {
if (StringUtils.isEmpty(username)
&& StringUtils.isNotEmpty(password)) {
throw new IllegalArgumentException("Invalid url, password without username!"); //密码与用户名要么没有,要么一起出现
}
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.port = Math.max(port, 0);
this.address = getAddress(this.host, this.port); //构建地址,由host、port组成,如:127.0.0.1:2181(port<=0时,只包含host值)
// trim the beginning "/"
while (path != null && path.startsWith("/")) { //循环去掉path前的"/"
path = path.substring(1);
}
this.path = path;
if (parameters == null) {
parameters = new HashMap<>();
} else {
parameters = new HashMap<>(parameters);
}
this.parameters = Collections.unmodifiableMap(parameters); //unmodifiableMap:不可修改的Map,(内部维护者用final修饰的Map,只读不可修修改 put、remove等)
this.methodParameters = Collections.unmodifiableMap(methodParameters);
}
public static URL valueOf(String url) { //按URL语法规则进行解析,并构建URL对象。 如输入字符串为:zookeeper://127.0.0.1:2181?name=test
if (url == null || (url = url.trim()).length() == 0) { //URL语法格式:protocol://username:password@host:port/path?key1=value1&key2=value2
throw new IllegalArgumentException("url == null");
}
String protocol = null;
String username = null;
String password = null;
String host = null;
int port = 0;
String path = null;
Map<String, String> parameters = null;
int i = url.indexOf('?'); // separator between body and parameters(使用"?" 来分隔出 url的主体和参数)
if (i >= 0) { //(url的参数parameters处理)判断是否包含参数,若url中包含"?",则先把参数解析出来,把"?"后面的字符串先处理掉
String[] parts = url.substring(i + 1).split("&"); //按&符号分隔出参数键值对
parameters = new HashMap<>();
for (String part : parts) { //遍历参数键值对,如name=test
part = part.trim();
if (part.length() > 0) {
int j = part.indexOf('='); //按等号解析,解析出参数的键值对
if (j >= 0) {
String key = part.substring(0, j); //key如:name
String value = part.substring(j + 1);//value如:test (dubbo不会对key、value去空格,若有空格,原样输出,如key=value1 value2,那么值就为"value1 value2")
parameters.put(key, value); //设置到url的参数map集合中
// compatible with lower versions registering "default." keys
if (key.startsWith(DEFAULT_KEY_PREFIX)) { //若参数名是"default."开头的,则把这个前缀去掉存储,即这个值会有两个不同的key
parameters.putIfAbsent(key.substring(DEFAULT_KEY_PREFIX.length()), value);
}
} else { //若没有带上等号的,则键值对都存一样的值
parameters.put(part, part); //若没有带上等号,则键值都存为一样的(如?k0&k1=v1,则k0的值存为k0)
}
}
}
url = url.substring(0, i); //截取参数前的url,如zookeeper://127.0.0.1:2181
}
i = url.indexOf("://"); //解析出protocol值
if (i >= 0) { //解析协议,如:zookeeper://127.0.0.1:2181
if (i == 0) { // "://"前面没有设置协议时,就会抛出异常
throw new IllegalStateException("url missing protocol: \"" + url + "\"");
}
protocol = url.substring(0, i); //如:zookeeper
url = url.substring(i + 3); //如:127.0.0.1:2181
} else { //未设置协议(系统没有设置默认协议,若没设定,则protocol=null)
// case: file:/path/to/file.txt(兼容不规范的写法)
i = url.indexOf(":/");
if (i >= 0) {
if (i == 0) {
throw new IllegalStateException("url missing protocol: \"" + url + "\""); //URL处理protocol是必须的,其它属性都是可选的
}
protocol = url.substring(0, i);
url = url.substring(i + 1);
}
}
i = url.indexOf('/'); //解析出资源路径,如127.0.0.1:2181/all的 path为all
if (i >= 0) {
path = url.substring(i + 1); //接口路径,即接口路径,如com.foo.BarService,path中没有"?"后面带的参数
url = url.substring(0, i);
}
i = url.lastIndexOf('@'); //解析出用户名、密码(未设置用户名、密码,则为null,以最后一个@来分隔,用户名和密码可包含@符号)
if (i >= 0) {
username = url.substring(0, i);
int j = username.indexOf(':');
if (j >= 0) {
password = username.substring(j + 1); //解析出用户名、密码
username = username.substring(0, j);
}
url = url.substring(i + 1);
}
i = url.lastIndexOf(':'); //解析出host与port,url如:127.0.0.1:2181(以最后一个":"做分隔)
if (i >= 0 && i < url.length() - 1) { //解析host、ip
if (url.lastIndexOf('%') > i) {
// ipv6 address with scope id (若ipv6地址带上范围id,则忽略不处理,即不做截取)
// e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0 对应的host、address为 fe80:0:0:0:894:aeec:f37d:23e1%en0
// see https://howdoesinternetwork.com/2013/ipv6-zone-id
// ignore
} else { //host、port分隔处理
port = Integer.parseInt(url.substring(i + 1));
url = url.substring(0, i);
}
}
if (url.length() > 0) { //若url字符串经过前面处理后,还存在字符,则设置为host,如url="10.20.130.230"
host = url;
}
return new URL(protocol, username, password, host, port, path, parameters); //构建新的URL对象
}
private String buildString(boolean appendUser, boolean appendParameter, boolean useIP, boolean useService, String... parameters) {
StringBuilder buf = new StringBuilder();
if (StringUtils.isNotEmpty(protocol)) {
buf.append(protocol);
buf.append("://");
}
if (appendUser && StringUtils.isNotEmpty(username)) { //需要附加用户信息且用户名、密码不为空时进行拼接
buf.append(username);
if (StringUtils.isNotEmpty(password)) {
buf.append(":");
buf.append(password);
}
buf.append("@");
}
String host;
if (useIP) { //是否使用ip作为主机号
host = getIp(); //使用ip
} else {
host = getHost(); //使用host
}
if (StringUtils.isNotEmpty(host)) { //主机号不为空时,进行拼接
buf.append(host);
if (port > 0) {
buf.append(":");
buf.append(port);
}
}
String path;
if (useService) { //是否使用服务key作为path值
path = getServiceKey(); //服务key,在有group时,是会带上group值的,如group/xxx.service
} else {
path = getPath();
}
if (StringUtils.isNotEmpty(path)) {
buf.append("/");
buf.append(path);
}
if (appendParameter) { //是否需要添加附加参数
buildParameters(buf, true, parameters);
}
return buf.toString();
}
public static URLBuilder from(URL url) { //读取url内容,创建构造器URLBuilder
String protocol = url.getProtocol();
String username = url.getUsername();
String password = url.getPassword(); //读取URL内容
String host = url.getHost();
int port = url.getPort();
String path = url.getPath();
Map<String, String> parameters = new HashMap<>(url.getParameters());
Map<String, Map<String, String>> methodParameters = new HashMap<>(url.getMethodParameters());
return new URLBuilder( //将传入的URL参数,设置到URLBuilder的成员属性中
protocol,
username,
password,
host,
port,
path,
parameters,
methodParameters);
}
public URL build() { //用URLBuilder维护的参数构建URL
if (StringUtils.isEmpty(username) && StringUtils.isNotEmpty(password)) {
throw new IllegalArgumentException("Invalid url, password without username!");
}
port = port < 0 ? 0 : port;
// trim the leading "/"
int firstNonSlash = 0; //Slash: 斜杠
if (path != null) {
while (firstNonSlash < path.length() && path.charAt(firstNonSlash) == '/') {
firstNonSlash++; //找到斜杠的位置,并且加1
}
if (firstNonSlash >= path.length()) {
path = "";
} else if (firstNonSlash > 0) {
path = path.substring(firstNonSlash); //获取去除斜杠后的子串
}
}
if (CollectionUtils.isEmptyMap(methodParameters)) { //创建URL对象
return new URL(protocol, username, password, host, port, path, parameters);
} else {
return new URL(protocol, username, password, host, port, path, parameters, methodParameters);
}
}
public static URL parseEncodedStr(String encodedURLStr) { //解析编码后的URL字符串,产生对应的URL对象
Map<String, String> parameters = null; //编码前的字符串/context/path?version=1.0.0&application=morgan,编码后的字符串:%2Fcontext%2Fpath%3Fapplication%3Dmorgan%26version%3D1.0.0
int pathEndIdx = encodedURLStr.indexOf("%3F");// '?' 查找参数分隔符(%3F对应的ASCII值为'?')
if (pathEndIdx >= 0) { //url中包含参数
parameters = parseEncodedParams(encodedURLStr, pathEndIdx + 3); //取%3F后面的字符串处理
} else { //url中不包含参数
pathEndIdx = encodedURLStr.length();
}
//decodedBody format: [protocol://][username:password@][host:port]/[path]
String decodedBody = decodeComponent(encodedURLStr, 0, pathEndIdx, false, DECODE_TEMP_BUF.get());
return parseURLBody(encodedURLStr, decodedBody, parameters);
}
private static Map<String, String> parseEncodedParams(String str, int from) { //解析出编码url中的参数键值对
int len = str.length(); //取出字符串长度
if (from >= len) { //起始位置不能超过字符串长度
return Collections.emptyMap();
}
TempBuf tempBuf = DECODE_TEMP_BUF.get(); //从本地线程中获取缓存的TempBuf
Map<String, String> params = new HashMap<>();
int nameStart = from;
int valueStart = -1;
int i;
for (i = from; i < len; i++) { //遍历url字符串的字符(from为参数起始位置)
char ch = str.charAt(i);
if (ch == '%') { //遇到百分号分隔符,解码得到原始的字符(ASCII中的字符,经过url编码后,都是类似 %xx,即%后面带上两个16进制字符)
if (i + 3 > len) { //分隔符不是完整的情况,抛出异常,比如%3、%等,应该是%3D,百分号后面带两个十六进制数
throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str);
}
ch = (char) decodeHexByte(str, i + 1); //解码16进制字符,得到原有字符,如"3D"处理后,得到字符'='
i += 2;
}
switch (ch) { //找到指定的分隔符,做对应的处理
case '=': //按键值对处理
if (nameStart == i) { //url未经过编码时,进入此处
nameStart = i + 1;
} else if (valueStart < nameStart) {
valueStart = i + 1; //记录值的下标
}
break;
case ';':
case '&': //多个参数时,进行参数拼接
addParam(str, true, nameStart, valueStart, i - 2, params, tempBuf);
nameStart = i + 1;
break;
default: //非分隔符,不做处理
// continue
}
}
addParam(str, true, nameStart, valueStart, i, params, tempBuf);
return params;
}
URL都承载了什么信息,都有哪些功能?
URLBuilder都有什么功能用途?
URLStrParser#DECODE_TEMP_BUF成员变量的用途是什么?