常見則不疑:URL

要是說哪個Web開發者不知道URL,可以說是天方夜譚了。但是要是問哪位詳細的瞭解過URL,可能真就剩下寥寥數人了。

老張實際工作中發現有些同事真的從來沒有去主動了解過URL。URL歷史悠久,URL應用廣泛,URL形式多樣且標準寬泛,URL熟悉且陌生。

今天老張把URL的講解放在《Web開發進階》系列的第一篇,給大家介紹一下URL。

 

URI:URL和URN

 

我們常說的URL(Uniform Resource Locator,統一資源定位符)其實是URI(Uniform Resource Identifier,統一資源標識符)的子集。除了URL,URI還有另一種形式——URN(Uniform Resource Name,統一資源名)。

 

通過URI,客戶端就可以指定他們想要獲取的互聯網資源,但是URL和URN的本質是有區別的。

  • URL描述了特定服務器上某資源的特定位置。

  • URN與特定的服務器無關,僅需通過資源名即可定位並訪問資源。

 

比起URN,URL更爲我們所熟知。作爲一個開發者可以很容易的分辨出下面三個URL指向不同的資源位置:

http://www.example.com:8080/1.htmlhttp://www.example.com/1.htmlhttp://www.example.com/2.htm

 

習慣了URL,可能很多人都不知道URN的存在,更好奇爲什麼URN不需要指定服務器位置。其實,下載用的磁力鏈接就是URN,下面的示例應該能夠很好幫助理解:

​​​​​​​magnet:?xt=urn:btih:EP3XFJ7BOAFA6GFJTNZKRQ6CIN7A5AB5

只需要有了這段神祕代碼,我們就可以下載相應的資源,而不需要關心資源實際存在於互聯網的哪個角落。

 

URL的常見形式

 

除了HTTP之外,還有多種多樣的協議也使用URL(比如FTP)來定位資源。大多數協議使用的URL格式都可以滿足以下格式:

​​​​​​​<scheme>://<user>:<password>@<host>:<post>/<path>;<params>?<query>#<frag>

  • scheme:協議名。指明客戶端訪問服務器時使用的協議類型,常見的有HTTP、HTTPS、FTP、mailto以及telnet等。

  • user:用戶名。常見於FTP協議。

  • password:密碼。和user一起用於鑑權。

  • host:主機地址。可以是ip,也可以域名。

  • port:端口。缺省時使用默認值,不同協議的默認值有所區別。

  • path:路徑。一般來說符合UNIX文件路徑規範。

  • params:參數。多個參數之間同樣使用 ”:” 分割。

  • query:查詢參數。不同協議之間其形式可能有所區別。

  • frag:片段。主要用於客戶端。

以上是URL通用形式的介紹,幾乎囊括了請求互聯網資源所需要的所有信息。具體到HTTP,其格式就要簡單許多。

 

 

HTTP協議的URL形式

 

RCF1945《超文本傳輸協議——HTTP/1.0》)給出了HTTP_URL的標準形式:

http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]

其中port是可以省略的,如果省略,則使用默認值80。並且該文檔指明瞭協議實現需要支持ip形式的host,見過帶有IPV4地址的URL,不知道大家見過帶有IPV6地址的URL嗎?

 

注意,雖然標準描述並沒有提到frag,但是實際各瀏覽器都是支持錨點的,甚至有的還支持user。

 

實現一個URL解析和組裝函數

 

文本形式的URL雖然擴展性很強,但是同HTML一樣,其對機器的友好性卻遠不如二進制形式,加上RFC屬於規範,本身並不包含強制性,所以HTTP_URL具體實現之間會有所差別。

綜上,司空見慣的URL解析起來就顯得沒那麼簡單了。老張在這裏用Python實現了一個玩具版的URL解析和組裝函數,僅用於幫助大家理解本篇文章,請勿用於實際開發。

"""
@auther: zhang3
"""
__all__ = ["parse_http_url", "unparse_http_url"]


def unparse_http_url(scheme, host, port=80, path="", query="", frag=""):
    """
    根據入參拼接http_URL
    """
    if scheme.lower() != "http":
        raise ValueError("only support http scheme")
    url = "%s://" % scheme

    if not host:
        raise ValueError("host is needed")
    url += host

    if not port or port in [80, "80"]:
        pass
    else:
        url += ":%s" % port

    if path:
        if not path.startswith("/"):
            raise ValueError("illegal path")
        url += path

    if query:
        url += "?" + query
    if frag:
        url += "#" + frag

    return url


def parse_http_url(url):
    """
    將url解析爲 http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query [ "#" frag ]]]
    僅支持http協議格式

    >>> parse_http_url("http://www.example.com:8080/1.html")
    ('http', 'www.example.com', 8080, '/1.html', '', '')

    >>> parse_http_url("http://www.example.com/1.html?name=zhang3")
    ('http', 'www.example.com', 80, '/1.html', 'name=zhang3', '')

    >>> parse_http_url("http://www.example.com/2.html#anchor")
    ('http', 'www.example.com', 80, '/2.html', '', 'anchor')
    """
    if not url.lower().startswith("http://"):
        raise ValueError("scheme must be http")
    scheme, url = url.split("://")
    loc, url = _split_loc(url)
    host, port = _split_host_port(loc)
    query = frag = ""
    if "#" in url:
        url, frag = url.split("#")

    if "?" in url:
        url, query = url.split("?")

    path = url

    return scheme, host, port, path, query, frag



def _split_loc(url):
    delim_index = len(url)
    for delim in "/?#":
        i = url.find(delim)
        if i >= 0:
            delim_index = min(i, delim_index)
    return url[:delim_index], url[delim_index:]


def _split_host_port(loc):
    host, port_ = "", ""
    if loc.startswith("["):
        i = loc.find("]")
        if i < 0:
            raise ValueError("illegal IPV6 host")
        host, port_ = loc[:i+1], loc[i+2:]
    elif ":" in loc:
        host, port_ = loc.split(":")
    else:
        host = loc

    if port_:
        if not port_.isdigit():
            raise ValueError("illegal port")
        port = int(port_)
    else:
        port = 80

    return host, port


if __name__=='__main__':
    import doctest
    doctest.testmod()

在命令行執行幾條測試命令,效果如下:

>>>url = "http://www.example.com/1.html?name=zhang3"

>>>parse_http_url(url)
('http', 'www.example.com', 80, '/1.html', 'name=zhang3', '')

>>>unparse_http_url(*parse_http_url(url))
'http://www.example.com/1.html?name=zhang3'

備周則意怠,常見則不疑。

       ——《三十六計 · 瞞天過海》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章