URI = Universal Resourcce Identifier
URL = Uniform Resource Locators
URN = Uniform Resource Name
URI、URL与URN都是用来标识某个资源的,其中URI是一个大的概念,URL与URN是它的两个不同的实现,它们三者之间的关系如下图:
URL使用'位置'来标识一个资源,类似于现实中的家庭住址。举个例子,比如一个URL为:
http://www.baidu.com/a/b/c.html
就标识出在百度服务器上的路径a下有路径b,路径b下面有c.html文件。
URN通过'名字'来表示一个资源,类似于现实中的身份证号,只要取到身份证号,就能定位到这个号码后面对应的人。
URI的组成
一个URI的标准组成为:scheme authority path query fragment
scheme
组成scheme的字符包括:字母、数字、加号(+)、点(.)、连字符(-),其中scheme的首字符必须是字母,对于scheme的定义,RFC1738与RFC3986是一致的。
scheme的字符必须都是小写,但是在具体实现中,大写的scheme与小写的scheme是等价的,也就是说,HTTP = http。
authority
authority由三部分组成,authority = [userinfo@] host [:port],中括号表示是可选的。
userinfo提供获取这个资源的认证信息,比如用户名、密码,不同部分使用冒号(:)分割。举个例子:
https://username:passport@www.baidu.com:80
https://username@www.baidu.com:80
https://www.baidu.com
都是合法的,也就是说username:passport、:passport、:port这三者是可以任意省略的。
userinfo可以使用的字符集定义如下:
RFC1738
userinfo = *[ uchar | ";" | "?" | "&" | "=" | "@" | ":" ]
RFC3986
userinfo = *[ unreserved | pct-encoded | sub-delims | ":" ]
* 表示0个或者多个
[ ] 表示可选的,不是必须的
| 表示可以选择其中之一
uchar、unreserved、pct-encoded、sub-delims以及下文出现的字符集范围都定义在文末。
比较RFC1738与RFC3986的定义,两者字符集一致。
host
标识主机名,可以使用的字符集定义如下:
RFC1738
host = hostname | hostnumber
hostname = *[ domainlabel "." ] toplabel
domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit
toplabel = alpha | alpha *[ alphadigit | "-" ] alphadigit
alphadigit = alpha | digit
hostnumber = digits "." digits "." digits "." digits
RFC3986
host = IP-literal | IPv4address | reg-name
IP-literal = "[" IPv6address | IPvFuture "]"
reg-name = *[ unreserved | pct-encoded | sub-delims ]
IPvFuture是一种还在开发中的IP地址表示法,详情可以参考RFC3986文档。比较RFC1738与RFC3986的定义,RFC3986定义的host字符集要多于RFC1738
port
是端口号,字符集只能是数字
path
除去其他部分,剩下的就是path部分,path以"/"划分。path可以使用的字符集如下:
RFC1738
urlpath = *xchar
xchar = unreserved | reserved | escape
RFC3986
urlpath = *pchar
pchar = unreserved | pct-encoded | sub-delims | ":" | "@"
两者定义的path字符集几乎一样,只是RFC1738比RFC3986多了一个?。但是问号被用来作为query起始的标志,所以?是不会被算进path里面的,从这个意义上说,这两者定义的path字符集一样。
query
表示查询字符串,从第一个问号(?)开始,到第一个井号(#)之间就是query。query可以使用的字符集定义如下:
query = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
uchar = unreserved | escape
query = *[ pchar | "/" | "?" ]
RFC1738定义的字符集比RFC3986定义的字符集少了一个"/",但是iOS中NSURL虽然遵循RFC1738,也把"/"当成是正常的字符。
fragment
通常用来定位到一个文档中的某个部分,从第一个井号(#)后面开始都是fragment。fragment定义的字符集与query一样。
URL字符编码
URL有时需要对字符进行编码,编码格式为:百分号 + HEX + HEX,比如逗号(,)被编码成%2C。编码的时,对于十六进制中的字母,大写和小写是等价的。也就是说,两个URL,不会因为编码使用的大小写不一样就是两个不一样的URL。
在URL里面,字符需要编码的情形有3种。
1 不可打印的字符
组成URL的字符都是可打印的ASCII字符,对于无法进行打印的字符(ASCII区间为:0x00-0x1F、0x7F)需要进行编码。
2 不安全的字符需要编码
有些字符使用起来并不安全,比如空格,在URL里面就需要进行编码。RCF1738里面规定的不安全字符有:
空格 | ">" | "
需要注意的是,像%、#并不是在URL的任何地方都需要编码,比如作为fragment标识的时候,#就无需编码,但是如果#出现在其他地方,就需要编码了。
在上面的字符集需要注意的是"[" "]"两个字符,iOS中NSURL遵循RFC1738,但是这两个字符集即使不编码,也可以出现在除了scheme的任何地方,NSURL正常返回;但是对于遵循RFC3986的NSURLComponents来说,除了以IPV6的形式表示host之外,"[" "]"不能出现在任何其他地方。
3 保留字符集
RFC1738和RFC3986规定的reserve保留字符集,如果你确实是想使用这些保留字符集本身,而不是它们在URL里面的特殊意义,那么最好是将它们编码。
字符集定义
RFC1738
lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" |
"i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" |
"q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
"y" | "z"
hialpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
"J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
"S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
alpha = lowalpha | hialpha
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
"8" | "9"
safe = "$" | "-" | "_" | "." | "+"
extra = "!" | "*" | "'" | "(" | ")" | ","
hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
"a" | "b" | "c" | "d" | "e" | "f"
escape = "%" hex hex
unreserved = alpha | digit | safe | extra
uchar = unreserved | escape
xchar = unreserved | reserved | escape
reserved = ";" | "/" | "?" | ":" | "@" | "&" | "="
RFC3986
reserved = gen-delims | sub-delims
gen-delims = ":" | "/" | "?" | "#" | "[" | "]" | "@"
sub-delims = "!" | "$" | "&"| "'"| "(" / ")"
| "*" | "+" | "," | ";"| "="
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
pct-encoded = "%" HEXDIG HEXDIG ;即百分号编码格式
参考资料
https://tools.ietf.org/html/rfc1738#section-3.3
https://www.ietf.org/rfc/rfc3986.txt