前言:
本文作為解決如何透過 Golang 來編寫 Web 應用這個問題的前瞻,對 Golang 中的 Web 基礎部分進行一個簡單的介紹。目前 Go 擁有成熟的 Http 處理包,所以我們去編寫一個做任何事情的動態 Web 程式應該是很輕鬆的,接下來我們就去學習瞭解一些關於 Web 的相關基礎,瞭解一些概念,以及 Golang 是如何執行一個 Web 程式的。
文章預計分為四個部分逐步更新
2023-04-13 星期四 一更 全文共計約 3800 字 閱讀大約花費 5 分鐘
2023-04-14 星期五 二更 全文共計約 2000 字 閱讀大概花費 4 分鐘
文章目錄:
- Web 的工作方式
- 用 Go 搭建一個最簡單的 Web 服務
- 瞭解 Golang 執行 web 的原理
- Golang http 包詳解(原始碼剖析)
- 總結
正文:
用 Go 搭建一個最簡單的 Web 服務
在前面一節我們介紹了 Web 的工作方式,知道了 Web 是基於 HTTP 協議的一個服務, Go 語言裡面提供了一個完善的 net/http 包,透過 http 包可以很方便的就搭建起來一個可以執行的 Web 服務。使用這個包也能很簡單地對 Web 的路由、靜態檔案、模板、Cookie 等資料進行設定和操作。
-
http 包建立 Web 伺服器
先貼個go程式碼?
點選檢視程式碼
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
//處理函式
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // 解析引數,預設不會解析
fmt.Println(r.Form)// 以下這些資訊是輸出到服務端的列印資訊:請求表單form、路徑path、格式scheme
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintln(w, "Hello astaxie!") // 輸出到客戶端
}
func main() {
http.HandleFunc("/", sayhelloName) // 設定訪問的路由
err := http.ListenAndServe(":9090", nil) // 設定監聽的埠
if err != nil {
log.Fatal("ListenAndServe:", err)
}
}
go build + go run
程式碼結構像這樣
在瀏覽器位址列輸入:
結果如下↓
加個字尾看看伺服器輸出了什麼↓
在編寫這個 Web 伺服器的過程中,我們只呼叫了 http 包中的兩個函式——ListenAndServe、HandleFunc
我們編寫的 sayhelloName 其實是我們寫的一個邏輯(處理)函式 (Handler),類似於 php 裡的控制層(controller)函式
同時,golang還擁有 類似 python 這樣的動態語言的特性,所以寫 Web 應用很方便
而且這個 Web 服務內部有支援高併發的特性,之後我們會仔細講解。
下面讓我們看看 Go 的 Web 服務的工作方式
看看Go 如何使得 Web 工作
瞭解 Golang 執行 web 的原理
首先我們先了解一下 Web 工作方式的幾個概念
以下是幾個伺服器端的概念
- Request:使用者請求的資訊,用啦解析使用者的請求資訊,包括 post、get、cookie、url 等資訊
- Response:伺服器需要反饋給客戶端的響應資訊
- Conn:使用者的每次請求連線(connect)
- Handler:處理請求和生成返回資訊的處理邏輯
分析 http 包執行機制
Go 實現 Web 服務的工作模式的流程如下圖
具體流程大概是這樣:
- 建立 Listen Socket,監聽指定埠等待客戶端請求傳送過來
- Listen Socket 接受從客戶端傳送來的請求,建立 Connect,得到 Client Socket,接下來透過 Client Socket 與客戶端通訊(中轉站)
- 處理客戶端的請求,首先從 Client Socket 讀取 HTTP 請求的協議頭,檢視是哪一種方法,如果是 POST 方法,還可能要讀取客戶端提交的資料,然後交給響應的 Handler 處理請求,handler 處理完之後準備好客戶端需要的資料,透過 Client Socket 寫給客戶端
至此一次請求-響應過程結束
在整個過程中,我們需要了解下面三個問題:
- 如何監聽埠?
- 如何接受客戶端請求?
- 如何分配 Handler 處理客戶端請求?
對 Go 而言,是透過叫 ListenAndServe 來處理這個事情的
底層細節是先初始化一個 server 物件,然後呼叫 net.Listen("tcp",addr)
,也就是底層使用了 TCP 協議搭建了一個服務,然後監聽我們設定的埠
原始碼如下:
上面的原始碼也實現了 接受客戶端請求 的功能,在上面程式碼執行了監控埠之後呼叫了 srv.Serve(net.Listener),這個函式就是處理接受客戶端的請求資訊。在這個函式中 有一個 for {},裡面首先透過 Listener 接受請求I.Accept()
,其次建立了一個 Conn c,err := srv.newConn(rw)
最後單獨開了一個 goroutine go c.server()
把這個請求的資料當作引數扔給 conn 去服務。這裡就體現了高併發,使用者的每一次請求都是在一個新的 goroutine 去服務,互相不影響。
那麼最後一個問題,如何分配 handler 去處理請求的呢?
conn 首先會解析 request :c.readRequest()
,然後獲取相應的 handler :handler := c.server.Handler
也就是我們呼叫 ListenAndServe 時候的第二個引數err := http.ListenAndServe(":9090", nil)
這裡是 nil 也就是 空,預設會獲取 handler = DefaultServeMux
那麼這個變數用來做什麼呢?其實他就是一個路由器,用它來匹配 url 跳轉到其相應的 handle 函式,我們之前呼叫程式碼的第一句就呼叫了 http.HandleFunc("/", sayhelloName)
,這個就是註冊了請求 / 的路由規則,當我們 請求的 url 為 “/” 時,路由就會轉到 sayhelloName 這個處理函式,DefaultServeMux 會呼叫 ServeHttp 方法,而這個方法內部其實就是呼叫 sayhelloName 本身,最後透過 response 的資訊返回到客戶端。
流程如圖↓
總結
到這裡我們對 Go 的 Web 服務工作原理有了基本的瞭解,下一節我們會繼續去學習 Go 的 http 包,詳細解剖以下里面的內容,瞭解其中的流程
關於 Golang 基礎部分 以及 計算機網路部分讀者可以參閱我的往期 blog?
Goalng:基礎複習一遍過
漫談計算機網路:網路層 ------ 重點:IP協議與網際網路路由選擇協議
以上
看完記得留下一個?