在瀏覽器輸入網址,Enter之后瀏覽器和服務器做了什么?
如題:八股文會給出:
- DNS Resolution
- Establishing a Connection
- Sending an Http Request
- Receiving the HTTP Response
- Rendering the Web Page
但今天我斗膽插入第0.9步URL Parsing,為什么叫0.9步,是因為動作很小 ,但確實不可缺少。
URL( uniform resource locator)由四部分組成: scheme、domain、path、resource
URL Parsing做了2個事情:
- prase url:只有解析分離出domain,才能有后續的第1步:
DNS resolution - url_encode
本文我主要想聊一聊 瀏覽器url_encode 和對應的服務端 url normalization
瀏覽器url_encode
在瀏覽器插入https://www.baidu.com/s?wd=博客園馬甲哥,Enter之前童鞋們可嘗試拷貝地址欄, 粘貼到任意位置, 內容是:https://www.baidu.com/s?wd=%E5%8D%9A%E5%AE%A2%E5%9B%AD%E9%A9%AC%E7%94%B2%E5%93%A5, 這就是瀏覽器自動url_encode的結果, 瀏覽器會拿這個網址去做 dns、request等行為。
瀏覽器中就是url_encoded的結果。

1. 為什么會有url_encode?
https://zhuanlan.zhihu.com/p/557035152?utm_id=0
在URL的最初設計時,希望可以通過書面轉錄,比如寫在餐巾紙上告訴另外一人,因此URI的構成字符必須是可寫的ASCII字符。
中文不在ASCII字符中,因此中文出現在URL地址中時,需要進行編碼;同時可書寫的ASCII字符中,存在一些不安全字符也需要轉碼,如空格(空格容易被忽略,也容易意想不到的原因引入)。
URL encoding replaces unsafe ASCII characters with a "%" followed by two hexadecimal digits.
URLs cannot contain spaces. URL encoding normally replaces a space with a plus (+) sign or with %20.
瀏覽器會自動對請求路徑和查詢字符串做url encode,但不會對請求頭的值做url encode,是否編碼由開發者根據業務決定。
默認按照UTF-8編碼, UTF-8 到底是什么意思?
例如:漢字 “你好”
- UTF-8字節流打印為:-28 -67 -96 -27 -91 -67
- 對應的16進制表示為:E4 BD A0 E5 A5 BD
- URLEncode編譯后為:%E4%BD%A0%E5%A5%BD
當然服務端會對應的url_decode函數, 編碼/解碼的次數需要對應。
注意,多次url編碼不是冪等的,多次url解碼是冪等的。
--
各種語言都提供urlencode、decode的支持,這種支持不僅是url,也有對字符串的支持。
2. js 中的encodeURI() vs encodeURIComponent()
encodeURI是js 中內置的全局函數,用于url_encode,不會對以下特殊字符編碼,這也是為了確保url中原生字符的正確表達:
A–Z a–z 0–9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , #
const uri = 'https://mozilla.org/?x=шеллы';
const encoded = encodeURI(uri);
console.log(encoded);
// Expected output: "https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
encodeURIComponent 也是全局函數,但他的用途是對字符串做完整的url_encode, 這個函數會對上面排除的字符做編碼,這個函數一般用于已知是特殊字符需要做url編碼。
// Encodes characters such as ?,=,/,&,:
console.log(`?x=${encodeURIComponent('test?')}`);
// Expected output: "?x=test%3F"
3. 我為什么會關注這個問題?
一般web框架會為我們自動解碼,所以我們在直接處理http請求時可以忽略該問題。
但是在自行使用 httpclient反代時就要注意這個問題。
直連應用的時候,瀏覽器的發出的是url_encode請求;
接入openresty網關時, 內部使用的$uri nginx內置變量, 這是一個被normalization的uri字符串,與應用預期的接收不符,應用報錯。
-
$uri: (ngx.var.uri) current URI in request,normalized
The value of $uri may change during request processing, e.g. when doing internal redirects, or when using index files.
-
$request_uri: (ngx.var.request_uri) full original request URI (with arguments)
4. 服務端URI規范化
URI Normalization是與瀏覽器url parsing 相對應的操作,由服務端根據RFC 3986 normalization實現。
常見的URL規范化步驟包括:
① 百分號編碼解碼:將百分號編碼的字符(如 %20)解碼為其對應的字符(如空格)。
② 小寫化:將主機名和某些轉義字符的小寫化。
③ 路徑清理:處理路徑中的 . 和 .. 部分。
④ 移除默認端口:如果端口是默認端口(例如 HTTP 的 80 端口),則將其移除。
⑤ 排序查詢參數:有些實現會對查詢參數進行排序,但這是可選的,且不是所有實現都包含這一步驟。
Nginx 中的URI 規范化
在 Nginx 中,通常使用
$uri變量來表示已規范化的 URI, 不包括查詢字符串。查詢字符串可以通過$args變量獲取, 規范化后的URI(即$uri變量)不包含查詢字符串。
目前我見到的web框架都自動實現了url normalization, 完成了url_decoded, 不需要我們手動再去解碼。
x-www-form-urlencoded 編碼模式
另一個與瀏覽器url_encoded編碼,相關的是x-www-form-urlencoded編碼模式,這也是form表單默認的編碼格式。
https://dev.to/sidthesloth92/understanding-html-form-encoding-url-encoded-and-multipart-forms-3lpa
<form action="/urlencoded?firstname=sid&lastname=sloth" method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="username" value="sidthesloth"/>
<input type="text" name="password" value="slothsecret"/>
<input type="submit" value="Submit" />
</form>
請求body會產生 username=sidthesloth&password=slothsecret url_encode編碼值, 這和請求url: /urlencoded?firstname=sid&lastname=sloth 一樣,都會被url_encoded。
5. 常見的httpclient是否自動url_encode?
.NET、go、lua的HttpClient(包括curl)都不會自動對 URL 進行編碼。 如果我們的httpclient想要模仿瀏覽器發出的url_encoded請求,你需要自己手動進行 URL 編碼。
- [C#] System.Net.WebUtility.UrlEncode
- [golang] url.QueryEscape(rawURL)
- [lua] ngx.escape_uri(str, 0) https://stackoverflow.com/questions/78225022/is-there-a-lua-equivalent-of-the-javascript-encodeuri-function
- curl
--data-urlencode選項提供了url_encoded 編碼能力。
curl --data-urlencode "name=John Doe (Junior)" http://example.com
# name=John%20Doe%20%28Junior%29
總結
本文從一個常見的話題聊起, 提出了瀏覽器發出請求時一個容易被忽略的階段url_encoded, 與此同時服務端根據 url normalization協議完成了url_decoded, 客戶端和服務端相輔相成, 果然優秀程序的終點是標準協議。
從一個小點 url_encoded,延伸到x-www-form-urlencoded 表單編碼模式在body中的表現; 延伸到常規的httpclient如果要模仿瀏覽器請求,應該如何做url_encoded。
本文來自博客園,作者:{有態度的馬甲},轉載請注明原文鏈接:http://www.rzrgm.cn/JulianHuang/p/18154545
歡迎關注我的原創技術、職場公眾號, 加好友談天說地,一起進化

浙公網安備 33010602011771號