Go web server開發(fā)學習2

  • 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(&note)
    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(&noteToUpd)
    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ā).

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末游两,一起剝皮案震驚了整個濱河市衫嵌,隨后出現(xiàn)的幾起案子残家,更是在濱河造成了極大的恐慌颊糜,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桩砰,死亡現(xiàn)場離奇詭異哮洽,居然都是意外死亡纹磺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門钮惠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茅糜,“玉大人,你說我怎么就攤上這事素挽∶镒福” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缩赛。 經(jīng)常有香客問我耙箍,道長,這世上最難降的妖魔是什么酥馍? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任辩昆,我火速辦了婚禮,結(jié)果婚禮上旨袒,老公的妹妹穿的比我還像新娘汁针。我一直安慰自己,他們只是感情好砚尽,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布施无。 她就那樣靜靜地躺著,像睡著了一般尉辑。 火紅的嫁衣襯著肌膚如雪帆精。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天隧魄,我揣著相機與錄音卓练,去河邊找鬼。 笑死购啄,一個胖子當著我的面吹牛襟企,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狮含,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼顽悼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了几迄?” 一聲冷哼從身側(cè)響起蔚龙,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎映胁,沒想到半個月后木羹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡解孙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年坑填,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弛姜。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡脐瑰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出廷臼,到底是詐尸還是另有隱情苍在,我是刑警寧澤绝页,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站忌穿,受9級特大地震影響抒寂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掠剑,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一屈芜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朴译,春花似錦井佑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盯拱,卻和暖如春盒发,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狡逢。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工宁舰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奢浑。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓蛮艰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雀彼。 傳聞我的和親對象是個殘疾皇子壤蚜,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)徊哑,斷路器袜刷,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • github地址窒盐,歡迎大家提交更新。 express() express()用來創(chuàng)建一個Express的程序钢拧。ex...
    Programmer客棧閱讀 2,525評論 0 1
  • 是什么蟹漓,是生命不能承受的,是“失去”嗎源内?那“失去”的具體含義又是什么呢葡粒?有人說是愛情份殿,有人說是責任,有人說是對生活...
    安靜的等待中閱讀 332評論 0 0
  • 從不曾想嗽交,只是做了十二分鐘的無氧塑形運動卿嘲,我的汗已經(jīng)止不住了,我身體的局部是多么缺乏運動啊夫壁,只是針對性做了簡短運動...
    Sophia索菲閱讀 237評論 0 0