網絡相關知識(Http協議, 請求響應, 服務器知識和Ajax)

網絡

  • 什麼是服務器

  • 如何訪問服務器

  • http協議

    1. 請求消息格式

      • 請求頭
      • 請求體
      • GET和POST
    2. 響應消息格式

      • 響應頭
      • 響應體
  • 在瀏覽器中輸入一個頁面地址, 按下回車以後發生了什麼

  • ajax

什麼是服務器?

如果所有的程序都是單機的, 會導致什麼後果呢?

  1. 數據難以共享

  2. 受計算機配置的影響, 運算速度差異巨大

  3. 個人計算機的安全性堪憂, 可能受到惡意程序的影響

而有了服務器之後這些問題都可以得到解決

服務器在不同的語境中表達了不同的含義

  1. 一臺獨立的計算機
    比如我們說要去買一臺服務器 配置一臺服務器, 就是代表的是一臺獨立的計算器

  2. 一個應用程序

絕大部分開發者, 通常把服務器看做是一個應用程序

無論他是哪個概念, 他都具有以下的特點:

  1. 能夠通過網絡, 被其他應用程序所訪問

  2. 能夠提供一些服務

如果一個服務器(應用程序), 它僅僅爲一個瀏覽器提供訪問網站服務, 我們將它稱之爲web服務器

實際上目前的web服務器和遊戲服務器界限已經非常模糊, 可以認爲, 凡是在互聯網中提供服務的服務器都是web服務器

通常我們把訪問服務器的程序, 稱之爲客戶端

實際上web服務器不僅僅爲瀏覽器提供服務, 還可以爲手機app, 小程序, 小遊戲等常見互聯網應用提供服務, 而我們今天討論的是僅考慮爲瀏覽器提供服務的服務器

常見的web服務器有: nginx, apache, iis

開發階段服務器一般安裝在本地計算機, 通常也稱之爲本地服務器

vscode有一個live Server插件, 他其實就是一個輕量級的服務器

如何訪問服務器

服務器可能在本機, 也可能在遠程, 他一定運行在一臺計算機上, 要在茫茫互聯網中訪問到服務器程序, 就必須:

  1. 精確的定位到服務器所在的計算機

  2. 精確定位到計算機中的服務器程序

  3. 精確定位到程序中的某個功能

通常我們用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, 瀏覽器則不會重新訪問服務器因此通常用於不刷新頁面的跳轉

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-807e20Gi-1581654054428)('...')]

經過這些瞭解我們可以看出:

  • 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地址發送數據到服務器的過程

  • 響應: 服務器收到請求之後反饋結果給客戶端的過程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jWAhmo0t-1581654054429)('...')]

當請求和響應都完成以後, 本次交互結束, 如果需要得到額外的服務器, 則需要重新發送請求

同時 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
...

我們可以看出, 該字符串由兩部分組成

  1. 請求行: 請求方法path協議

    • 請求方法, 一個普通的字符串, 會被服務器讀取到, 常見的請求方法: GET, POST

    • path: 即url中的path + search + hash, 瀏覽器可能會用到path的信息

    • 協議: 協議即版本號, 目前固定爲HTTP/1.1

  2. 鍵值對: 大量的屬性名和屬性值組合, 可以自定義

    • 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請求上的差異

  1. GET請求一般不設置請求體, POST有

  2. GET請求的業務數據放在地址中安全性相較於POST較低, POST放在請求體中

  3. GET請求傳遞的業務數據量是有限的, POST則不做限制(除非服務器做限制)

  4. GET請求利於分享頁面結果, POST不行

  5. 在瀏覽器刷新或者回退頁面的時候, 會按照之前的請求方式重新請求數據, 如果是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

該字符串由兩部分組成

  • 響應行: 協議 狀態碼 狀態碼文本

    1. 協議: 協議以及協議版本號, 目前固定爲HTTP/1.1

    2. 狀態碼和狀態碼文本: 一個數字和該數字對應的單詞, 來描述服務器的響應狀態, 瀏覽器會根據不同的狀態碼做不同的處理
      (常用的狀態碼如下)

      • 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之間的狀態碼都是正常的, 其他都是不正常的

  • 鍵值對: 大量的屬性名和屬性值的結合, 可以在服務器響應的時候自定義

    1. 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
      
    2. Server: web服務器類型

響應體

響應體沒什麼好說的, 就是響應消息的正文

在瀏覽器地址欄中輸入一個地址, 按下回車鍵以後都發生了什麼?

  1. 瀏覽器將url地址補充完整: 沒有書寫協議, 則補上協議

  2. 找到這個url域名的服務器ip, 瀏覽器會首先從緩存中獲取, 如果無則會去系統文件的hosts文件中尋找,如果無則會查詢DNS服務器

  3. 瀏覽器將對url地址進行url編碼: 如果url地址中出現了非ASCII碼字符, 則瀏覽器會對其進行編碼

  4. 瀏覽器會構造一個http請求, 將請求頭請求體和附帶的數據封裝在一個tcp包中, 該tcp包會依次經過傳輸層, 網絡層, 數據鏈路層, 物理層到達服務器

  5. 服務器接收到請求, 解析之後將一個html頁面代碼放進響應體中, 返回給瀏覽器

  6. 瀏覽器拿到服務器的響應以後, 丟棄當前頁面, 開始渲染新的html頁面, 根據頁面結構生成dom樹, 根據css生成css規則樹, 然後合併成渲染樹

  7. 瀏覽器在渲染頁面的過程中, 發現有其他的嵌入資源, 如圖片js等, 瀏覽器會使用不阻塞加載的方式重新向服務器發送請求獲取資源

  8. 當上述事件處理完畢以後觸發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請求了

至此, 希望對大家可以有所幫助

發佈了28 篇原創文章 · 獲贊 11 · 訪問量 2042
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章