Go For Web:一篇文章帶你用 Go 搭建一個最簡單的 Web 服務、瞭解 Golang 執行 web 的原理

slowlydance2me發表於2023-04-14

前言:

本文作為解決如何透過 Golang 來編寫 Web 應用這個問題的前瞻,對 Golang 中的 Web 基礎部分進行一個簡單的介紹。目前 Go 擁有成熟的 Http 處理包,所以我們去編寫一個做任何事情的動態 Web 程式應該是很輕鬆的,接下來我們就去學習瞭解一些關於 Web 的相關基礎,瞭解一些概念,以及 Golang 是如何執行一個 Web 程式的。
文章預計分為四個部分逐步更新
2023-04-13 星期四 一更 全文共計約 3800 字 閱讀大約花費 5 分鐘
2023-04-14 星期五 二更 全文共計約 2000 字 閱讀大概花費 4 分鐘


文章目錄:

  1. Web 的工作方式
  2. 用 Go 搭建一個最簡單的 Web 服務
  3. 瞭解 Golang 執行 web 的原理
  4. Golang http 包詳解(原始碼剖析)
  5. 總結

正文:

用 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

程式碼結構像這樣 image

在瀏覽器位址列輸入:

http://localhost:9090

結果如下↓
image
加個字尾看看伺服器輸出了什麼↓
image
image

在編寫這個 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 服務的工作模式的流程如下圖
image

具體流程大概是這樣:

  1. 建立 Listen Socket,監聽指定埠等待客戶端請求傳送過來
  2. Listen Socket 接受從客戶端傳送來的請求,建立 Connect,得到 Client Socket,接下來透過 Client Socket 與客戶端通訊(中轉站)
  3. 處理客戶端的請求,首先從 Client Socket 讀取 HTTP 請求的協議頭,檢視是哪一種方法,如果是 POST 方法,還可能要讀取客戶端提交的資料,然後交給響應的 Handler 處理請求,handler 處理完之後準備好客戶端需要的資料,透過 Client Socket 寫給客戶端
    至此一次請求-響應過程結束

在整個過程中,我們需要了解下面三個問題:

  • 如何監聽埠?
  • 如何接受客戶端請求?
  • 如何分配 Handler 處理客戶端請求?

對 Go 而言,是透過叫 ListenAndServe 來處理這個事情的
底層細節是先初始化一個 server 物件,然後呼叫 net.Listen("tcp",addr),也就是底層使用了 TCP 協議搭建了一個服務,然後監聽我們設定的埠
原始碼如下:
image
image
image

上面的原始碼也實現了 接受客戶端請求 的功能,在上面程式碼執行了監控埠之後呼叫了 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 的資訊返回到客戶端。

流程如圖↓
image

總結

到這裡我們對 Go 的 Web 服務工作原理有了基本的瞭解,下一節我們會繼續去學習 Go 的 http 包,詳細解剖以下里面的內容,瞭解其中的流程

關於 Golang 基礎部分 以及 計算機網路部分讀者可以參閱我的往期 blog?
Goalng:基礎複習一遍過

漫談計算機網路:網路層 ------ 重點:IP協議與網際網路路由選擇協議

以上

看完記得留下一個?

相關文章