http 是典型的 C/S 架構,客戶端向服務端發送請求(request),服務端作出應答(response)。html
golang 的標準庫 net/http
提供了 http 編程有關的接口,封裝了內部TCP鏈接和報文解析的複雜瑣碎的細節,使用者只須要和 http.request
和 http.ResponseWriter
兩個對象交互就行。也就是說,咱們只要寫一個 handler,請求會經過參數傳遞進來,而它要作的就是根據請求的數據作處理,把結果寫到 Response 中。廢話很少說,來看看 hello world 程序有多簡單吧!git
package main
import (
"io"
"net/http"
)
type helloHandler struct{}
func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
}
func main() {
http.Handle("/", &helloHandler{})
http.ListenAndServe(":12345", nil)
}複製代碼
運行 go run hello_server.go
,咱們的服務器就會監聽在本地的 12345
端口,對全部的請求都會返回 hello, world!
:github
正如上面程序展現的那樣,咱們只要實現的一個 Handler,它的接口原型是(也就是說只要實現了 ServeHTTP
方法的對象均可以做爲 Handler):golang
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}複製代碼
而後,註冊到對應的路由路徑上就 OK 了。正則表達式
http.HandleFunc
接受兩個參數:第一個參數是字符串表示的 url 路徑,第二個參數是該 url 實際的處理對象。編程
http.ListenAndServe
監聽在某個端口,啓動服務,準備接受客戶端的請求(第二個參數這裏設置爲 nil
,這裏也不要糾結什麼意思,後面會有講解)。每次客戶端有請求的時候,把請求封裝成 http.Request
,調用對應的 handler 的 ServeHTTP
方法,而後把操做後的 http.ResponseWriter
解析,返回到客戶端。json
上面的代碼沒有什麼問題,可是有一個不便:每次寫 Handler 的時候,都要定義一個類型,而後編寫對應的 ServeHTTP
方法,這個步驟對於全部 Handler 都是同樣的。重複的工做老是能夠抽象出來,net/http
也正這麼作了,它提供了 http.HandleFunc
方法,容許直接把特定類型的函數做爲 handler。上面的代碼能夠改爲:後端
package main
import (
"io"
"net/http"
)
func helloHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func main() {
http.HandleFunc("/", helloHandler)
http.ListenAndServe(":12345", nil)
}複製代碼
其實,HandleFunc
只是一個適配器,瀏覽器
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}複製代碼
自動給 f
函數添加了 HandlerFunc
這個殼,最終調用的仍是 ServerHTTP
,只不過會直接使用 f(w, r)
。這樣封裝的好處是:使用者能夠專一於業務邏輯的編寫,省去了不少重複的代碼處理邏輯。若是隻是簡單的 Handler,會直接使用函數;若是是須要傳遞更多信息或者有複雜的操做,會使用上部分的方法。服務器
若是須要咱們本身寫的話,是這樣的:
package main
import (
"io"
"net/http"
)
func helloHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func main() {
// 經過 HandlerFunc 把函數轉換成 Handler 接口的實現對象
hh := http.HandlerFunc(helloHandler)
http.Handle("/", hh)
http.ListenAndServe(":12345", nil)
}複製代碼
大部分的服務器邏輯都須要使用者編寫對應的 Handler,不過有些 Handler 使用頻繁,所以 net/http
提供了它們的實現。好比負責文件 hosting 的 FileServer
、負責 404 的NotFoundHandler
和 負責重定向的RedirectHandler
。下面這個簡單的例子,把當前目錄全部文件 host 到服務端:
package main
import (
"net/http"
)
func main() {
http.ListenAndServe(":12345", http.FileServer(http.Dir(".")))
}複製代碼
強大吧!只要一行邏輯代碼就能實現一個簡單的靜態文件服務器。從這裏能夠看出一件事:http.ListenAndServe
第二個參數就是一個 Handler 函數(請記住這一點,後面有些內容依賴於這個)。
運行這個程序,在瀏覽器中打開 http://127.0.0.1:12345
,能夠看到全部的文件,點擊對應的文件還能看到它的內容。
其餘兩個 Handler,這裏就再也不舉例子了,讀者能夠自行參考文檔。
雖然上面的代碼已經工做,而且能實現不少功能,可是實際開發中,HTTP 接口會有許多的 URL 和對應的 Handler。這裏就要講 net/http
的另一個重要的概念:ServeMux
。Mux
是 multiplexor
的縮寫,就是多路傳輸的意思(請求傳過來,根據某種判斷,分流到後端多個不一樣的地方)。ServeMux
能夠註冊多了 URL 和 handler 的對應關係,並自動把請求轉發到對應的 handler 進行處理。咱們仍是來看例子吧:
package main
import (
"io"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
func echoHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.URL.Path)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
mux.HandleFunc("/", echoHandler)
http.ListenAndServe(":12345", mux)
}複製代碼
這個服務器的功能也很簡單:若是在請求的 URL 是 /hello
,就返回 hello, world!
;不然就返回 URL 的路徑,路徑是從請求對象 http.Requests
中提取的。
這段代碼和以前的代碼有兩點區別:
NewServeMux
生成了 ServerMux
結構,URL 和 handler 是經過它註冊的http.ListenAndServe
方法第二個參數變成了上面的 mux
變量還記得咱們以前說過,http.ListenAndServe
第二個參數應該是 Handler 類型的變量嗎?這裏爲何能傳過來 ServeMux
?嗯,估計你也猜到啦:ServeMux
也是是 Handler
接口的實現,也就是說它實現了 ServeHTTP
方法,咱們來看一下:
type ServeMux struct {
// contains filtered or unexported fields
}
func NewServeMux() *ServeMux func (mux *ServeMux) Handle(pattern string, handler Handler) func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)複製代碼
哈!果真,這裏的方法咱們大都很熟悉,除了 Handler()
返回某個請求的 Handler。Handle
和 HandleFunc
這兩個方法 net/http
也提供了,後面咱們會說明它們之間的關係。而 ServeHTTP
就是 ServeMux
的核心處理邏輯:根據傳遞過來的 Request,匹配以前註冊的 URL 和處理函數,找到最匹配的項,進行處理。能夠說 ServeMux
是個特殊的 Handler,它負責路由和調用其餘後端 Handler 的處理方法。
關於ServeMux
,有幾點要說明:
/
:表示一個子樹,後面能夠跟其餘子路徑; 末尾不是 /
,表示一個葉子,固定的路徑/
結尾的 URL 能夠匹配它的任何子路徑,好比 /images
會匹配 /images/cute-cat.jpg
ServeMux
也會識別和處理 .
和 ..
,正確轉換成對應的 URL 地址你可能會有疑問?咱們之間爲何沒有使用 ServeMux
就能實現路徑功能?那是由於 net/http
在後臺默認建立使用了 DefaultServeMux
。
嗯,上面基本覆蓋了編寫 HTTP 服務端須要的全部內容。這部分就分析一下,它們的源碼實現,加深理解,之後遇到疑惑也能經過源碼來定位和解決。
首先來看 http.ListenAndServe()
:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}複製代碼
這個函數其實也是一層封裝,建立了 Server
結構,並調用它的 ListenAndServe
方法,那咱們就跟進去看看:
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
......
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections. If
// srv.Addr is blank, ":http" is used.
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}複製代碼
Server
保存了運行 HTTP 服務須要的參數,調用 net.Listen
監聽在對應的 tcp 端口,tcpKeepAliveListener
設置了 TCP 的 KeepAlive
功能,最後調用 srv.Serve()
方法開始真正的循環邏輯。咱們再跟進去看看 Serve
方法:
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
// 循環邏輯,接受請求並處理
for {
// 有新的鏈接
rw, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
// 建立 Conn 鏈接
c, err := srv.newConn(rw)
if err != nil {
continue
}
c.setState(c.rwc, StateNew) // before Serve can return
// 啓動新的 goroutine 進行處理
go c.serve()
}
}複製代碼
最上面的註釋也說明了這個方法的主要功能:
Listener l
傳遞過來的請求srv.Handler
func (c *conn) serve() {
origConn := c.rwc // copy it before it's set nil on Close or Hijack
...
for {
w, err := c.readRequest()
if c.lr.N != c.server.initialLimitedReaderSize() {
// If we read any bytes off the wire, we're active.
c.setState(c.rwc, StateActive)
}
...
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
serverHandler{c.server}.ServeHTTP(w, w.req)
w.finishRequest()
if w.closeAfterReply {
if w.requestBodyLimitHit {
c.closeWriteAndWait()
}
break
}
c.setState(c.rwc, StateIdle)
}
}複製代碼
看到上面這段代碼 serverHandler{c.server}.ServeHTTP(w, w.req)
這一句了嗎?它會調用最先傳遞給 Server
的 Handler 函數:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}複製代碼
哇!這裏看到 DefaultServeMux
了嗎?若是沒有 handler 爲空,就會使用它。handler.ServeHTTP(rw, req)
,Handler 接口都要實現 ServeHTTP
這個方法,由於這裏就要被調用啦。
也就是說,不管如何,最終都會用到 ServeMux
,也就是負責 URL 路由的傢伙。前面也已經說過,它的 ServeHTTP
方法就是根據請求的路徑,把它轉交給註冊的 handler 進行處理。此次,咱們就在源碼層面一探究竟。
咱們已經知道,ServeMux
會以某種方式保存 URL 和 Handlers 的對應關係,下面咱們就從代碼層面來解開這個祕密:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // 存放路由信息的字典!\(^o^)/
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}複製代碼
沒錯,數據結構也比較直觀,和咱們想象的差很少,路由信息保存在字典中,接下來就看看幾個重要的操做:路由信息是怎麼註冊的?ServeHTTP
方法究竟是怎麼作的?路由查找過程是怎樣的?
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 邊界狀況處理
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
// 建立 `muxEntry` 並添加到路由字典中
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
// 這是一個頗有用的小技巧,若是註冊了 `/tree/`, `serveMux` 會自動添加一個 `/tree` 的路徑並重定向到 `/tree/`。固然這個 `/tree` 路徑會被用戶顯示的路由信息覆蓋。
// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern}
}
}複製代碼
路由註冊沒有什麼特殊的地方,很簡單,也符合咱們的預期,注意最後一段代碼對相似 /tree
URL 重定向的處理。
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}複製代碼
好吧,ServeHTTP
也只是經過 mux.Handler(r)
找到請求對應的 handler,調用它的 ServeHTTP
方法,代碼比較簡單咱們就顯示了,它最終會調用 mux.match()
方法,咱們來看一下它的實現:
// Does path match pattern?
func pathMatch(pattern, path string) bool {
if len(pattern) == 0 {
// should not happen
return false
}
n := len(pattern)
if pattern[n-1] != '/' {
return pattern == path
}
// 匹配的邏輯很簡單,path 前面的字符和 pattern 同樣就是匹配
return len(path) >= n && path[0:n] == pattern
}
// Find a handler on a handler map given a path string
// Most-specific (longest) pattern wins
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
// 最長匹配的邏輯在這裏
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}複製代碼
match
會遍歷路由信息字典,找到全部匹配該路徑最長的那個。路由部分的代碼解釋就到這裏了,最後回答上面的一個問題:http.HandleFunc
和 ServeMux.HandlerFunc
是什麼關係?
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}複製代碼
原來是直接經過 DefaultServeMux
調用對應的方法,到這裏上面的一切都串起來了!
最後一部分,要講講 Handler 函數接受的兩個參數:http.Request
和 http.ResponseWriter
。
Request 就是封裝好的客戶端請求,包括 URL,method,header 等等全部信息,以及一些方便使用的方法:
// A Request represents an HTTP request received by a server
// or to be sent by a client.
//
// The field semantics differ slightly between client and server
// usage. In addition to the notes on the fields below, see the
// documentation for Request.Write and RoundTripper.
type Request struct {
// Method specifies the HTTP method (GET, POST, PUT, etc.).
// For client requests an empty string means GET.
Method string
// URL specifies either the URI being requested (for server
// requests) or the URL to access (for client requests).
//
// For server requests the URL is parsed from the URI
// supplied on the Request-Line as stored in RequestURI. For
// most requests, fields other than Path and RawQuery will be
// empty. (See RFC 2616, Section 5.1.2)
//
// For client requests, the URL's Host specifies the server to
// connect to, while the Request's Host field optionally
// specifies the Host header value to send in the HTTP
// request.
URL *url.URL
// The protocol version for incoming requests.
// Client requests always use HTTP/1.1.
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// A header maps request lines to their values.
// If the header says
//
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// Connection: keep-alive
//
// then
//
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Connection": {"keep-alive"},
// }
//
// HTTP defines that header names are case-insensitive.
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
//
// For client requests certain headers are automatically
// added and may override values in Header.
//
// See the documentation for the Request.Write method.
Header Header
// Body is the request's body.
//
// For client requests a nil body means the request has no
// body, such as a GET request. The HTTP Client's Transport
// is responsible for calling the Close method.
//
// For server requests the Request Body is always non-nil
// but will return EOF immediately when no body is present.
// The Server will close the request body. The ServeHTTP
// Handler does not need to.
Body io.ReadCloser
// ContentLength records the length of the associated content.
// The value -1 indicates that the length is unknown.
// Values >= 0 indicate that the given number of bytes may
// be read from Body.
// For client requests, a value of 0 means unknown if Body is not nil.
ContentLength int64
// TransferEncoding lists the transfer encodings from outermost to
// innermost. An empty list denotes the "identity" encoding.
// TransferEncoding can usually be ignored; chunked encoding is
// automatically added and removed as necessary when sending and
// receiving requests.
TransferEncoding []string
// Close indicates whether to close the connection after
// replying to this request (for servers) or after sending
// the request (for clients).
Close bool
// For server requests Host specifies the host on which the
// URL is sought. Per RFC 2616, this is either the value of
// the "Host" header or the host name given in the URL itself.
// It may be of the form "host:port".
//
// For client requests Host optionally overrides the Host
// header to send. If empty, the Request.Write method uses
// the value of URL.Host.
Host string
// Form contains the parsed form data, including both the URL
// field's query parameters and the POST or PUT form data.
// This field is only available after ParseForm is called.
// The HTTP client ignores Form and uses Body instead.
Form url.Values
// PostForm contains the parsed form data from POST or PUT
// body parameters.
// This field is only available after ParseForm is called.
// The HTTP client ignores PostForm and uses Body instead.
PostForm url.Values
// MultipartForm is the parsed multipart form, including file uploads.
// This field is only available after ParseMultipartForm is called.
// The HTTP client ignores MultipartForm and uses Body instead.
MultipartForm *multipart.Form
...
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string
...
}複製代碼
Handler 須要知道關於請求的任何信息,都要從這個對象中獲取,通常不會直接修改這個對象(除非你很是清楚本身在作什麼)!
ResponseWriter 是一個接口,定義了三個方法:
Header()
:返回一個 Header 對象,能夠經過它的 Set()
方法設置頭部,注意最終返回的頭部信息可能和你寫進去的不徹底相同,由於後續處理還可能修改頭部的值(好比設置 Content-Length
、Content-type
等操做)Write()
: 寫 response 的主體部分,好比 html
或者 json
的內容就是放到這裏的WriteHeader()
:設置 status code,若是沒有調用這個函數,默認設置爲 http.StatusOK
, 就是 200
狀態碼// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
type ResponseWriter interface {
// Header returns the header map that will be sent by WriteHeader.
// Changing the header after a call to WriteHeader (or Write) has
// no effect.
Header() Header
// Write writes the data to the connection as part of an HTTP reply.
// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
// before writing the data. If the Header does not contain a
// Content-Type line, Write adds a Content-Type set to the result of passing
// the initial 512 bytes of written data to DetectContentType.
Write([]byte) (int, error)
// WriteHeader sends an HTTP response header with status code.
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
// Thus explicit calls to WriteHeader are mainly used to
// send error codes.
WriteHeader(int)
}複製代碼
實際上傳遞給 Handler 的對象是:
// A response represents the server side of an HTTP response.
type response struct {
conn *conn
req *Request // request for this response
wroteHeader bool // reply header has been (logically) written
wroteContinue bool // 100 Continue response was written
w *bufio.Writer // buffers output in chunks to chunkWriter
cw chunkWriter
sw *switchWriter // of the bufio.Writer, for return to putBufioWriter
// handlerHeader is the Header that Handlers get access to,
// which may be retained and mutated even after WriteHeader.
// handlerHeader is copied into cw.header at WriteHeader
// time, and privately mutated thereafter.
handlerHeader Header
...
status int // status code passed to WriteHeader
...
}複製代碼
它固然實現了上面提到的三個方法,具體代碼就不放到這裏了,感興趣的能夠本身去看。
雖然 net/http
提供的各類功能已經知足基本需求了,可是不少時候還不夠方便,好比:
ServeMux
變量中雖然這些均可以本身手動去碼,但實在很不方便。這部分看看有哪些三方的包,都提供了哪些額外的功能。
alice 的功能很簡單——把多個 handler 串聯起來,有請求過來的時候,逐個經過這個 handler 進行處理。
alice.New(Middleware1, Middleware2, Middleware3).Then(App)複製代碼
Gorilla 提供了不少網絡有關的組件, Mux 就是其中一個,負責 HTTP 的路由功能。這個組件彌補了上面提到的 ServeMux
的一些缺陷,支持的功能有:
SubRouter
能夠實現路由信息的傳遞ServeMux
徹底兼容r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)複製代碼
httprouter 和 mux
同樣,也是擴展了自帶 ServeMux
功能的路由庫。它的主要特色是速度快、內存使用少、可擴展性高(使用 radix tree 數據結構進行路由匹配,路由項不少的時候速度也很快)。
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}複製代碼
http middleware 庫,支持嵌套的中間件,可以和其餘路由庫兼容。同時它也自帶了很多 middleware 可使用,好比Recovery
、Logger
、Static
。
router := mux.NewRouter()
router.HandleFunc("/", HomeHandler)
n := negroni.New(Middleware1, Middleware2)
// Or use a middleware with the Use() function
n.Use(Middleware3)
// router goes last
n.UseHandler(router)
http.ListenAndServe(":3001", n)複製代碼
這篇文章參考瞭如下資料: