文章目錄
Web開發基礎
所謂Web開發,也就是我們通常說的網站後端開發。與其他語言相比,Go的Web開發具有簡單易學,併發效率高,原生標準庫支持等特點。即使是Python Web開發,也沒有Go的簡單。
學習Go的Web,是可以不需要安裝任何第三方庫的,標準庫即支持,且底層已經使用Go協程封裝了併發請求,因此Go不需要任何所謂的服務器容器的軟件,例如Java開發需要Tomcat服務器,Python需要Gunicorn,uWSGI之類的服務器,而Go語言,直接上手擼API即可,可以說Go語言是爲Web而生的,最適合後端開發。
學習Web開發,應當具備HTTP協議的基礎,請先閱讀我的另一篇文章 Web基礎(一) HTTP詳解
最簡示例
- 運行以下代碼
package main
import (
"fmt"
"net/http"
)
//定義一個名爲 handler 的處理器函數
func handler(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintf(writer, "Hello, %s!", request.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
- 在瀏覽器輸入
http://localhost:8080/Mary
這時瀏覽器顯示Hello, Mary!
handler
函數:事件被觸發之後,負責對事件進行處理的回調函數。該處理器函數接受兩個參數,第一個參數類型爲ResponseWriter
接口 , 第二個參數爲指向Request
結構的指針。
handler
函數會從 Request 結構中提取相關的信息,然後創建一個HTTP響應, 最後再通過ResponseWriter
接口將響應返回給客戶端。
handler
函數中的Fprintf
函數在被調用時,需要傳入一個ResponseWriter
接口實例 ,第二個參數是帶有格式化佔位符%s
的字符串,第三參數就是佔位符需要替換的內容,這裏則是將Request
結構裏帶的URL路徑截取後作爲參數
Go Web工作流程
示例代碼
package main
import (
"net/http"
"time"
"fmt"
)
func main() {
fmt.Println("*** 服務器啓動,監聽端口:8080 ***")
mux := http.NewServeMux()
// 處理靜態資源文件
files := http.FileServer(http.Dir("./public"))
mux.Handle("/static/", http.StripPrefix("/static/", files))
mux.HandleFunc("/", index)
// 配置服務器
server := &http.Server{
Addr: "0.0.0.0:8080",
Handler: mux, // 設置多路複用器
ReadTimeout: time.Duration(10 * int64(time.Second)),
WriteTimeout: time.Duration(200 * int64(time.Second)),
MaxHeaderBytes: 1 << 20, // 左移運算,等同:1*2^20,高性能乘法運算
}
server.ListenAndServe()
}
func index(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "Hello, world!")
}
- 創建以上代碼
- 在代碼所在目錄創建
public
文件夾 - 在
public
中分別創建home.html
、note.txt
文件 - 使用編輯器打開創建的兩個文件,分別將以下內容複製粘貼到文件中保存
<html>
<head>
<title>
這是主頁
</title>
</head>
<body style="background:Pink">
<h1>Home</h1>
<p style="color:red">
Welcome to the Go homepage!
</p>
</body>
</html>
現在,讓我們在瀏覽器分別訪問http://127.0.0.1:8080/
、http://127.0.0.1:8080/static/home.html
和http://127.0.0.1:8080/static/note.txt
可以發現,http://127.0.0.1:8080/static/home.html
和http://127.0.0.1:8080/static/note.txt
的結果是不一樣的,這是因爲瀏覽器能識別html
標記語言,關於前端html
標記語言本文不做說明,請自行學習前端相關知識。
創建多路複用器
通過
NewServeMux
函數來創建一個默認的多路複用器,調用HandleFunc
函數將發送至根URL的請求重定向到對應的處理器。因爲所有處理器都接受一個ResponseWriter
實例和一個指向Request
結構的指針作爲參數,並且所有請求參數都可以通過訪問Request
結構得到,所以程序並不需要向處理器顯式地傳入任何請求參數。
多路複用器主要負責接收 HTTP 請求,並根據請求中的 URL 將請求重定向到正確的處理器。 注意,所有引入了 net/http
標準庫的程序都可以使用一個默認的多路複用器實例,當沒有爲 Server
結構指定處理器時,服務器就會使用 DefaultServeMux
。
實際上,所謂多路複用器,也就是我們在開發中常說的路由的概念,根據不同的URL,調用不同的函數去處理。
處理靜態文件
使用
FileServer
函數創建了一個處理器,它能夠處理指定目錄中的靜態文件。最後將這個處理器傳遞給多路複用器的Handle
函數
如示例代碼,當服務器接收到一個以/static/
開頭的 URL 請求時,以上將URL中的/static/
路徑映射到public
目錄中,然後查找被請求的文件。
例如,當服務器接收到一個針對文件 http://127.0.0.1:8080/static/note.txt
的請求時,它將會在public
目錄中查找note.txt
文件。這樣做的好處是可以將服務器上的真實文件目錄隱藏。
創建處理器函數
處理器函數實際上就是一個接受
ResponseWriter
和Request
指針作爲參數的 Go 函數。通常該函數負責生成HTML內容並將其寫人ResponseWriter
中。
func index(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "Hello, world!")
}
Go Web 應用基礎
簡單配置
除了可以通過
ListenAndServe
的參數對服務器的網絡地址和處理器進行配置之外,還可以通過 Server 結構對服務器進行更詳細的配置,如上面的例子,其中包括爲請求讀取操作設置超時時間、爲響應寫入操作設置超時時間,以及爲 Server 結構設置錯誤日誌記錄器等
最簡配置
package main
import (
"net/http"
)
func main() {
server := http.Server{
Addr : "127.0.0.1:8080" ,
Handler : nil,
}
server.ListenAndServe()
}
Server
結構詳細字段
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
TLSConfig *tls.Config // optional TLS config, used by ServeTLS and ListenAndServeTLS
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
inShutdown int32 // accessed atomically (non-zero means we're in Shutdown)
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used
mu sync.Mutex
listeners map[net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
處理器與處理器函數
處理器 是一個擁有
ServeHTTP
方法的接口,這個ServeHTTP
方法需要接受兩個參數,第一個參數是一個ResponseWriter
接口實例,而第二個參數則是一個指向Request
結構的指針。即任何擁有ServeHTTP
方法的接口,且該方法的簽名爲ServeHTTP(http.ResponseWriter, *http.Request)
,那麼這個接口就是一個處理器。處理器函數 實際上是與處理器擁有相同行爲的函數,這個函數與
ServeHTTP
方法擁有相同的簽名,即接受ResponseWriter
和指向Request
結構的指針作爲參數。通過使用HandlerFunc
可以把一個帶有正確簽名的函數f
轉換成一個帶有方法f
的處理器實例,這個方法會與DefaultServeMux
進行綁定。因此,處理器函數只不過是創建處理器的一種便利方法而已。
雖然處理器函數能夠完成跟處理器一樣的工作,並且使用處理器函數的代碼比使用處理器的代碼更爲簡潔,但是處理器函數並不能完全代替處理器。 這是因爲在某些情況下,代碼可能已經包含了某個接口或者某種類型,這時我們只需要爲它們添加 ServeHTTP
方法就可以將它們轉變爲處理器,這種轉變更靈活也有助於構建出更爲模塊化的 Web 應用。
創建處理器
package main
import (
"fmt"
"net/http"
)
// 聲明一個結構體
type MyHandler struct{
}
// 讓結構體實現 ServeHTTP 函數
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler, //指定處理器
}
server.ListenAndServe()
}
創建多個處理器
不用在
Server
結構的Handler
字段中指定處理器,而是讓服務器使用默認的DefaultServeMux
作爲處理器, 然後通過http.Handle
函數將處理器綁定至DefaultServeMux
。
需要說明的是,雖然Handle
函數來源於http
包,但它實際上是ServeMux
結構的方法,這些函數是爲了操作便利而創建的函數,調用它們等同於調用DefaultServeMux
的方法。 比如調用http.Handle
實際上就是在調用DefaultServeMux
的Handle
方法
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct{}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}
type WorldHandler struct{}
func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World!")
}
func main() {
hello := HelloHandler{}
world := WorldHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.Handle("/hello", &hello)
http.Handle("/world", &world)
server.ListenAndServe()
}
多路複用器
ServeMux
是一個HTTP請求多路複用器,它負責接收HTTP請求並根據請求中的 URL將請求重定向到正確的處理器。ServeMux
結構包含了一個映射,這個映射會將URL映射至相應的處理器。因爲ServeMux
結構也實現了ServeHTTP
方法,所以它也是一個處理器
ServeMux
是一個結構體而不是一個接口,因此DefaultServeMux
是ServeMux
的一個實例而不ServeMux
的實現。
前面提到的http.ListenAndServe(":8080", nil)
函數接受的第二個參數是一個處理器,但它的默認值卻是一個多路複用器DefaultServeMux
,這是因爲DefaultServeMux
多路複用器是ServeMux
結構的一個實例,而ServeMux
也擁有ServeHTTP
方法,也就是說DefaultServeMux
既是ServeMux
結構的實例,也是Handler
結構的實例。
第三方多路複用器
ServeMux
的一個缺陷是無法使用變量實現URL模式匹配。例如在瀏覽器請求/threads
的時候,ServeMux
可以很好地獲取並顯示所有帖子,但當瀏覽器請求的是/threads/123
時,那麼要獲取並顯示ID爲123的帖子就會變得非常困難。
創建自定義的多路複用器來代替net/http
包中的ServeMux
是可行的,並且目前市面上已經出現了很多第三方的多路複用器可供使用,而HttpRouter
就是一個功能強大的輕量級第三方多路複用器。
執行以下命令安裝 HttpRouter
go get github.corn/julienschrnidt/httprouter
這表示從GitHub上下載HttpRouter包源碼,並將其保存到GOPATH/src目錄中
使用示例
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", p.ByName("name"))
}
func main() {
//創建多路複用器
mux := httprouter.New()
//將處理器函數與給定的HTTP方法進行綁定
mux.GET("/hello/:name", hello)
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
server.ListenAndServe()
}
以上代碼不再使用HandleFunc
綁定處理器函數,而是直接把處理器函數與給定的 HTTP方法進行綁定。當瀏覽器向這個URL發送GET請求時,hello
函數就會被調用,但當瀏覽器向這個URL發送其他請求時,hello
函數不會被調用。
可以看到被綁定的URL包含了具名參數(named parameter),這些具名參數會被 URL中的具體值所代替,並且程序可以在處理器裏面獲取這些值。此時的處理器函數hello
接收三個參數,而第三個參數Params
就包含了具名參數,其值可以
通過ByName
方法獲取。
如,運行程序後,瀏覽器輸入localhost:8080/hello/fox
,則顯示 hello,fox,p.ByName
成功獲取到URL中的fox字段。