天天看點

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:

文章目錄

  • 一、概述
    • 1. Web工作方式:
    • 2. HTTP協定
    • 3. 位址(URL)
  • 二、HTTP封包解析:
    • 1. 請求封包格式:
    • 2.響應封包格式:
  • 三、Go語言HTTP程式設計:
    • 1. 簡單的服務端:
    • 2. 簡單的用戶端:

一、概述

1. Web工作方式:

對于普通的上網過程, 系統其實是這樣做的:

浏覽器本身是一個用戶端, 當輸入URL的時候, 首先浏覽器會去請求DNS伺服器, 通過DNS擷取相應的域名對應的IP, 然後通過IP位址找到IP對應的伺服器後, 要求建立TCP連接配接, 等浏覽器發送完

HTTP Request(請求)

包後, 伺服器接收到請求包之後才開始處理請求包, 伺服器調用自身服務, 傳回

HTTP Response(響應)

包; 用戶端收到來自伺服器的響應後開始渲染這個

Response

包裡的主體

(body)

, 等收到全部的内容随後斷開與該伺服器之間的TCP連接配接;

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:

DNS域名伺服器(Domain Name Server)是進行域名(domain name)和與之相對應的IP位址轉換的伺服器; DNS中儲存了一張域名解析表;解析消息的域名;

一個Web伺服器也被稱為HTTP伺服器, 它通過HTTP(HyperText Transfer Protocol 超文本傳輸協定)協定與用戶端通信; 這個用戶端通常指的是Web浏覽器(其實手機端用戶端内部也是浏覽器實作的);

Web伺服器的工作原理可以簡單地歸納為:

  • 客戶機通過

    TCP/IP

    協定建立到伺服器的TCP連接配接;
  • 用戶端向伺服器發送HTTP協定請求包, 請求伺服器裡的資源文檔;
  • 伺服器向客戶機發送HTTP協定應答包, 如果請求的資源包含有動态語言的内容, 那麼伺服器會調用動态語言的解釋引擎負責處理“動态内容”, 并将處理得到的資料傳回給用戶端;
  • 客戶機與伺服器斷開; 由用戶端解釋HTML文檔, 在用戶端螢幕上渲染圖形結果;

2. HTTP協定

超文本傳輸協定(HTTP, HyperText Transfer Protocol)是網際網路上應用最為廣泛的一種網絡協定, 它詳細規定了浏覽器和網際網路伺服器之間互相通信的規則, 通過網際網路傳送網際網路文檔的資料傳送協定;

HTTP協定通常承載于TCP協定之上, 有時也承載于TLS或SSL協定層之上, 這個時候, 就成了HTTPS; 如下圖所示:

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:

3. 位址(URL)

URL全稱為

Unique Resource Location

, 用來表示網絡資源, 可以了解為網絡檔案路徑;

基本URL的結構包含模式(協定)、伺服器名稱(IP位址)、路徑和檔案名; 常見的協定/模式如

http、https、ftp

等; 伺服器的名稱或IP位址後面有時還跟一個冒号和一個端口号; 再後面是到達這個檔案的路徑和檔案本身的名稱; 如:

http://localhost[":"port][abs_path]
http://192.168.31.1/html/index
https://pan.baidu.com/
           

URL的長度有限制, 不同的伺服器的限制值不太相同, 但是不能無限長;

二、HTTP封包解析:

1. 請求封包格式:

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:
golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:
  • 請求行:
    • 請求行由方法字段、URL 字段 和HTTP 協定版本字段 3個部分組成, 他們之間使用空格隔開;
  • 請求頭部
    • 請求頭部為請求封包添加了一些附加資訊, 由名/值對組成, 每行一對, 名和值之間使用冒号分隔; 請求頭部通知伺服器有關于用戶端請求的資訊, 典型的請求頭有:

      |請求頭 |含義|

      |-|-|

      |User-Agent |請求的浏覽器類型|

      |Accept| 用戶端可識别的響應内容類型清單, 星号“ * ”用于按範圍将類型分組, 用“ / ”訓示可接受全部類型, 用“ type/* ”訓示可接受

      type

      類型的所有子類型|

      |Accept-Language |用戶端可接受的自然語言|

      |Accept-Encoding| 用戶端可接受的編碼壓縮格式

      |Accept-Charset |可接受的應答的字元集|

      |Host |請求的主機名, 允許多個域名同處一個IP 位址, 即虛拟主機|

      |connection |連接配接方式(close或keepalive)|

      |Cookie| 存儲于用戶端擴充字段,向同一域名的服務端發送屬于該域的cookie|

  • 空行
    • 最後一個請求頭之後是一個空行, 發送回車符和換行符, 通知伺服器以下不再有請求頭;
  • 請求包體
    • 請求包體不在

      GET

      方法中使用, 而在

      POST

      方法中使用;

      POST

      方法适用于需要客戶填寫表單的場合了; 與請求包體相關的最常使用的是包體類型

      Content-Type

      和包體長度

      Content-Length

      ;

2.響應封包格式:

http的成功響應封包:

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:

http的失敗響應封包:

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:

HTTP 響應封包由

狀态行、響應頭部、空行、響應包體

4個部分組成, 如下圖所示:

golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:
  • 狀态行:
    • 狀态行由

      HTTP 協定版本字段、狀态碼和狀态碼的描述文本

      3個部分組成, 他們之間使用空格隔開;
    • 狀态碼: 狀态碼由三位數字組成, 第一位數字表示響應的類型, 常用的狀态碼有五大類如下所示:
狀态碼 含義
1xx 表示伺服器已接收了用戶端請求,用戶端可繼續發送請求
2xx 表示伺服器已成功接收到請求并進行處理
3xx 表示伺服器要求用戶端重定向
4xx 表示用戶端的請求有非法内容
5xx 表示伺服器未能正常處理用戶端的請求而出現意外錯誤

常見的狀态碼舉例:

狀态碼 含義
200 OK 用戶端請求成功
400 Bad Request 請求封包有文法錯誤
401 Unauthorized 未授權
403 Forbidden 伺服器拒絕服務
404 Not Found 請求的資源不存在
500 Internal Server Error 伺服器内部錯誤
503 Server Unavailable 伺服器臨時不能處理用戶端請求
  • 響應頭部
    • 響應頭可能包括:
響應頭 含義
Location Location響應報頭域用于重定向接受者到一個新的位置
Server Server 響應報頭域包含了伺服器用來處理請求的軟體資訊及其版本
Vary 訓示不可緩存的請求頭清單
Connection 連接配接方式
  • 空行:
    • 最後一個響應頭部之後是一個空行, 發送回車符和換行符, 通知伺服器以下不再有響應頭部;
  • 響應包體:
    • 伺服器傳回給用戶端的文本資訊;

三、Go語言HTTP程式設計:

Go語言标準庫内建提供了

net/http

包, 涵蓋了HTTP用戶端和服務端的具體實作; 使用

net/http

包, 可以很友善地編寫HTTP用戶端或服務端的程式:

1. 簡單的服務端:

package main

import (
    "fmt"
    "net/http"
)

//服務端編寫的業務邏輯處理程式 —— 回調函數
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("method = ", r.Method) 	//請求方法
    fmt.Println("URL = ", r.URL)		// 浏覽器發送請求檔案路徑
    fmt.Println("header = ", r.Header)		// 請求頭
    fmt.Println("body = ", r.Body)		// 請求包體
    fmt.Println(r.RemoteAddr, "連接配接成功")  	//用戶端網絡位址

    w.Write([]byte("hello http")) 	//給用戶端回複資料
}

func main() {
    http.HandleFunc("/hello", myHandler)	// 注冊處理函數

    //該方法用于在指定的 TCP 網絡位址 addr 進行監聽,然後調用服務端處理程式來處理傳入的連接配接請求。
    //該方法有兩個參數:第一個參數 addr 即監聽位址;第二個參數表示服務端處理程式,通常為nil
    //當參2為nil時,服務端調用 http.DefaultServeMux 進行處理
    http.ListenAndServe("127.0.0.1:8000", nil)
}
           

浏覽器輸入url位址:

127.0.0.1:8000/hello

回調函數

myHandler

的函數原型固定;

func myHandler(w http.ResponseWriter, r *http.Request)

有兩個參數:

  • w http.ResponseWriter

    r *http.Request

    ;
  • w用來“給用戶端回發資料”, 它是一個interface:
type ResponseWriter interface {
   Header() Header			
   Write([]byte) (int, error)	
   WriteHeader(int)			
}
           
  • r 用來“接收用戶端發送的資料”; 浏覽器發送給伺服器的http請求包的内容可以借助r來檢視, 它對應一個結構體:
type Request struct {
	Method string		// 浏覽器請求方法 GET、POST…
	URL *url.URL		// 浏覽器請求的通路路徑
	……
	Header Header		// 請求頭部
	Body io.ReadCloser	// 請求包體
	RemoteAddr string	// 浏覽器位址
	……
    	ctx context.Context
}
           

檢視一下結構體成員:

fmt.Println("Method = ", r.Method)
fmt.Println("URL = ", r.URL)
fmt.Println("Header = ", r.Header)
fmt.Println("Body = ", r.Body)
fmt.Println(r.RemoteAddr, "連接配接成功")
           
golang學習十一: 網絡程式設計之HTTP一、概述二、HTTP封包解析:三、Go語言HTTP程式設計:

2. 簡單的用戶端:

用戶端通路web伺服器資料, 主要使用

func Get(url string) (resp *Response, err error)

函數來完成; 讀到的響應封包資料被儲存在

Response

結構體中:

type Response struct {
   Status     string // e.g. "200 OK"
   StatusCode int    // e.g.  200
   Proto      string // e.g. "HTTP/1.0"
   ……
   Header Header
   Body io.ReadCloser
   ……
}
           

伺服器發送的響應包體被儲存在Body中; 可以使用它提供的Read方法來擷取資料内容; 儲存至切片緩沖區中, 拼接成一個完整的字元串來檢視;

結束的時候, 需要調用Body中的

Close()

方法關閉io;

package main

import (
   "net/http"
   "fmt"
)

func main()  {
   // 使用Get方法擷取伺服器響應包資料
   //resp, err := http.Get("http://www.baidu.com")
   resp, err := http.Get("http://127.0.0.1:8000/hello")
   if err != nil {
      fmt.Println("Get err:", err)
      return
   }
   defer resp.Body.Close()

   // 擷取伺服器端讀到的資料
   fmt.Println("Status = ", resp.Status)           // 狀态
   fmt.Println("StatusCode = ", resp.StatusCode)   // 狀态碼
   fmt.Println("Header = ", resp.Header)           // 響應頭部
   fmt.Println("Body = ", resp.Body)               // 響應包體

   buf := make([]byte, 4096)         // 定義切片緩沖區,存讀到的内容
   var result string
   // 擷取伺服器發送的資料包内容
   for {
      n, err := resp.Body.Read(buf)  // 讀body中的内容。
      if n == 0 {
         fmt.Println("Body.Read err:", err)
         break
      }
      result += string(buf[:n])     // 累加讀到的資料内容
   }
   // 列印從body中讀到的所有内容
   fmt.Println("result = ", result)
}
           

繼續閱讀