「Python 編程」編碼實現網絡請求庫中的 URL 解析器

摘要:怎麼寫出更短的代碼並非此次要討論的話題。今天咱們來研究一下:運行代碼的計算機是如何找到目標服務器的?git

相信各位 Python 開發者都用過 Requests 庫,有些朋友還用過 WebSockets 庫。這裏回顧一下它們的基本用法,例如使用 Requests 庫向目標網站發出 GET 請求:github

import requests


url = "https://www.baidu.com"
resp = requests.get(url)
print(resp.status_code)  # output -> 200
複製代碼

使用起來很是簡單,咱們很輕鬆地向目標網站發出了請求並打印輸出響應狀態碼。固然,你還能夠把它縮短:web

import requests


print(requests.get("https://www.baidu.com").status_code)  # output -> 200
複製代碼

怎麼寫出更短的代碼並非此次要討論的話題。今天咱們來研究一下:運行代碼的計算機是如何找到目標服務器的?編程

顯然,你的第一映象是 IP 地址和端口號。安全

沒錯,就是 IP 地址和端口號。bash

但你明明輸入的是 URL 地址,怎麼就 IP + 端口號呢?服務器

URL 解析的緣由

一會兒你也回答不上來吧?websocket

咱們能夠將上方代碼的邏輯,即計算機向目標服務器發出請求並拿到響應信息的過程抽象成下圖:網絡

程序輸入的是 https://www.baidu.com,但最終要解析出具體的 IP 地址和端口號才能訪問,例如 183.232.231.172:443socket

網絡交互實際上屬於 Socket 編程的範疇,不管是 Requests 仍是 WebSockets 庫,最終都會經過 Socket 與目標網站的服務器進行交互。而 Socket 編程中並不能直接使用域名,而是採用 IP + 端口號這種形式進行尋址的。

假設你如今須要編寫一個網絡請求庫,有多是 HTTP 協議的,也有多是 WebSocket 協議的。你要解決的第一個問題就是解析 URL,將網址轉換成 IP + 端口號,甚至還須要分割出協議類型、資源路徑以及是否採用更安全的傳輸方式等。

URL 解析格式

以 WebSocket 協議方面的客戶端庫爲例,在雙端確認鏈接以前有一個「握手」的過程,這個過程以前已經須要雙端的 IP 和端口號等信息了。下面的代碼描述了 WebSocket 發出「握手」請求以前,雙端創建鏈接時須要用到的基本信息:

# aiowebsocket
 reader, writer = await asyncio.open_connection(host=host, port=port, ssl=ssl)
複製代碼

也就是 hostportssl

大部分的 WebSocket 服務給出的都是域名,例如 wss://echo.websocket.org。「握手」時還會用到資源路徑。

接下來,咱們來嘗試一下,如何將域名轉換爲 IP + 端口號和 is ssl 這樣的格式。

代碼實現 URL 解析

開始以前,咱們先規劃一下基本步驟:

而後肯定要使用的標準庫:解析 URL 固然要用到 urllib 庫中的 url parse;解析 address 則須要用到 socket 庫;爲了方面取數據,能夠嘗試使用 collections 庫中的 namedtuple。

首先引入這幾個庫:

# 崔慶才和韋世東邀請你關注公衆號:進擊的Coder
import socket
from collections import namedtuple
from urllib.parse import urlparse
複製代碼

而後定義輸出結構,對應代碼以下:

REMOTE = namedtuple('REMOTE', ['scheme', 'hostname', 'address', 'port', 'resource', 'ssl'])
複製代碼

而後定義一個方法,咱們傳入 URL,得到解析好的 REMOTE 對象。方法定義以下:

def parses(url: str) -> REMOTE:
    pass
複製代碼

待會咱們在 pass 處編寫屬於該方法的代碼。

最開始要解析 URL,得到 schemehostname,對應代碼以下:

url = urlparse(url)
複製代碼

urlparse 方法會返回一個 ParseResult 對象,對象大致格式以下:

ParseResult(scheme='wss', netloc='echo.websocket.org', path='', params='', query='', fragment='')
複製代碼

有了 schemehostname 後,就能夠獲得 portis ssladdress。對應代碼以下:

# 崔慶才和韋世東邀請你關注公衆號:進擊的Coder
scheme = url.scheme
address = url.hostname
port = url.port or (443 if scheme == 'wss' else 80)
ssl = True if scheme == 'wss' else False
複製代碼

WebSocket 協議中只有兩種協議頭:wswss。它們對應的端口分別是 80443,這裏藉助 scheme 的值進行判斷便可獲得答案。同理,也直接獲得了 is ssl 答案。

拿到 hostname 後,調用 socket 庫的 getbyhostname 方法就可以獲得目標服務器的 IP 地址了。對應代碼以下:

address = socket.gethostbyname(hostname)
複製代碼

至於資源路徑,它早已存在於 ParseResult 對象中,直接取出便可:

resource = url.path
複製代碼

要注意的是,有些 URL 中還會攜帶請求正文(即參數和值)。因此這裏須要取 query,並將其拼接到 resource 中:

if url.query:
    resource += '?' + url.query
複製代碼

至此,咱們已經拿到了所需的全部數據。

如今將它們裝在到 REMOTE 結構中,返回給調用方:

return REMOTE(scheme, hostname, address, port, resource, ssl)
複製代碼

此時,調用 parses 方法後就會拿到 REMOTE 結構,它的取值方式很舒服,用 . 符號取值便可。例如:

# 夜幕團隊邀請你關注公衆號:NightTeam
res = parses("ws://echo.websocket.org?sign=i9878")
print(res.address, res.port, res.resource)
複製代碼

代碼運行結果以下:

174.129.224.73 
80 
?sign=i9878
複製代碼

這樣,咱們就完成了 URL 解析的代碼編寫。

小結

代碼雖然很少,邏輯也並不複雜。但咱們完整實現了網絡請求庫中的 URL 解析模塊,這表明着完成了編寫庫的基石之一。

在這個過程中,咱們瞭解到雙端通訊的基本過程和要用到的信息。在編碼中學會了如何將 urlparsesocketnamedtuple 結合到一塊兒。

並且,你今天學到了 namedtuple 這個新姿式!

夜幕團隊邀請你關注公衆號:NightTeam

本篇文章由一文多發平臺 ArtiPub 自動發佈

相關文章
相關標籤/搜索