0%

[week 4] 網路基礎概論 - HTTP 協定、TCP/IP

本篇為 [NET101] 網路基礎概論(搭配 JS 實作練習) 這門課程的學習筆記。如有錯誤歡迎指正。

學習目標:

 知道網路背後大概的運作模式
 知道什麼是 Request 跟 Response
 知道什麼是 DNS 以及運作原理
 知道 HTTP 與 HTTPS 的差異
 知道 localhost 跟 127.0.0.1 是什麼
 知道 GET 與 POST 的差別
 知道常用的 HTTP Header
 知道什麼是 API
 會使用 node.js 寫出串接 API 的程式
 知道 HTTP method 有哪些
 知道基本的 HTTP statud code,像是 200、301、400、404、500

HTTP 是什麼?

全名是 HyperText Transfer Protocol,中文翻作「 超文本傳輸協定」。

HTTP 是一套網路傳輸協定,為全球資訊網的資料通訊的基礎。也就是說,網頁前端和後端在溝通時,就是透過 HTTP 協定進行。

為什麼我們需要 Protocol(協定)?

簡言之,協定就是一個「標準」。協定是為了讓彼此溝通而建立的規範,制定標準以統一格式,如此能夠進行規模化。

Client & Server

而透過協議溝通的兩端,通常可分為客戶端(Client)和伺服器端(Server)。

我們如何看到網頁的畫面?

網頁上的資訊,其實就是由許多 request 跟 response 構成。且兩者內容均分成 header 與 body,分別帶著不同資訊:

  • header:額外資訊
  • body:主要內容

我們可透過瀏覽器的開發者工具、或是 HTTP 抓包工具 Charles 來查看詳細資訊,過程大致如下:

  1. 瀏覽器(Client)產生 HTTP「request」傳給 Server
  2. Server 經過處理後,回傳一個「response」
  3. 再由瀏覽器進行解析(html、css、js 等),將程式碼渲染成我們所熟悉的網頁介面

瀏覽器

DNS 域名系統

全名是 Domain Name System,負責將域名轉換成 IP 位置。

  • 域名(Domain):即常用的網址。google.com 就是一個域名,類似景點名稱。
  • IP 位置:每個主機都有個 IP 位置,是網路溝通的地址。由四個數字組成,範圍是 0 ~ 255。

補充:localhost 跟 127.0.0.1 是什麼?

  • localhost:是一個域名,對應的的 IP 位址是 127.0.0.1
  • 127.0.0.1 是回送地址,指本地機,一般用來測試使用

以查詢地址為例

  1. 如果今天想要抵達 台北 101(域名)
  2. 查詢 Google 地址在哪(DNS)
  3. 回覆正確地址:台北市信義區信義路五段 7 號(IP)

Google 提供免費的 DNS 伺服器 8.8.8.8 和 8.8.4.4,如此可透過搜尋引擎來蒐集大數據。

以 GitHub 網站為例

就是透過 DNS 把 Request URL(github.com) 轉換成實際 IP 位置(13.250.177.223)。

DNS

nslookup 指令

在終端機輸入 nslookup '網址':可查詢 DNS 伺服器。有可能出現多個結果,代表對應到多個 server。

nslookup

瀏覽器只是一個程式

即使我們沒有瀏覽器,也能夠過其他方式發送 request 跟取得 response。

實做一個 Client 端發送 request

利用 Node.js 的一套 library:request - Simplified HTTP client,來模擬瀏覽器發送 request 的過程,步驟如下:

  1. 安裝套件:npm install request
  2. 使用方法:複製官網提供的範本,貼到 index.js 裡
const request = require('request');
request('http://www.google.com', function (error, response, body) {
  console.error('error:', error); // Print the error if one occurred
  console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
  console.log('body:', body); // Print the HTML for the Google homepage.
});
  1. 修改成要發送 request 的域名
const request = require('request');
request('https://github.com/Lidemy/mentor-program-4th-heidiliu2020', function (error, response, body) {
  console.error('error:', error);
  console.log('statusCode:', response && response.statusCode);
  console.log('body:', body);
});
  1. 執行 node index.js 可拿到 response

執行 request

  1. 或是利用 node index.js > github.html 導入程式碼,一樣能打開網頁

HTTP Request Method 請求方法

什麼是 HTTP Method?在 HTTP 1.1 協定中,定義了八種方法,來以不同方式操作指定的資源。詳細可參考 HTTP 請求方法- HTTP | MDN

  • GET:只應用於取得資料
  • HEAD:只獲取回應的 header,但沒有 body。較少使用,通常應用在只想知道 response 資訊時
  • POST:用於提交指定資源的實體。通常會改變伺服器的狀態
  • PUT:取代原本的整個 request。和 PATCH 類似
  • PATCH:修改部分 request
  • DELETE:刪除資源
  • OPTIONS:會回傳 server 支援哪些方法
  • TRACE
  • CONNECT

這是為了讓 Server 能夠清楚辨別 request 的目的。

CRUD 原則

常用的幾個動作分別為:GET / POST / PUT / DELETE,正好對應到資料庫基本操作 CRUD 增刪查改

  • Create:新增(POST)
  • Delete:刪除(DELETE)
  • Read:讀取(GET)
  • Update:更新(PUT)

RESTful

  • REST:全名是 Representational State Transfer,中文為「表現層狀態轉換」,是一種設計風格,以語意化且更為嚴謹的方式描述 API
  • RESTful:形容以此規範設計的 API

一般的 API 可能會長這樣:

新增使用者:POST + /new_user
刪除使用者:POST + /delete_user
查詢使用者:GET + /user_data/:id
更改使用者:POST + /update_user

若以 RESTful API 風格開發:

新增使用者:POST + /users
刪除使用者:DELETE + /users/:id
查詢使用者:GET + /users/:id
更改使用者:PATCH + /users/:id

由此可見 RESTful 的寫法較一致,且語意化更好理解。透過把「動作」藏在 HTTP method 裡面,而有唯一的 URL(/users)表示資源位置,藉此統一 API 接口。

如果用 HTML 來比喻,寫網頁時我們可以全部使用 div 標籤,但為了幫助理解,通常會使用 li、section、article 等具有語意的標籤,即可從結構看出內容的作用。

參考資料:API 是什麼? RESTful API 又是什麼? - Medium

GET & POST 請求的區別

當中又以 GETPOST 兩種,為最常使用的 HTTP 請求方法。兩者最直觀的區別是「資料傳遞方式」與「安全性」。

GET:向指定的資源要求資料,類似於查詢操作。

  • 資料傳遞方式:將引數由 URL 帶至 Server 端
  • 安全性:較 POST 不安全,因為傳遞的引數會在 URL 上顯示
  • 例如:讀取連結或圖片

GET

POST:將要處理的資料提交給指定的資源,類似於更新操作。

  • 資料傳遞方式:將引數放在 request body 中傳遞
  • 安全性:較 GET 安全,適合用於隱密性較高的資料
  • 例如:會員登入系統(如下圖所示)

POST

在最下方夾帶 request 資訊:

request body

HTTP Status Code 狀態碼

是 HTTP 用「3 位數字代碼」來表示回應狀態,通常以開頭的數字來進行區分。詳細可參考 HTTP 狀態碼- 維基百科

1xx:訊息(較少見)

2xx:Success 成功

  • 200 OK:代表成功
  • 204 No Content:伺服器成功處理了請求,沒有返回任何內容(例如發出 DELETE XXX 訊息,回傳 204 代表刪除成功)

3xx:Redirect 重新導向

  • 301 Moved Permanently:資源「永久」移到新位置
  • 302 Found:資源「暫時」移到其他位置

4xx:User error 客戶端錯誤

  • 400 Bad Request:請求語法錯誤、資源太大、請求訊息無效等
  • 404 Not Found:找不到資源

5xx:Server error 伺服器端錯誤

  • 500 Internal Server Error:伺服器出錯。例如搶票時伺服器當機
  • 503 Service Unavailable:由於臨時的伺服器維護或者過載,伺服器當前無法處理請求

參考資料:

  1. [第四週] 網路基礎 - HTTP、Request、Response - Yakim
  2. [Day 09] 表單中的 GET 與 POST
  3. GET 和 POST 請求的區別(面試和原理都得知道)
  4. http Post 和 Get 差異

實作一個簡易 HTTP Server 端

前面我們已經實作一個 Client 端,也就是利用 request 這個 library 來顯示出來。接下來我們要利用 Node.js 內建 library:http 來實作 Server 端:

  1. 建立一個檔案 server.js,並輸入下列程式碼:
let http = require("http"); // 引用 library: http

let server = http.createServer(handleRequest);
function handleRequest(req, res) {
  console.log(req.url);
  res.write("hello");
  res.end();
}

server.listen(5000); // 監聽 5000 這個 port
  1. 執行 node server.js:不會跑出任何東西。因為沒有 console.log 任何東西,所以是正常的。此時程式會一直執行到按 Crtl + C 才會退出,符合 server 端必須不斷運行來接收資訊。

  2. 在瀏覽器輸入網址 localhost:5000 會跑出下列畫面:

localhost:5000

  1. 若修改程式碼如下,即可根據「不同網址」給出「不同回應」:
let http = require("http"); // 引用 library: http

let server = http.createServer(handleRequest);
function handleRequest(req, res) {
  console.log(req.url);
  if (req.url === "/") {
    // 若網址為根目錄
    res.write("welcome!");
    res.end();
    return;
  }
  if (req.url === "/hello") {
    // 若網址為 /hello
    res.write("hello");
    res.end();
    return;
  }

  if (req.url === "redirect") {
  }
}

server.listen(5000); // 監聽 5000 這個 port
  • 輸入網址 localhost:5000:回應 welcome

welcome

  • 輸入網址 localhost:5000/hello:回應 hello

hello

  1. 再增加 redirect 部分和 res.writeHead(404),就完成一個較完整的 Server 端:
let http = require("http"); // 引用 library: http

let server = http.createServer(handleRequest);
function handleRequest(req, res) {
  console.log(req.url);
  if (req.url === "/") {
    res.write("welcome!");
    res.end();
    return;
  }
  if (req.url === "/hello") {
    res.write("hello");
    res.end();
    return;
  }

  if (req.url === "/redirect") {
    res.writeHead(200, {
      lidemy: "good",
    });
    res.end();
    return;
  }

  res.writeHead(404);
  res.end();
}

server.listen(5000); // 監聽 5000 這個 port
  • 若輸入不存在的網址,就會出現狀態 404 Not Found

404

  • 若輸入 localhost:5000/redirect,會在 Response Headers 會出現 'lidemy': 'good'

'lidemy': 'good'

  1. 在之前的狀態碼有提到,若要轉址必須改成 302 Found
if (req.url === "/redirect") {
  res.writeHead(302, {
    Location: "/hello", // 指定要轉到哪裡
  });
  res.end();
  return;
}
  1. 輸入網址 'localhost:5000/redirect,瀏覽器就會轉址到新位置:

302

  • 若將程式碼改成 'Location': 'https://google.com':輸入網址 localhost:5000/redirect 就會直接連到 google.com

淺談 TCP/IP

網路的層級

先前提到「從電腦發出 request 到 server」,在溝通訊息這段過程,其實需要經過非常多道手續。因此就有組織將網路標準化,將網路連接過程分成數個階層(layer),每個階層都有特別的獨立的功能。

分層的好處,是只要處理該層級的事情就好,因為每個階層的程式碼可以獨立撰寫,功能也不會相互干擾。

而著名的分層模型主要有兩種:

OSI 七層協定

由於協定非常嚴謹,較偏向理論。

OSI

TCP/IP 四層模型

TCP/IP 是由 OSI 七層協定簡化而來,為目前網路通訊的基礎架構。以下為兩者之間的比較圖與常見的通訊協定:

OSI TCP/IP

參考資料:2.4 TCP/IP 的傳輸層相關封包與資料 - 鳥哥的 Linux 私房菜


網路層 - IP 地址

IP 的全名是 Internet Protocol,中文是「網際網路協定」。

可分為 IPv4、IPv6 這兩種協議,兩者最主要差異在於「IP 地址的不同」:

  • IPv4:由四個數字組成,範圍是 0~255
  • IPv6:用來取代 IPv4,主要是為了解決 IP 地址不夠用的問題,可容納更多 IP 地址

各種 IP

我們常聽到的 IP 地址,代表在網路上的地址。在網際網路上,每台電腦之間,是透過 IP 位址來通訊。當中又分為下列幾種類型:

  • 固定 IP:不會改變的 IP

理想情況下,是每一台電腦都有一個 IP 位址。固定的 IP 位址,適合架設網站,也因為不會變動,確保使用者能夠連上伺服器。例如:伺服主機、網路設備多使用固定 IP。

  • 浮動 IP:每次連上網路時的 IP 位置都會不一樣

一般用戶大多是浮動 IP。浮動 IP 解決了「IP 不夠用」這個問題,因為每次連線的 IP 位址皆不同,也不會輕易被駭客攻擊。

  • 虛擬 IP:僅能使用於內部網路,外網連不上該位置

只有在內網才能互相連接,外網找不到該地址,所以內網 IP 位址是可以重複的。但對外網而言,一定會有一個「固定 or 浮動」的 IP 位址。例如:公司使用內網溝通,藉由鎖 IP 位址,可提高安全性。

通常以 192.16810.0 開頭的,都是虛擬 IP。

虛擬 IP

參考資料:浮動 IP 與 固定 IP 有何不同?? 各有何優缺點??


Port 連接埠(端口)

在應用層當中,每台電腦主機 IP 位置(localhost),可能對應到不同應用程式。例如:HTTP、FTP、信件收發等服務。

而 Port 扮演網路通訊的端點,用來區分不同功能。如此即可辨認出,該連線要對應到哪個應用程式。

  • 例如實作 server 端提過的 server.listen(5000):代表監聽 5000 這個 Port,輸入網址 localhost:5000 即可連到這個服務

如果沒有輸入 Port,會有一些預設或常用值:

  • HTTP 80:說明用於網頁瀏覽
  • HTTPS 443
  • FTP 21
  • 「測試」常用:3000、4000、8080、8000 等冷門 Port

參考資料:[網路管理]常用 port 說明


傳輸層 - TCP & UDP

傳輸層(Transport Layer)協定提供不同主機之間的資料傳輸及控制。根據不同需求,又分為「可靠的 TCP」和「速度較快的 UDP」兩種協定:

TCP

  • 全名:Transmission Control Protocol 傳輸控制協定
  • 是一種可靠的資料傳輸,因此大部分的網路協定都是建立在 TCP 上面
  • 透過「三次握手」確認建立一個連接:

若以「傳紙條概念」比喻三次握手:
小明:安安,在嗎?
小美:在阿,你好。
小明:收到,太好了!

UDP

  • 全名:User Datagram Protocol 用戶資料包協定
  • 流量是不受規範的,需要快速、重複傳送資料的情況會使用 UDP,相對而言也較不穩定
  • 例如:視訊功能

參考資料:

  1. TCP 協議 - Wikipedia
  2. 什麼是 OSI 的 7 層架構?和常聽到的 Layer 7 有關?

小結 - 從傳紙條概念看 TCP/IP

參考 Huli - 從傳紙條輕鬆學習基本網路概念,我們能夠透過傳紙條的例子,來試著理解網路通訊概念:

  • 應用層(HTTP / FTP):傳輸的資料內容
  • 傳輸層(TCP / UDP):傳輸方式
  • 網路層(IP):傳輸地址
  • 實體層(網路電纜):實體傳輸

傳紙條概念


淺談 API

可參考 Huli - 從拉麵店的販賣機理解什麼是 API,或是這部影片:什麼是 API?來瞭解。

全名是 Application Programming ==Interface==,中文是「應用程式==介面==」。簡言之,就是方便溝通、交換資料的管道。

API 是應用程式、裝置之間資料的交換,但不一定要透過網路才能有 API,例如:

  • 軟硬體廠商的 API:USB 與 電腦交換資料
  • 作業系統裡的 API:讀取、傳輸及寫入等等電腦上的操作

而對於網頁來說就是 Web API

  • Web API:是基於 http 協定下運作的 API,代表透過網路進行資料交換

以下為實際的運用例子:

  • 會員登入系統:社群連結註冊登入(例如:設置 FB 或 Google 登入按鈕)
  • 社群嵌入:分享、留言版、按讚按鈕、影音(例如:Facebook Graphic
  • 資料嵌入:Yahoo 氣象Google 地圖Tweet timelines
  • PokéAPI:拿到寶可夢的資訊
  • 假圖 API:隨機產生出圖片

透過 API 能夠達到省時、便利、營利等優點,因此我們也可以說,API 是一個「能讓生產者與消費者雙方溝通的介面」。

實際串接 API

串接的前置作業

  1. 準備 Client 端(發出端):使用 node.js library request 實作一個 Client 端
  2. 提供 API 的網站(接收端):Regres 是一個提供 API 測試的網站
  3. 能夠發出 request 的工具:終端機

實戰演練

Step1. 實作一個 Client 端

與先前「實做一個 Client 端發送 request」操作方式相同,是利用 library request 來模擬瀏覽器發送 request 的過程。

  • 新增 index.js 並貼上官網提供的範本
const request = require("request");
request("http://www.google.com", function (error, response, body) {
  console.error("error:", error);
  console.log("statusCode:", response && response.statusCode);
  console.log("head:", response.headers);
  console.log("body:", body);
});

Step2. 串接 Regres API

將範本中的 google 網址改成 https://reqres.in/api/users

Step3. 用 node.js 運行

在終端機輸入 node index.js,即可獲得 Regres API 所提供的資訊:

運行

這樣其實就完成了簡單的 API 串接!透過丟一個 request 到網站,我們能夠獲取想要的資訊。

學會查看官方文件

而透過 request 文件,我們能找到不同的 request.METHOD(),瞭解如何使用各種方法:

request.get(): Defaults to method: "GET".
request.post(): Defaults to method: "POST".
request.put(): Defaults to method: "PUT".
request.patch(): Defaults to method: "PATCH".
request.del() / request.delete(): Defaults to method: "DELETE".

情境一:查詢不同使用者資料

  • Method:GET(預設方法)
  • url :https://reqres.in/api/users/<userID>
  • 透過 Reqres 官網可知,更改 「Request 網址結尾」可獲得不同使用者資訊,以 /api/users/2 為例:

方法一:直接將網址改成 https://reqres.in/api/users/2

id 2

方法二:使用 node.js 內建 library process

這個方法是利用 process.argv 達成帶入參數,我們可以先用 console.log 來查看 process.argv 是什麼:

const process = require("process");
console.log(process.argv);

array

會發現 process.argv 其實是一個陣列,利用 process.argv[2] 就可以拿到我們需要的參數:

const request = require("request");
const process = require("process");

request(
  "https://reqres.in/api/users/" + process.argv[2],
  function (error, response, body) {
    console.log("body:", body);
  }
);

輸入 node index.js 2,就可獲得 id: 2 的使用者資料:

node index.js 2

情境二:新增使用者資料

  • method:POST
  • url :https://reqres.in/api/users/
  • 到 request library 的 Github 頁面,點選 forms,填入相關程式碼來新增資料至 reqres
const request = require("request");

request.post(
  {
    url: "https://reqres.in/api/users",
    form: {
      name: "Heidi",
      job: "web engineer",
    },
  },
  function (error, response, body) {
    const json = JSON.parse(body); // 轉成 JS 物件
    console.log(json);
  }
);

成功新增使用者資料:

註:這裡的操作只是測試用,並不會真的新增資料到網站。

新增使用者資料

情境三:刪除使用者資料

  • method:DELETE
  • url :https://reqres.in/api/users/<userID>
const request = require("request");
request.delete(
  {
    url: `https://reqres.in/api/users/2`, // 將使用者 id 帶入
  },
  function (error, response, body) {
    // console.log(JSON.parse(body));
    // 因為是刪除,所以不會回傳東西,因此轉印狀態碼
    console.log("status code:", response.statusCode);
  }
);

回傳 204,代表成功刪除使用者資料:

delete

情境四:修改使用者資料

  • method :PATCH
  • url :https://reqres.in/api/users/<userID>
const request = require("request");
request.patch(
  {
    url: `https://reqres.in/api/users/2`,
    form: {
      // 傳入要修改的資料
      name: "hello",
    },
  },
  function (error, response, body) {
    console.log(JSON.parse(body));
  }
);

成功修改使用者資料:

patch

綜合應用

Base URL: https://lidemy-book-store.herokuapp.com

說明 Method path 參數 範例
獲取所有書籍 GET /restaurants _limit:限制回傳資料數量 /restaurant?_limit=6
獲取單一書籍 GET /restaurants/:id /restaurant/12
刪除書籍 DELETE /restaurants/:id
新增書籍 POST /restaurants name: 書籍名稱
更改書籍 PATCH /restaurants/:id name: 書籍名稱
const request = require("request");

const API_ENDPOINT = "https://lidemy-book-store.herokuapp.com";

request(`${API_ENDPOINT}/books?_limit=10`, (error, response, body) => {
  // 判斷 status code 是否為 2 開頭:偵測回傳是否成功
  if (response.statusCode >= 200 && response.statusCode < 300) {
    let books = "";
    // try catch:偵測處理資料這個動作是否出現錯誤
    try {
      books = JSON.parse(body);
      // 列出前十筆資料
      for (let i = 0; i < books.length; i += 1) {
        console.log(`${books[i].id} ${books[i].name}`);
      }
    } catch (err) {
      // 若 response 不是一個合法的 JSON 字串,會回傳錯誤
      return console.log(err);
    }
  }
});

補充:如何在請求帶入資料

在發出 request 時,我們其實能透過兩種方式來帶入資料:

  1. Request header:把資料放入 header
    • 通常會放層級較高、具有機密性的資料,例如身分驗證
  2. Query-string parameter:把資訊放在網址列結尾
    • 由結尾的問號開始,是 KEY / Value 的組合

實際例子可參考Twitch API v5 文件,同樣支援這兩種方法:

  1. Request header (Client-ID: XXXXX)
  2. Query-string parameter
    (https://api.twitch.tv/kraken/users/44322889?client_id=XXXXX)

因此在串接之前,必須先確認該 API 支援哪種方式來發出請求。


資料格式 XML & JSON

在 API 實戰中的 response 資料其實就是「JSON 格式字串」。在談到如何整理資訊之前,先來介紹常用的資料格式:

XML

  • 全名為 Extensible Markup Language,中文為「可延伸標記式語言」
  • 和 HTML 非常類似,均屬於 Markup Language(標記語言),內容用前後標籤包起來。
    • 例如:<firstName>John</firstName>
  • 缺點是檔案較大、不易閱讀,因此現代開發較少使用

JSON

  • 全名為 JavaScript Object Notation,中文為「JavaScript 物件表示法」
  • 為現代最普遍、常用的資料格式
  • 格式容易理解,且相容性高,許多程式均支援讀取或修改
  • 資料格式看似 JavaScript 物件,但需注意以下幾點:
    1. 回傳值的型態是「字串」
    2. key 值要用雙引號 "key" 包起來
    3. 支援許多資料格式:[array]{object} 等;但 value 值 不能放 function
    4. 整個 JSON 格式字串不能使用註解
  • 相關函式
    1. JSON.prase(<JSON>):將 JSON 格式字串轉成物件
    2. JSON.stringify(<object>):將物件轉成 JSON 格式字串

參考資料:

  1. 你不可不知的 JSON 基本介紹
  2. JSON Editor online:可將得到的資料轉成 JSON 格式

實際來處理 JSON 格式字串

同樣以串接 Reqres API 為例。得到 response 就是「JSON 格式的字串」,但這種資料並不易閱讀,也無法直接使用。

因此使用內建函式 JSON.parse() 做處理,就會轉成「JS 物件」:

const request = require("request");

request(`https://reqres.in/api/users/2`, function (error, response, body) {
  console.log("原始格式,JSON 格式的字串----------");
  console.log(body);
  console.log("轉成 JS 物件--------------------");
  console.log(JSON.parse(body));
});

json

如此就可以「物件」方式來取出想要的資料:

const request = require("request");

request(`https://reqres.in/api/users/2`, function (error, response, body) {
  const json = JSON.parse(body); // JSON 物件
  console.log(json.data.first_name); // 印出 Janet
});

反之,若想把「JS 物件」轉成「JSON 格式字串」,可以用: JSON.stringify(<object>)


必學指令 curl

curl 官網下載,安裝方法可參考 Day14 - cURL 工具,即可在終端機使用。

curl 做法非常簡單,只要在終端機輸入指令就可支援發 HTTP request 、下載及上傳檔案的功能,基本格式為:

curl [options] [URL...]

預設是 GET 方法

以使用 curl 能夠發 request  到 google 首頁為例:

  • 輸入 crul 'http://www.google.com':即可下載該網頁程式碼
  • 或輸入 crul '網址' > google.html:可將回傳值導向其他檔案

參考資料:

  1. linux 指令 curl 指令详解
  2. Linux Curl Command 指令與基本操作入門教學