前言
網頁程式基本的行為是對請求 (request) 進行相對應的回應 (response)。對不同的路徑有不同的行為。本文說明在 Golang 網頁程式中設置路由 (route) 的方法。
再訪 Hello World
我們另寫了一個和先前略有不同的 Hello World 程式,請讀者注意一下相異處:
package main
import (
"fmt"
"net/http"
)
func main() {
// Create a new HTTP request multiplexer
mux := http.NewServeMux()
// Set routes to their handlers
mux.HandleFunc("/", handler)
// By creating a new http.Server object,
// we may tune its behaviors
server := http.Server{
Addr: "0.0.0.0:8080",
Handler: mux,
}
// Run the server.
server.ListenAndServe()
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
我們在前文提過,mux
的用途是統整路徑和其相對應的處理器,將請求的路徑導向特定的處理器。我們先前直接用內建的 mux
,在這個寫法中,將 mux
這個路由物件獨立出來。目前我們暫時先用內建的路由物件,但這個架構保留了日後使用其他相容的第三方路由物件的彈性。
另外,我們建立 server
(伺服器) 物件,在建立物件時,我們可以藉由配置不同的參數來微調 server
物件的行為,是這個寫法帶來的好處。
使用動態路由
到目前為止,我們的範例程式都使用內建的 mux
物件;不過,Go 的 net/http 套件保留了擴充的彈性,我們可以用第三方的路由物件來取代內建的路由物件。像 httprouter 就是一個常見的路由套件。以下簵例用 httprouter
物件取代內建的 mux
物件:
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
func main() {
// Create a new HTTP request multiplexer
mux := httprouter.New()
// Set routes to their handlers
mux.GET("/hello/:name", hello)
// Create a HTTP server
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
// Run the server.
server.ListenAndServe()
}
func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprintf(w, "Hello, %s!", p.ByName("name"))
}
在這個範例中,這一段是新的語法:
mux.GET("/hello/:name", hello)
這個語法有兩個意義,一個告訴路由物件我們的 HTTP 動作 (action) 是 GET
,另一個則是在路徑中加入變數 :name
,而內建的路由物件無法加入變數。
另外,我們的路徑處理器 (handler) 的參數也和先前不同:
func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprintf(w, "Hello, %s!", p.ByName("name"))
}
除了原本的兩個參數,這個處理器另外加入 httprouter.Params
這個新的參數,這個參數用來處理路徑的變數。
使用 HTTP 動作 (Actions)
原先的 HTTP 協定只有 GET
一種 HTTP 動作,後來則新增數種 HTTP 動作,以符合多樣化的需求,常見的動作有以下數種:
GET
:取得資源,瀏覽器預設的動作POST
:建立資源PUT
:修改資源DELETE
:刪除資源
除了這四種動作,還有一些較少用的動作,此處不一一列出,有興趣的讀者可以參考這裡。在網頁程式中時常利用 HTTP 動作搭配路徑來封裝資料庫的 CRUD (增 查 改 刪) 行為。
在網頁程式中處理錯誤
在先前的程式中,我們都假設程式沒有錯誤,但我們不能一廂情願地認定使用者會按照我們設計的方式使用程式,而要針對可能的錯誤情境去撰寫相對應的程式碼。
網頁程式透過 HTTP 狀態碼 (status code) 回傳錯誤訊息,但使用者不會接觸到這個代碼,我們需另外撰寫錯誤頁面來告知使用者。
如果我們不寫錯誤頁面來告知使用者,網頁伺服器 (如 Apache 或 Nginx) 或網頁函式庫 (或框架) 本身可能會內建通知頁面;然而,這些通知頁面會無意間暴露過多的技術細節,成為駭客攻擊的依據。因此,真正上線的網頁程式應該要提供自己的錯誤頁面。
一般來說,常見的錯誤情境有以下數種:
- Not Found (HTTP 404):該頁面不存在
- Forbidden (HTTP 403):使用者沒有權限存取特定內容
- Internal Server Error (HTTP 500):網頁程式內部發生錯誤
建議至少針對這三種錯誤情境去做相對應的頁面。
在本範例中,我們加入 HTTP 404 和 HTTP 500 的頁面:
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
func main() {
// Set a new HTTP request multiplexer
mux := httprouter.New()
// Listen to root path
mux.GET("/", index)
// Custom 404 page
mux.NotFound = http.HandlerFunc(notFound)
// Custom 500 page
mux.PanicHandler = errorHandler
// Set the parameters for a HTTP server
server := http.Server{
Addr: "0.0.0.0:8080",
Handler: mux,
}
// Run the server.
server.ListenAndServe()
}
func index(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprintln(w, "Hello World")
}
func notFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w, "Page Not Found")
}
func errorHandler(w http.ResponseWriter, r *http.Request, p interface{}) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Internal Server Error")
}
這個程式的關鍵在於指定 mux.NotFound
和 mux.PanicHandler
兩項事件的處理器 (handler),在網頁程式發生錯誤時,就會連到我們預先撰寫的特定頁面,而不會連到網頁伺服器內建的頁面。
在預設情形下,路徑處理器皆回傳 HTTP 200 的狀態碼。如果要更改回傳的 HTTP 狀態碼,就必需要在程式碼中明確設置。在本範例中,我們使用 WriteHeader() 函式寫入 HTTP 狀態碼。
在 Golang 中,有許多代表狀態碼的常數 可用,不用去記憶 HTTP 狀態碼的數字。