- 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(¬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()
}
- 数据模型和存储
上面的例子使用简单的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的开发.