--- tags: backend description: 6/16後端會議 --- # 二、06/16 HTTP protocal(承儒) ## HTTP / 1.1 * HTTP (Hypertext Transfer Protocol) 超文本傳輸協定,主要是架構於 TCP/IP 之上的 應用層,為 無狀態 的 請求-回應 通訊協定。 * HTTP 由最初的 頁面、媒體傳輸,發展為今日 Web 應用的根基,許多 APP、嵌入裝置、軟體…,皆依此取得服務,因此,也成了大部分開發者的必備技能。 * 基本的網路傳輸資料模式,原本有上對下,傳過去由下到上解密 --- ## TCP/IP model ![](https://i.imgur.com/KXJwfaQ.jpg) --- Topic 1. Interface 2. URL,Resource 3. Message,payload 4. Request/Response 5. Example 6. Homework --- ## Interface ### Server vs Client * 客戶端 (Client),指用來發起連線,建構並發出 一或多個 請求 (request) 給伺服端,以傳達所需意圖者。 * 伺服端 (Server),是指監聽 (listen) 與接受 (accept)/拒絕 (reject) 連線,並送回 一或多個 回應 (response),以服務客戶端者。 * 兩者只是一個對應的角色 * Client、Server 僅指特定連線的相對角色,一個程式可能有時做為 Client,某些時候為 Server。 * 大部分應用程式,你玩的遊戲都有兩者的功能,會接收跟傳送訊息 * Http只是一個應用層,不具有傳輸之用 :::info * 通訊協定 * 只是一個介面,他的實作依賴client和server如何去定義訊息(自定義) * 如果網頁都用http的話,那網站傳輸資料也都要使用http的通訊協定 ::: ![](https://i.imgur.com/Ab6O0N2.png) ## URI,Resource,Representation :::info 「HTTP 透過 Client 發送 請求 (Request),Server 送出 回應 (Response) 以進行互動。」 ::: * HTTP通過Client發出請求 (Request),Server回傳Response,發出請求的目的就是去找Server的Resource,可能是一個檔案或是database,任何一個可被實體化出來的都可稱之為一個Resourse * ==HTTP是一個通訊協定,定義我們與Resource互動的介面== **那 請求 的 目標 (target) 與 目的 (purpose) 是?** ### Resource :::info 可能是一個檔案、資料庫的查詢結果、另一群資源的集合或任何能被命名的資訊,都是 — 資源(resource)。 ::: **HTTP 沒有限制資源的種類,只定義與資源交互的介面。** ### URI (統一資源識別符 ) ``` http(s) URI = "http(s):" "//" path [ "?" query ] [ "#" fragment ] Ex: https://echo.paw.cloud:443/hello/world?age=24&gender=female#Uehara ``` * 統一資源識別符 (Uniform Resource Identifier, URI) * 即「辨識 資源 的方式」 * 常見如同 10.33.41.126 就是一個內網網址,只有瀏覽器是沒用的,還需要透過 HTTP URI,來指定 目標資源。 * 按照 URI 標準,上面的例子實際上是一個URI,包含了以下三者 : * 介面 (http) * HOST (echo.paw.cloud) * 路徑 (/hello/world) * URL就是一種URI #### 路徑選填 1. ==":"port== * 連接埠 (port),若無特別指定,預設是 80 port, * 若是 https 則預設為 443 port * 欲指定其他埠號需須根據 Server 之個別規範。 2. ==“?” query== * 查詢 (query), 以「?」開始 * 用 key=value 的鍵值對接著 * (中間別忘了加 =) * 若還有其他 query ,則通常以「&」做為區隔 * 譬如: ?age=24&gender=female * 代表的可能是,我想在這個網站 (如果有支援)尋找: * 『年紀 24 且 性別是女性 』。 * 開發時,記得處理 query 字串 (尤其是 客戶端應用) * 以免送出保留字元或錯誤的編碼。 * 沒處理的話容易受到SQL injection,這也是最常見的攻擊方式 * 安全性問題在於query的設定不完整 * 例如:敏感字元沒有篩選,就能直接利用敏感字元攻擊 3. ==“#” fragment== * 片段 (fragment),是輔助資源,通常為主要資源的一部份或子集 * fragment,是由 Client 端 (通常為瀏覽器) 處理的部分,並 不會傳遞給 Server ! * 因此,HTTP 規範所說的 — 目標 URI (target URI),不包含 片段 (fragment) 部分。 #### HOST(主機) * 包含 網域名稱 與 IP 位址 (e.g., google.com、172.217.27.142) * 若使用的是經註冊的網域名稱 (Domain Name) * 例: 「example.org」、「NotFalse.net」… * 則會先透過 名稱解析服務 (ex: DNS),找到 Server 的 IP 位址,並藉此訪問: * google.com等等網址都是透過DNS轉成一個domain * 我們要訪問google就會先通過DNS轉譯,得到一個IP ![](https://i.imgur.com/eCabQMF.png =400x150) :::success 決定了 請求目標 後,HTTP 透過可靠的 (reliable) 傳輸/會話層 (e.g., TCP), 進行連線 (connection) 以 交換訊息 (Exchange Message)。 ==那現在就要來看看怎麼處理Message的部分囉 !== ::: ### Message,Payload * 請求範例 ``` GET /31/fetch-api HTTP/1.1 Host: notfalse.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-encoding: gzip, deflate, br accept-language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4 ////body//// ``` 所有訊息格式都是: ![](https://i.imgur.com/lS4wq7e.png) * CRLF (carriage return followed by line feed),是網際網路 (Internet) 嚴謹的 換行 (newline) 標準 ,也就是鼎鼎大名的: * CR = 回車 (Carriage Return),又稱 歸位 => \r * LF = 換行 (Line Feed) => \n ### Request * GET or POST 方法 (method)、空白 (space, SP)、請求目標 (request-target)、再一個 空白 (space, SP)、HTTP 版本 (HTTP-version) * GET / HTTP/1.1 * 方法GET是區分大小寫的 * HTTP/1.1 -> HTTP版本 對應上面的顏色範例: ![](https://i.imgur.com/cz7T7Mw.png) ### Response * HTTP 版本 (HTTP-version)、空白 (space, SP)、狀態碼 (status-code)、再一個 空白 (space, SP)、原因短語 (reason-phrase)、以及 CRLF (carriage return followed by line feed)。 * 請求/回應 訊息,語法上格式相同, * 但 內容 與 訊息主體 (message body) 的規則不同。 * 正確或錯誤訊息都統一顯示在status code * 狀態碼其實是後端自己定義的 ![](https://i.imgur.com/e7RAD5j.png) #### header-field 表頭 (header),又譯為 首部、檔頭、標頭, 是由 0 或多個 表頭欄位 (header-field) + CRLF 組合而成。 類似程式語言 函式/方法的 參數 (parameter)。 例如,你對一服務生指定 目標資源 — — 牛排, 而 要幾分熟、幾份餐具、點心什麼時候上…, 這種 附加的重要資訊,即為 表頭 (header)。 :::info 資訊的附加重要資訊 之後的訊息如果是用JSON,就是在這邊定義 寫出來要空兩行,以顯示與下方的body的區隔 ::: #### message-body訊息主體 訊息主體 (亦稱Body),就像包裹的箱子,是 訊息 (message) 中 乘載 資料的地方。 :::info 其實資料格式都可以隨便亂傳,只是server會不會回應你而已 這邊的資料大小都是用位元組去運算 ::: ==只是這樣子怎麼知道要傳什麼格式呢?== ### Representation 資料表示 :::info 表示 (representation) (確切來說為 資源 的 表示),旨在透過協議容易傳達的格式 (位元組 bytes),並伴隨 元資料 (metadata),以反映出 資源「現在」「過去」或「預期」的 『狀態 (state)』。 ::: * 元資料->你傳送時要傳給server看,看看content type有沒有符合 * 表示 (representation) 的 元資料 (metadata),通常包含了: * 內容類型 (Content-Type) * 內容編碼 (Content-Encoding) * 內容語言 (Content-Language) * 內容位置 (Content-Location)... #### content type 內容類型 Content-Type 表頭欄位,是指相關 表示 的 媒體類型 (Media Type),用於定義 資料格式 (data format),以供接收者以相應的方式處理。 * 例如,下面的是用JSON和utf-8,之後的就不能使用 plain-text * (plain-text就是一般的字,連括弧都沒有) * (JSON是用字串傳,其實都是用字串傳,只是它有自己個格式) ![](https://i.imgur.com/aIbaTge.png) --- ![](https://i.imgur.com/Anb5v8B.png =500x500) 存取某個API時,非常重要 * 其中「application/x-www-form-urlencoded」or「application/x-www-form-urlencoded; charset=utf-8」,普遍用於 HTML 中的 POST 表單 (e.g., 提交帳號密碼),是 類似百分比編碼 的==鍵值對==形式。 * 例如:「name=%E5%8B%9D&password=9487」 * 就像encode,"%E5%8B%9D"就是你打的帳號,編碼之後再遞交 * Google在搜尋時,就可用非英文搜尋,只是他會再幫你encode。 :::success 一個 資源,可能具有不同的 表示 (representation) 方式 (不同的類型、編碼 或 語言…),如「使用者資料 」概念,我們可以用 JSON 表示,也可以表示為XML。 ::: ```javascript= { "id": 9487, "name": "Jason", "speciality": "sleep", "gender": "male", "blog": "NotFalse 技術客", "blog_url": "https://NotFalse.net" } ``` --- 也可以表示為XML ```xml= <id>9487</id> <name>Jason</name> <speciality>sleep</speciality> <gender>male</gender> <blog>NotFalse 技術客</blog> <blog_url>https://NotFalse.net</blog_url> ``` #### 內容協商機制 :::info 不同的 使用者 或 使用者代理 對於 表示,可能具有不同的功能,特徵或偏好。 而 Client 與 Server 如何協調,以傳輸最理想的 表示 的機制, 則稱為 — — **內容協商 (Content Negotiation)。** ::: * 缺點:霸道,我們只要這幾個,但如果server不提供,他就會不提供或是回傳錯誤的資源。 * 使用者今天要Gzip就回傳Gzip,要html就回傳html --- 內容協商 (Content Negotiation) 主要有三種模式: * 伺服端驅動 (server-driven) * 使用者代理驅動 (agent-driven) * 代理驅動 (proxy-driven) --- #### 伺服端驅動 (server-driven) * 內容協商 使用最頻繁的模式,又稱 主動協商 (Proactive Negotiation) 或 搶先式協商 (preemptive negotiation)。 * 是指 使用者代理 (User Agent) 在請求中發送 期望的 偏好表示資訊,使 Server 根據該偏好資訊,使用適當的演算法,以 選擇 (select) 優選的表示。 * 結果可能事與願違 * 輕則回應非預期的表示 * 重則回應 客戶端錯誤 (e.g., 415 Unsupported Media Type, 406 Not Acceptable)。 ![](https://i.imgur.com/9wfozsx.png =500x400) --- #### 使用者代理 (User Agent) * 代送請求或幫你處理的agent,上面其實是表明自己的身份,看server那邊有沒有bang掉。 * 這算是必要,你普通查詢的時候不會發現,但可能你寫爬蟲時,他們就會很嚴格的要求user-agent * 藉由顯示的 協商欄位,指明對表示的 期望選項: * Accept * 相對於剛剛的content type是傳送,accept就是接受 * Accept-Charset * 對映於 Content-Type 的 charset 參數,隨著對 UTF-8 良好的支持,大部分通用的使用者代理,並 不會 發送 Accept-Charset 欄位。 * Accept-Encoding * 對映於 Content-Encoding 欄位,也可以使用 空值 指明不進行編碼 (no encoding)。 * Accept-Language * 對應於 Content-Language 欄位 * 可以設定 想要資料的優先順序,0.8>0.3,先取0.3的 * 傳送的重點是Accept 跟 Encoding ,中間預設都是utf-8 ### Requeset / Response ![](https://i.imgur.com/yvCbdnM.png =500x400) * 那麼,除了在 網址列 中輸入 URI,還有什麼方式送出 HTTP 請求呢? * 可以用 JavaScript 的 XHR、jQuery、Fetch API(幫我們實作http的工具) * 或者 GET vs. POST 介紹的 HTML 表單。 * 還有 發送請求 的 Postman、Insomnia 等 GUI 工具, * 甚至 以 Socket 實作 HTTP Client! ## Example ``` API 網址: https://www.thef2e.com/api/{router功能路由} ``` --- ``` [API]: /isSignUp [方法]: post [參數]: { "email": "hexscholl@test.com" } [成功回應] { "success": true, "message": "報名成功!", "nickName": "gonsakon", "timeStamp": 1527035405000, "Certificate": "獎狀網址" } [失敗回應]: 未報名成功 { "success": false, "message": "此 Mail 尚未報名" } [失敗回應]: 參數錯誤 { "success": false, "message": "email 參數未提供" } ``` --- ```javascript= POST /api/isSignUp HTTP/1.1 \r\n Host: www.thef2e.com \r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 \r\n Content-Type: Application/json \r\n \r\n { "email": "hexscholl@test.com" } ``` --- ``` [API]: /signUpTotal [方法]: get [成功回應] { "success": true, "total": 899 } ``` --- ```javascript= GET /api/signUpTotal HTTP/1.1 \r\n Host: www.thef2e.com \r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 \r\n \r\n //////空der////// ``` --- ### 重申: * http沒有傳輸功能 * 不是 傳輸資訊 用的 protocal * 只是在傳輸層 TCP/IP 協定上的應用層協定 * ==所以,並不需要HTTP也能傳送資料,只是傳送的資料可否被 **識別**== * 傳輸實際上是給TCP和IP兩層還有你的實體網路做的,http比較像是要怎麼去標記我們的訊息,讓我們的資料能被識別。 * TCP IP 實體網路都解決後,才會到http的應用。 * 但,現在都有工具讓你不用硬幹http --- 以上就是簡單的小示範,HOST在真實的環境中是不用填上的,會自己找然後帶入,也就是無法在 request 中自行修改,現在教另外一個工具叫做 POSTMAN ,可以測試 API 的小工具。 ### POSTMAN DEMO * 如果是get,直接貼url,就會回傳你JSON * form-data 也是 raw ,只是 encode 的方式不同 * form-data / raw / JSON * server那邊要定義你要接的資料格式,前端也要傳這樣的格式下去給server處理,但你也可以在content-type那邊寫JSON但傳一堆plain-Text,然後server也可以寫好JSON,但不處理JSON,回傳你一堆plain-Text。 ==主要是前後端溝通要溝通好== * Endpoint * 網路傳輸的中點,只要可以被傳輸,主要傳輸資料的就叫endpoint ==其實網站本身也是API,只是回傳的是資料還是網站== * GET & POST * POST是你要先傳一些資料過去,通常用於表單,如帳密 --- ## 了解了這些還有呢 ? Header 的更多注意事項(order/duplicate),Message Routing(Proxy/Tunnel/Gateway),Cache ...等等都是非常重要的技能。 更多資訊/參考資訊 : https://notfalse.net/33/http1_1 --- ## Homework 使用 POSTMAN 來測試 幾個 API 是否可用。 --- 題目一 : HTTP GET URL : https://120.126.136.19:7089/get ,需要附加標準HTTP protocal 內容,不須攜帶任何Body。 應該接收到的回應為 { "message" : "Hello World!"} --- 題目二 : HTTP POST URL : https://120.126.136.19:7089/post ,除了需要附加標準HTTP protocal 內容,並額外包含以下的 Header => Content-Type : application/json ,並且 Body 需要有 { "StuId" : "你的學號 英文名字"} 應該接收到的回應為 { "message" : "Hello! 學號 名字"} --- # 同場加映: Express 周杰 ## 什麼是 Express? 為什麼要用 Express? :::info Express 是目前最穩定、使用最廣泛開發框架,並且是==Node.js 官方唯一推薦的 Web 開發框架。== Express 除了為 HTTP 模組提供了更高階的接口外,還實現了許多功能,其中包含:路由控制、模板解析支持、動態視圖、用戶會話、CSRF 保護、靜態文件服務、錯誤控制器、訪問日誌、緩存、插件支持。 ==特別在此註明,Express 不是一個無所不包的全能框架==,像 Rails 或 Django 的那樣實現了模板引擎甚至 ORM(Object Relation Model,對象關係模型),它只是一個輕量級的框架,多數功能只是對 HTTP 協議中常用操作的封裝,更多的功能需要插件或整合其他模組來完成。 ::: ### 以周杰的router為例 流程: * 使用Express首先要創建一個實體,設一個變數 * app.use->所有東西在使用的時候都會經過這一道手續 * 意思是寫的任何的網址api都會經過這道手續,後續才會接 * 接著會有階層的關係,你可以先寫一個根目錄去引用你想要的目錄子集 ```javascript= //server.js const express = require('express'); const router = require('./routes/index'); //去看index裡面有什麼 ... const app = express(); ... app.use('/api',router); //添加router中間件 //先寫一個api根目錄 //router接後續想要的子集 --- //index.js const express = require('express'); const articles = require('./articles'); const videos = require('./videos'); const router = express.Router(); //路由中間件 router((req, res, next) => { console.log('this is a api request!'); //任何路由訊息都會執行這句,讓我們知道這件事有成功 next();//把他交給下個middleware //middleware的註冊順序是按序執行 }); //再分一個子集下去,目錄再目錄的概念 router.use('/articles', articles); router.use('/videos', videos); --- //articles.js const express = require('express'); const Articles = require('../db/models/articles');//引入模組 const router = express.Router(); //這邊取用不是用/ //而是用 /api/articles //因為子層有定義相對的根目錄 router.get('/', (req, res) => { res.json([{ id: 0, title: '前端好難', content: '嗚嗚嗚真的好難TAT', }, { id: 1, title: '後端好難', content: '嗚嗚嗚真的好難TAT', }, { id: 2, title: '學完 redux後發現還有 graphql', content: 'asd', }]) }); //router使用get寫在這根目錄,就是要回傳這些東西 modules.exports = router ``` ```javascript= //前端 ... fetch("http:/localhost:3001/api/articles") .then(res => res.json()) .then(data => { console.log("data: ", data) this.setState({ articles: data, }) }) ... ``` :::success 而前端在fetch的時候,也就寫同樣的路徑,也就是後端寫好這個路徑後,前端就可以接。 ::: 後續就是再加有的沒的套件,像是JWT等等。 更詳細的參考資料: [使用 Node.js + Express 建構一個簡單的微博網站](https://cythilya.github.io/2014/11/23/nodejs-express-microblog/)