- DefaultServeMux
DefaultServeMux在http包使用的時候初始化
var DefaultServeMux = NewServeMux()
func NewServeMux() *ServeMux{return &ServeMux{m:make(map[string]muxEntry)}}
http包使用DefaultServeMux,實現(xiàn)了http.Handle和http.HandleFunc的簡寫方式.http.Handle方法在DefaultServeMux注冊了handler,而http.HandleFunc在DefautServeMux注冊了一個返回值是http.Handler的方法.所以這兩個方式都是在DefaultServeMux簡易的使用了ServeMux.Handle和ServeMux.HandleFunc;
ListenAndServe方法的第二個參數(shù)如果是nil,就會調(diào)用DefaultServeMux,提供一個http.Handler對象;
使用DefaultServeMux例子:
package main
import (
"fmt"
"log"
"net/http"
)
func messageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "歡迎使用Go!")
}
func main() {
http.HandleFunc("/welcome", messageHandler)
log.Println("Listening...")
http.ListenAndServe(":9090", mux)
}
- http.Serve 結(jié)構體
在前面的例子中,運行HTTP服務器就調(diào)用http.ListenAndServe;缺憾就是不能手動配置服務器的設置. http包提供了Serve結(jié)構體可以讓開發(fā)者自定義服務器的參數(shù).
go源碼
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
ReadTimeout time.Duration // maximum duration before timing out read of the request
WriteTimeout time.Duration // maximum duration before timing out write of the response
MaxHeaderBytes int // maximum size of request headers, DefaultMaxHeaderBytes if 0
TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS
// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an NPN
// protocol upgrade has occurred. The map key is the protocol
// name negotiated. The Handler argument should be used to
// handle HTTP requests and will initialize the Request's TLS
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
// If TLSNextProto is nil, HTTP/2 support is enabled automatically.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)
// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
nextProtoOnce sync.Once // guards initialization of TLSNextProto in Serve
nextProtoErr error
}
允許設置error日志,最大最小超時時間,請求頭字節(jié)
使用http.Server的例子
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func messageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "歡迎使用Go!")
}
func main() {
http.HandleFunc("/welcome", messageHandler)
server := &http.Server{
Addr: ":9090",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Println("Listening...")
server.ListenAndServe()
}
自定義的server調(diào)用ListenAndServe()方法啟動服務器.
- 第三方庫 Gorilla Mux
http.ServeMux 在很多情況下都能夠適應請求的多路由,在前面的多個例子中都用到,但當我們需要更加靈活的路由時,自帶的就可能不能滿足需求了,需要尋求第三庫.比如我們要RESTful API時.
Gorilla Mux允許自定義路由.當要建立RESTful服務時,和自帶的http.ServeMux對比就能感受到差別.
使用Gorilla Mux的大致模樣
func main() {
r := mux.NewRouter().StrictSlash(false)
r.HandleFunc("/api/notes", GetNoteHandler).Methods("GET")
r.HandleFunc("/api/notes", PostNoteHandler).Methods("POST")
server := &http.Server{
Addr: ":9090",
Handler: r,
}
server.ListenAndServe()
}
一個mux.Router對象通過調(diào)用NewRouter方法創(chuàng)建,之后導向路由的資源.
當指定到一個URI參數(shù)時,可以和http請求匹配,這在建立RESTful應用的時候非常有用. 因為mux包實現(xiàn)了http.Handler 接口,可以容易的和http標準庫結(jié)合使用.可以非常容易的拓展開發(fā)出自己的包或者第三方庫.
和其它的web組合系統(tǒng)不同,Go的web開發(fā)合適的方式是:拓展基礎功能結(jié)合第三方庫;當選擇第三方庫,最好選擇和標準庫融合度較好的.Gorilla Mux就是一個很好的例子.
- 使用RESTful API
// RESTful
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
)
type Note struct {
Title string `json:"title"`
Description string `json:"description"`
CreateOn time.Time `json:"createon"`
}
//保存notes
var noteStore = make(map[string]Note)
//每個對象的id
var id int = 0
//HTTP GET - /api/notes
func GetNoteHandler(w http.ResponseWriter, r *http.Request) {
var notes []Note
for _, v := range noteStore {
notes = append(notes, v)
}
w.Header().Set("Content-Type", "application/json")
j, err := json.Marshal(notes)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusOK)
w.Write(j)
}
//HTTP Post /api/notes
func PostNoteHandler(w http.ResponseWriter, r *http.Request) {
var note Note
err := json.NewDecoder(r.Body).Decode(¬e)
if err != nil {
panic(err)
}
note.CreateOn = time.Now()
id++
k := strconv.Itoa(id)
noteStore[k] = note
j, err := json.Marshal(note)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
w.Write(j)
}
//HTTP Put - /api/notes/{id}
func PutNoteHandler(w http.ResponseWriter, r *http.Request) {
var err error
vars := mux.Vars(r)
k := vars["id"]
var noteToUpd Note
err = json.NewDecoder(r.Body).Decode(¬eToUpd)
if err != nil {
panic(err)
}
if note, ok := noteStore[k]; ok {
noteToUpd.CreateOn = note.CreateOn
delete(noteStore, k)
noteStore[k] = noteToUpd
} else {
log.Printf("Could not find key of Note %s to delete", k)
}
w.WriteHeader(http.StatusNoContent)
}
//HTTP Delete - /api/notes/{id}
func DeleteNoteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
k := vars["id"]
if _, ok := noteStore[k]; ok {
delete(noteStore, k)
} else {
log.Printf("Could not find key of Note %s to delete", k)
}
w.WriteHeader(http.StatusNoContent)
}
func main() {
r := mux.NewRouter().StrictSlash(false)
r.HandleFunc("/api/notes", GetNoteHandler).Methods("GET")
r.HandleFunc("/api/notes", PostNoteHandler).Methods("POST")
r.HandleFunc("/api/notes/{id}", PutNoteHandler).Methods("PUT")
r.HandleFunc("/api/notes/{id}", DeleteNoteHandler).Methods("DELETE")
server := &http.Server{
Addr: ":9090",
Handler: r,
}
log.Println("Listeing...")
server.ListenAndServe()
}
- 數(shù)據(jù)模型和存儲
上面的例子使用簡單的CRUD操作數(shù)據(jù)模型Note,建立簡單的REST API
type Note struct {
Title string `json:"title"`
Description string `json:"description"`
CreateOn time.Time `json:"createon"`
}
對應JSON類型的數(shù)據(jù)API,結(jié)構體字段被編碼成json作為相應發(fā)送到客戶端.可以很輕松的將struct和json之間相互轉(zhuǎn)換,還可以自定義json的字段名.
在上面的例子,還沒有用到數(shù)據(jù)庫持久化數(shù)據(jù),只是把數(shù)據(jù)保存到一個字典中;
- 配置路由
使用mux包作為路由,配置handler.因為mux支持HTTP方法的映射,可以輕松的使用RESTful的方式展示數(shù)據(jù)源.
//程序的入口點
func main() {
r := mux.NewRouter().StrictSlash(false)
r.HandleFunc("/api/notes", GetNoteHandler).Methods("GET")
r.HandleFunc("/api/notes", PostNoteHandler).Methods("POST")
r.HandleFunc("/api/notes/{id}", PutNoteHandler).Methods("PUT")
r.HandleFunc("/api/notes/{id}", DeleteNoteHandler).Methods("DELETE")
server := &http.Server{
Addr: ":9090",
Handler: r,
}
log.Println("Listeing...")
server.ListenAndServe()
}
- Handler函數(shù)來作CRUD操作
//HTTP GET - /api/notes
func GetNoteHandler(w http.ResponseWriter, r *http.Request) {
var notes []Note
for _, v := range noteStore {
notes = append(notes, v)
}
w.Header().Set("Content-Type", "application/json")
j, err := json.Marshal(notes)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusOK)
w.Write(j)
}
在這里,先沒救了noteStore字典,把值添加到臨時的一個slice中(notes),通過調(diào)用json包下的Marshal方法,把notes切片轉(zhuǎn)換成json數(shù)據(jù).
當我們訪問這個API時,不出問題就能得到類似的json結(jié)果:
[
{
"title": "hello",
"description": "Hello,Go is awesome",
"createon": "2016-07-27T14:07:15.314297691+08:00"
}
]
小結(jié):目前學習了基本的web開發(fā)和RESTful API的開發(fā).
Go對應web,后端系統(tǒng),特別是建立RESTful APIs的出色表現(xiàn),是非常好的技術棧選擇.net/http包提供了構建web應用的基礎模塊,拓展基礎的功能,可以開發(fā)出第三方庫和自己的庫.
net/http包HTTP請求的主要的兩個模塊:http.ServeMux和http.Handler.對應了請求的路由和相應操作;
最后還進行了基于json數(shù)據(jù)的RESTful API的開發(fā).