Go web server开发学习2

  • DefaultServeMux

DefaultServeMux在http包使用的时候初始化

var DefaultServeMux = NewServeMux()

func NewServeMux() *ServeMux{return &ServeMux{m:make(map[string]muxEntry)}}

http包使用DefaultServeMux,实现了http.Handle和http.HandleFunc的简写方式.http.Handle方法在DefaultServeMux注册了handler,而http.HandleFunc在DefautServeMux注册了一个返回值是http.Handler的方法.所以这两个方式都是在DefaultServeMux简易的使用了ServeMux.Handle和ServeMux.HandleFunc;

ListenAndServe方法的第二个参数如果是nil,就会调用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 结构体

在前面的例子中,运行HTTP服务器就调用http.ListenAndServe;缺憾就是不能手动配置服务器的设置. http包提供了Serve结构体可以让开发者自定义服务器的参数.

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日志,最大最小超时时间,请求头字节

使用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调用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对象通过调用NewRouter方法创建,之后导向路由的资源.

当指定到一个URI参数时,可以和http请求匹配,这在建立RESTful应用的时候非常有用. 因为mux包实现了http.Handler 接口,可以容易的和http标准库结合使用.可以非常容易的拓展开发出自己的包或者第三方库.

和其它的web组合系统不同,Go的web开发合适的方式是:拓展基础功能结合第三方库;当选择第三方库,最好选择和标准库融合度较好的.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()
}

  • 数据模型和存储

上面的例子使用简单的CRUD操作数据模型Note,建立简单的REST API

type Note struct {
    Title       string    `json:"title"`
    Description string    `json:"description"`
    CreateOn    time.Time `json:"createon"`
}

对应JSON类型的数据API,结构体字段被编码成json作为相应发送到客户端.可以很轻松的将struct和json之间相互转换,还可以自定义json的字段名.

在上面的例子,还没有用到数据库持久化数据,只是把数据保存到一个字典中;

  • 配置路由

使用mux包作为路由,配置handler.因为mux支持HTTP方法的映射,可以轻松的使用RESTful的方式展示数据源.

//程序的入口点
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函数来作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),通过调用json包下的Marshal方法,把notes切片转换成json数据.

当我们访问这个API时,不出问题就能得到类似的json结果:

[
    {
        "title": "hello",
        "description": "Hello,Go is awesome",
        "createon": "2016-07-27T14:07:15.314297691+08:00"
    }
]

小结:目前学习了基本的web开发和RESTful API的开发.
Go对应web,后端系统,特别是建立RESTful APIs的出色表现,是非常好的技术栈选择.net/http包提供了构建web应用的基础模块,拓展基础的功能,可以开发出第三方库和自己的库.

net/http包HTTP请求的主要的两个模块:http.ServeMux和http.Handler.对应了请求的路由和相应操作;

最后还进行了基于json数据的RESTful API的开发.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 140,587评论 1 295
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 60,384评论 1 254
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 92,492评论 0 206
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 40,584评论 0 171
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,270评论 1 250
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,517评论 1 167
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,227评论 2 266
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,001评论 0 161
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 28,705评论 6 223
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,339评论 0 211
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,105评论 2 211
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,432评论 1 222
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,131评论 0 31
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 26,950评论 2 208
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,335评论 3 200
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,571评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 25,897评论 0 163
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,295评论 2 226
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,414评论 2 228

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,360评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 133,983评论 18 139
  • github地址,欢迎大家提交更新。 express() express()用来创建一个Express的程序。ex...
    Programmer客栈阅读 2,406评论 0 1
  • 是什么,是生命不能承受的,是“失去”吗?那“失去”的具体含义又是什么呢?有人说是爱情,有人说是责任,有人说是对生活...
    安静的等待中阅读 302评论 0 0
  • 从不曾想,只是做了十二分钟的无氧塑形运动,我的汗已经止不住了,我身体的局部是多么缺乏运动啊,只是针对性做了简短运动...
    Sophia索菲阅读 221评论 0 0