網絡
-
什麼是服務器
-
如何訪問服務器
-
http協議
-
請求消息格式
- 請求頭
- 請求體
- GET和POST
-
響應消息格式
- 響應頭
- 響應體
-
-
在瀏覽器中輸入一個頁面地址, 按下回車以後發生了什麼
-
ajax
什麼是服務器?
如果所有的程序都是單機的, 會導致什麼後果呢?
-
數據難以共享
-
受計算機配置的影響, 運算速度差異巨大
-
個人計算機的安全性堪憂, 可能受到惡意程序的影響
而有了服務器之後這些問題都可以得到解決
服務器在不同的語境中表達了不同的含義
-
一臺獨立的計算機
比如我們說要去買一臺服務器 配置一臺服務器, 就是代表的是一臺獨立的計算器 -
一個應用程序
絕大部分開發者, 通常把服務器看做是一個應用程序
無論他是哪個概念, 他都具有以下的特點:
-
能夠通過網絡, 被其他應用程序所訪問
-
能夠提供一些服務
如果一個服務器(應用程序), 它僅僅爲一個瀏覽器提供訪問網站服務, 我們將它稱之爲web服務器
實際上目前的web服務器和遊戲服務器界限已經非常模糊, 可以認爲, 凡是在互聯網中提供服務的服務器都是web服務器
通常我們把訪問服務器的程序, 稱之爲客戶端
實際上web服務器不僅僅爲瀏覽器提供服務, 還可以爲手機app, 小程序, 小遊戲等常見互聯網應用提供服務, 而我們今天討論的是僅考慮爲瀏覽器提供服務的服務器
常見的web服務器有: nginx, apache, iis
開發階段服務器一般安裝在本地計算機, 通常也稱之爲本地服務器
vscode有一個live Server插件, 他其實就是一個輕量級的服務器
如何訪問服務器
服務器可能在本機, 也可能在遠程, 他一定運行在一臺計算機上, 要在茫茫互聯網中訪問到服務器程序, 就必須:
-
精確的定位到服務器所在的計算機
-
精確定位到計算機中的服務器程序
-
精確定位到程序中的某個功能
通常我們用url地址來準確的描述上面的三個條件
url(Uniform Resource Locator), 統一資源定位符, 是一個字符串, 他的格式如下
protocal://hostname:port/path?query#hash
-
protocal: 使用的協議, 選擇不同的協議會導致和服務器之間消息交互格式, 連接方式的不同, 大部分服務器都支持http和https兩種協議, 如果選擇了服務器不支持的協議則會導致訪問失敗
-
hostname: 主機名, 主機名可以是ip或者域名
- ip: 每臺計算機在網絡中的唯一編號, 127.0.0.1表示本機
- 域名: 網絡中容易記憶的唯一單詞, 通過DNS服務器可以將域名解析成ip, localhost會被解析成127.0.0.1
-
port: 端口, 0 - 65535之間的數字, 相當於服務器計算機上的房號, 使用不同的端口相當於敲不同的房門, 計算機上的程序可以監聽一個或者多個端口號, 如果訪問的端口號有程序被監聽, 則計算機會將到達的網絡訪問交給對應的程序來處理
- 端口號可以不寫有默認值, http默認爲80, https默認爲443
-
path: 一個普通的字符串, 該字符串會交給web服務器處理, 主要用於定位服務
- 如果path爲/, 則表示根路徑, 例如https://www.baidu.com/的path就是/
-
query: 一種特殊格式的字符串, 該字符串會交給web服務器處理, 主要用於向服務器的某個程序傳遞一些信息
-
hash: 一個普通的字符串, 在瀏覽器的地址欄中, 如果url其他位置的信息保持不變, 僅變動hash, 瀏覽器則不會重新訪問服務器因此通常用於不刷新頁面的跳轉
經過這些瞭解我們可以看出:
-
hostname是用來精準定位計算機的
-
port是用來精準定位服務器的
-
protocal是用於告訴服務器使用哪種協議進行傳輸數據
-
path是用於精準定位服務器上的服務的
-
query是在使用服務的時候傳遞的額外的信息, 具體看服務器要求
-
hash也是一些額外信息, 具體要不要也要看服務器要求
注意: url僅支持ASCII字符, 如果包含非ASCII字符, 會被現代瀏覽器自動進行編碼
例如: https://www.baidu.com/s?wd=王思聰
會被編碼爲: https://www.baidu.com/s?wd=%E7%8E%8B%E6%80%9D%E8%81%AA
同時url地址不能過長, 因爲很多瀏覽器對url地址長度是有限制的, Chrome對url的長度限制爲8182個ASSCI字符
http協議
我們可以通過url地址訪問服務器, 但是, 服務器和服務器之間的數據到底是怎麼交互的, 數據的格式是什麼, 這取決於用什麼協議
最常見的協議就是http協議
http協議將和服務器的一次交互看做是兩段簡單的過程組成: 請求request 和 響應response
-
請求: 客戶端通過url地址發送數據到服務器的過程
-
響應: 服務器收到請求之後反饋結果給客戶端的過程
當請求和響應都完成以後, 本次交互結束, 如果需要得到額外的服務器, 則需要重新發送請求
同時 http請求約定了請求的消息格式和響應的消息格式
請求消息格式
請求消息格式由兩部分組成, 請求頭request headers和請求體request body
請求頭
請求頭是一個多行文本的字符串
比如我們請求http://www.baidu.com/s?wd=html, 得到的請求頭如下:
GET /s?wd=html HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
...
我們可以看出, 該字符串由兩部分組成
-
請求行: 請求方法path協議
-
請求方法, 一個普通的字符串, 會被服務器讀取到, 常見的請求方法: GET, POST
-
path: 即url中的path + search + hash, 瀏覽器可能會用到path的信息
-
協議: 協議即版本號, 目前固定爲HTTP/1.1
-
-
鍵值對: 大量的屬性名和屬性值組合, 可以自定義
-
url: url地址中的hostname
-
User-Agent: 客戶端信息描述
我們看上方的Chrome的User-Agent在幹嘛, 他說自己又是Mozilla, 又是webkit又是Safari, 又是Chrome, 他在幹嘛, 他是不是在騙人, 他就是在騙人, 這個爲什麼要騙人還是跟瀏覽器大戰有關係
-
其他鍵值對
-
請求頭描述了請求的元數據信息, 這裏的元數據是指跟業務無關的額外信息(跟業務相關的數據如用戶賬號密碼稱之爲業務數據, 像告訴服務器自己是哪個瀏覽器 主機名是什麼這些都屬於元數據)
當我們在瀏覽器輸入一個url按下回車以後, 瀏覽器會自動構建一個請求, 方法爲GET 並向服務器發送請求
請求體
請求體是包含業務數據的字符串
理論上, 請求體可以是任意格式的字符串, 但習慣上, 服務器普遍能識別以下格式
-
application/x-www-form-unlencoded: 屬性名=屬性值&屬性名=屬性值
-
application/json: {“屬性名”: “屬性值”, “屬性名”: “屬性值”}
-
multipart/form-data: 使用某個隨機字符串作爲屬性之間的分隔符, 通常用於文件上傳
由於請求體樣式的多樣性, 服務器在分析請求體時可能無法知曉具體的格式, 從而不知道如何解析請求體, 因此瀏覽器通常要在請求頭中附帶一個屬性Content-Type來描述請求體使用的格式
例如:
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
Content-Type: multipart/form-data
GET和POST
雖然http協議沒有規定請求方式必須是什麼, 但隨意的請求方法服務器可能無法識別, 服務器一般都能識別GET和POST請求, 並作出如下的差異化處理
-
如果是GET請求, 不讀取請求體, 業務數據從path中的search或者hash中讀取
-
如果是POST請求, 讀取請求體, 業務數據從請求體中獲取, 關於請求體的格式, 不同的服務器, 或者同一個服務器不同的服務對於請求體格式要求都不相同
在地址欄輸入url是不可能產生POST請求的, 但是提交表單可以產生POST請求
<form action="請求的地址" method="POST"> <p> 賬號: <input type="text" name="loginId"> </p> <p>密碼: <input type="password" name="password"> </p> <p> <button type="submit">提交</button> </p> </form>
由於服務器對GET和POST請求處理上的差異, 導致了GET和POST請求上的差異
-
GET請求一般不設置請求體, POST有
-
GET請求的業務數據放在地址中安全性相較於POST較低, POST放在請求體中
-
GET請求傳遞的業務數據量是有限的, POST則不做限制(除非服務器做限制)
-
GET請求利於分享頁面結果, POST不行
-
在瀏覽器刷新或者回退頁面的時候, 會按照之前的請求方式重新請求數據, 如果是GET請求, 瀏覽器則會重新發送GET請求, 如果是POST請求 瀏覽器則會重新構建之前的消息體數據, 通常會彈出提示
響應消息格式
和請求類似, 響應消息也分響應頭(response headers)和響應體(response body)
響應頭
比如我們請求 https: //www.baidu.com/s?wd=html, 得到的響應頭可能如下
HTTP/1.1 200 OK
Bdpagetype: 3
Bdqid: 0xae29d62d0002d4fb
Cache-Control: private
Ckpacknum: 2
Ckrndstr: d0002d4fb
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Fri, 14 Feb 2020 01:22:58 GMT
Server: BWS/1.1
Set-Cookie: delPer=0; path=/; domain=.baidu.com
Set-Cookie: BD_CK_SAM=1;path=/
Set-Cookie: PSINO=3; domain=.baidu.com; path=/
Set-Cookie: BDSVRTM=18; path=/
Set-Cookie: H_PS_PSSID=30744_1468_21085; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1581643378042092493812549797325406655739
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
該字符串由兩部分組成
-
響應行: 協議 狀態碼 狀態碼文本
-
協議: 協議以及協議版本號, 目前固定爲HTTP/1.1
-
狀態碼和狀態碼文本: 一個數字和該數字對應的單詞, 來描述服務器的響應狀態, 瀏覽器會根據不同的狀態碼做不同的處理
(常用的狀態碼如下)- 200 OK: 一切正常
- 301 Move Permanently: 資源已經被永久重定向, 你的請求我已經收到了, 但是呢你要的東西已經不在這個地址了, 我已經永久的把它移動到了一個新的地址, 麻煩你請求新的地址, 地址我已經 放到請求頭的loacation中了
- 302 Found: 資源已經被臨時重定向, 你的請求我已經收到了, 但是呢你要的東西已經不在這個地址了, 我已經臨時的把它移動到了一個新的地址, 麻煩你請求新的地址, 地址我已經 放到請求頭的loacation中了(永久的話會直接覆蓋瀏覽器的歷史記錄, 臨時不會)
- 304 Not Modified: 文檔內容未修改, 你的請求我收到了, 但是你要的東西和之前沒有任何修改, 我就不給你結果了, 你自己就用以前的吧, 啥? 你沒有緩存以前的內容? 管我啥事?
- 400 Bad Request: 語義有誤, 當前請求無法被服務器理解 你給我發的是個啥啊? 聽都聽不懂
- 403 Forbidden: 服務器拒絕執行, 你的請求我已經收到了 但是我就是不給你東西
- 404 Not Found: 資源不存在, 你的請求我已經收到了, 但是我沒有你要的東西
- 500 Interval Server Error: 服務器內部錯誤, 你的請求我已經收到了, 但是這道題我不會, 解不出來 我先睡了
通常認爲, 0~399之間的狀態碼都是正常的, 其他都是不正常的
-
-
鍵值對: 大量的屬性名和屬性值的結合, 可以在服務器響應的時候自定義
-
Content-Type: 響應體中的數據格式, 常見格式如下
- text/plain: 普通的純文本, 瀏覽器通常會將響應體原封不動的展示在頁面上
- text/html: html文檔, 瀏覽器通常會將響應體作爲html頁面進行渲染
- text/javascript: js代碼, 瀏覽器通常會使用js引擎將他解析並且執行
- image/jpeg: 瀏覽器會將他視爲jpg圖片
- text/css: css代碼, 瀏覽器會將他視爲樣式
- attachment: 附件, 瀏覽器看到這個類型, 通常會直接觸發下載功能
- 其他MIME類型
面試題: 1. 如果訪問http://www.xxx.com/1.js 瀏覽器是否會按照js來執行 不會, 只看響應頭中的Content-Type 2. 如果服務器返回的響應體是js, 瀏覽器是否會按照js執行 不會, 還是隻看響應頭中的Content-Type
-
Server: web服務器類型
-
響應體
響應體沒什麼好說的, 就是響應消息的正文
在瀏覽器地址欄中輸入一個地址, 按下回車鍵以後都發生了什麼?
-
瀏覽器將url地址補充完整: 沒有書寫協議, 則補上協議
-
找到這個url域名的服務器ip, 瀏覽器會首先從緩存中獲取, 如果無則會去系統文件的hosts文件中尋找,如果無則會查詢DNS服務器
-
瀏覽器將對url地址進行url編碼: 如果url地址中出現了非ASCII碼字符, 則瀏覽器會對其進行編碼
-
瀏覽器會構造一個http請求, 將請求頭請求體和附帶的數據封裝在一個tcp包中, 該tcp包會依次經過傳輸層, 網絡層, 數據鏈路層, 物理層到達服務器
-
服務器接收到請求, 解析之後將一個html頁面代碼放進響應體中, 返回給瀏覽器
-
瀏覽器拿到服務器的響應以後, 丟棄當前頁面, 開始渲染新的html頁面, 根據頁面結構生成dom樹, 根據css生成css規則樹, 然後合併成渲染樹
-
瀏覽器在渲染頁面的過程中, 發現有其他的嵌入資源, 如圖片js等, 瀏覽器會使用不阻塞加載的方式重新向服務器發送請求獲取資源
-
當上述事件處理完畢以後觸發window.onload事件
Ajax
不僅僅是瀏覽器可以發出請求並且獲得響應, 任何具有網絡通信能力的程序均可以這樣做
過去, 在瀏覽器中, 只有瀏覽器本身有發送請求的能力, 直到ajax的出現
ajax是一種技術, 讓js語言在瀏覽器中獲得了新的api, 通過該api, js代碼擁有了和服務器通信的能力
同時,js代碼發出的網絡請求瀏覽器是不會刷新頁面的
我們來看一篇已經封裝好的ajax的方法, 根據代碼和註釋相信可以幫助你理解好Ajax是如何請求服務器資源的
// 筆者直接用es5書寫了, 避免有些朋友還未接觸到es6
function ajax(url, methods, data, callback, flag) {
var requestTypes = {
GET: function() {
var date = new Date().getTime; // 時間戳, 好讓服務器區分請求的不同
var paramsStr = '';
// 因爲get請求一般是不設置響應體的, 所以我們要將參數進行遍歷拼接進url
for(var prop in data) {
paramsStr += (prop + '=' + data[prop] + '&');
}
url = url + '?' + paramsStr + date;
xhr.open(methods, url, flag); // send代表配置請求, 這個flag一般代表是否異步, 默認肯定是異步
xhr.send(); // 代表構建請求發送至服務器
},
POST: function() {
// 如果是POST請求
xhr.open(method, url, flag);
xhr.send(data); // post請求的data是要進請求體的
}
}
// 創建發送請求的對象, 因爲兼容性問題IE獨有爲ActiveXObject來創建,所以我們做一下兼容性處理
var xhr = windows.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft XMLHttp');
// 設置請求的請求頭
xhr.setRequestHeader("Content-Type", "application/json");
var methods = methods.toUpperCase(); // 調用方法的人可能會使用小寫的get post 我們統一轉化爲大寫
// 當請求狀態發生改變的時候運行的函數, 這個方法最好寫在send之前, 因爲如果寫在send之後由於網速原因某些時候可能會監聽不到變化
xhr.onreadystatechange = function() {
// xhr.readyState: 一個數字, 用於判斷請求到了哪個階段
// 0: 剛剛創建好了這個對象, 但是還未配置請求(未調用open方法)
// 1: open方法已經被調用, 配置請求已經處理好
// 2: send方法已經被調用, 請求體構建好併發送到服務器
// 3: 正在接收服務器的響應消息體
// 4: 服務器響應的所有內容均已經接收完畢
// 根據這四個狀態我們可以想到的是我們要等到狀態爲4的時候纔可以取到完整的服務器響應數據
if(xhr.readyState === 4) {
// xhr.status 表示狀態碼和狀態文本
if(xhr.status === 200) {
// xhr.responseText: 獲取服務器響應的消息體
// xhr.getResponseHeader("Content-Type") 獲取響應消息頭中的Content-Type
// 這個時候我們直接將消息體傳進callback, 讓回調函數取做他該做的事
callback(xhr.responseText);
}
}
}
// 根據請求方法進行不同的操作
requestTypes[methods]();
}
註釋寫的相對來說比較詳細應該也可以幫助大家更好的理解ajax請求了