創(chuàng)建一個簡單的http服務(wù)
使用go語言搭建一個http服務(wù)事件很簡單的事情馋吗,如果代碼邏輯很簡單的話甚至不用框架就行。話不多說,直接看代碼吧限匣。
package main
import (
"fmt"
"net/http"
)
func echo(rw http.ResponseWriter, r *http.Request) {
var rspText string
params := r.URL.Query() // 獲取查詢參數(shù)
values, ok := params["name"]
if ok && len(values) > 0 {
rspText = fmt.Sprintf("hello, %s!", values[0])
} else {
rspText = "hello, boy!"
}
rw.Header().Add("Content-Type", "text/plain") // 添加header讥珍,值得注意的是历极,header必須在Write之前調(diào)用,否則不會生效
rw.WriteHeader(http.StatusOK) // 這里可以省略
rw.Write([]byte(rspText))
}
func main() {
http.HandleFunc("/echo", echo) // 注冊路由以及回調(diào)函數(shù)
// 監(jiān)聽ip和端口衷佃,第二個參數(shù)為nil趟卸,則使用默認(rèn)的DefaultServeMux來處理請求
if err := http.ListenAndServe("127.0.0.1:10001", nil); err != nil {
panic(err)
}
}
運(yùn)行以及返回結(jié)果
~ http :10001/echo name==coolboy -v
GET /echo?name=coolboy HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:10001
User-Agent: HTTPie/2.0.0
HTTP/1.1 200 OK
Content-Length: 15
Content-Type: text/plain
Date: Wed, 19 Jan 2022 08:00:36 GMT
hello, coolboy!
可看出go語言創(chuàng)建http服務(wù)最簡單只需要兩步
- 使用
http.HandleFunc
綁定路由以及路由對應(yīng)的回調(diào)函數(shù) - 使用
http.ListenAndServe
指定監(jiān)聽端口,并啟動服務(wù)
下面我們第一步的去分析這兩個函數(shù)氏义。
了解http.HandleFunc
首先我們先看下源碼锄列,go語言的源碼注釋還是可以的,借助源碼我們可以方便正確的了解該函數(shù)的作用
// file: net/http/server.go
var defaultServeMux ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
// 這里可以看出惯悠,實(shí)際上是調(diào)用了DefaultServeMux.HandleFunc
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
首先看參數(shù)類型邻邮,第一個是路由地址,沒啥好說的克婶。不過注釋提示了饶囚,如果想進(jìn)一步了解路由的匹配規(guī)則帕翻,可以查看ServeMux
的文檔。第二個參數(shù)也限定了回調(diào)函數(shù)的類型萝风。下面我們具體查看下回調(diào)函數(shù)的這兩個參數(shù)類型嘀掸。
首先是ResponseWriter
,它其實(shí)是一個接口類型规惰〔撬看起來也是簡單且清晰的,就是三件事三個接口
- 寫入http response status狀態(tài)碼
- 寫入http response header
- 寫入http response body(這里使用byte類型歇万,即支持任意數(shù)據(jù)類型)
type Header map[string][]string
// 注意揩晴,源碼注釋比較詳細(xì),但是字?jǐn)?shù)太多就不放這里了贪磺,推薦去看看的
type ResponseWriter interface {
// 返回一個header字典硫兰,調(diào)用WriteHeader函數(shù)時,將會返回該map數(shù)據(jù)寒锚。但是對header的
// 修改必須在調(diào)用WriteHeader前劫映,否則不會生效
Header() Header
// 寫數(shù)據(jù)到http響應(yīng)里的body中
// 調(diào)用該函數(shù)之前,如果沒調(diào)用WriteHeader則會先調(diào)用一次
Write([]byte) (int, error)
// 先http相應(yīng)寫入statusCode以及header內(nèi)容
WriteHeader(statusCode int)
}
下面再來看看Request
刹前,它是一個結(jié)構(gòu)體泳赋,而不像ResponseWriter
是一個接口類型。
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string // path (relative paths may omit leading slash)
RawPath string // encoded path hint (see EscapedPath method)
ForceQuery bool // append a query ('?') even if RawQuery is empty
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
RawFragment string // encoded fragment hint (see EscapedFragment method)
}
type Request struct {
// http請求方法:如GET, POST, PUT
Method string
// 如上面URL所示喇喉,記錄了請求url以及url后面的查詢參數(shù)信息
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// If a server received a request with header lines,
//
// Host: example.com
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// fOO: Bar
// foo: two
//
// then
//
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Foo": {"Bar", "two"},
// }
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.
// 這里由提示祖今,如果由使用Body,則要記得調(diào)用Close方法關(guān)閉Body
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
// 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 with a non-nil Body is
// also treated as unknown.
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 bool
Host string
// Form contains the parsed form data, including both the URL
// field's query parameters and the PATCH, POST, or PUT form data.
// This field is only available after ParseForm is called.
// The HTTP client ignores Form and uses Body instead.
// 注意的是拣技,這里的值包括url和body里的兩部分
Form url.Values
// PostForm contains the parsed form data from PATCH, 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 *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
// Response is the redirect response which caused this request
// to be created. This field is only populated during client
// redirects.
Response *Response
ctx context.Context
}
可以看出千诬,Request
結(jié)構(gòu)包含了http請求的信息并做了簡單的處理,同時也內(nèi)置了一些基礎(chǔ)的函數(shù)膏斤,大家可以通過IDE的智能提示或者源碼看看大渤。
了解ListenAndServe
ListenAndServe
函數(shù)主要是指定監(jiān)聽的ip和端口,并指定一個Handler
處理監(jiān)聽的請求掸绞。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
Handler
也是一個接口類型泵三,但是Handler
里的ServeHTTP(ResponseWriter, *Request)
的參數(shù)類型是不是很熟悉,跟我們上面路由回調(diào)函數(shù)一模一樣衔掸,這兩者之間是不是有啥不可告人的事呢烫幕?
Server
涉及到http服務(wù)器的處理細(xì)節(jié),我們先不細(xì)究敞映,主要還是看看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)
}
上面是Server
處理http請求的一個關(guān)鍵代碼,可以看到如果handler
為空振愿,則會使用DefaultServeMux
來代替捷犹,那么我們下面就來仔細(xì)探究DefaultServeMux
弛饭。請看代碼:
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // 這里就是路由映射表
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
// 這里就是路由以及對應(yīng)的路由回調(diào)函數(shù)
type muxEntry struct {
h Handler
pattern string
}
// ServeMux的一些關(guān)鍵函數(shù)
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 判斷路由不能為空,以及不能重復(fù)設(shè)置
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
// 將路由及回調(diào)函數(shù)保存到路由映射表中
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
// 這個函數(shù)的作用萍歉,就是請求url(路由)匹配一個handler(路由回調(diào)函數(shù))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// 不支持 http.method == CONNECT
if r.Method == "CONNECT" {
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
// 獲取host和path
// 比如url="https://www.baidu.com:8080/hello?a=b
// 則 host = www.baidu.com
// path = /hello
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
// 打個比方侣颂,如過請求 https://baidu.com/aaa 沒有匹配成功
// 則重定向到 https://baidu.com/aaa/
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
// 這里的path跟URL里的path不一致,則重定向到URL里的path去
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
// 其實(shí)這個才是真正的路由匹配函數(shù)枪孩,注意它跟Handler首字母大小寫不同
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
// 這里大致可以看出憔晒。先使用完全匹配,然后在按照順序使用前綴匹配
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
// 這里就是上面Server部分所說蔑舞,當(dāng)一個http請求進(jìn)來時拒担,默認(rèn)使用DefaultServeMux
// 從而進(jìn)入到這個函數(shù)來。
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
}
// 根據(jù)路由規(guī)則獲取對應(yīng)路由回調(diào)函數(shù)
h, _ := mux.Handler(r)
// 執(zhí)行路由回調(diào)函數(shù)
h.ServeHTTP(w, r)
}
另一種寫法
上面里的main
函數(shù)可以換成下面寫法攻询,兩者是等價的
func main() {
server := http.ServeMux{}
server.HandleFunc("/echo", echo) // 注冊路由以及回調(diào)函數(shù)
// 監(jiān)聽ip和端口从撼,第二個參數(shù)為nil,則使用默認(rèn)的DefaultServeMux來處理請求
if err := http.ListenAndServe("127.0.0.1:10001", &server); err != nil {
panic(err)
}
}
進(jìn)一步思考
通過上面的源碼钧栖,我可以可以發(fā)現(xiàn)go語言更多的是定義http處理相關(guān)接口低零,比如上面的ServeMux
,它負(fù)責(zé)將路由映射到處理函數(shù)中桐经。實(shí)際上我們完成可以自己寫一個,只要實(shí)現(xiàn)相關(guān)的接口就ok浙滤。
這里是不是可以看出go語言的一些哲學(xué)呢阴挣?比如提供接口,讓用戶去決定具體實(shí)現(xiàn)纺腊?(原諒我知識面不夠畔咧,不知道怎么說...)
最后我們想一想,利用這些接口揖膜,我們是不是可以開發(fā)一個簡單的go框架呢誓沸,比如說最簡單的MVC模型框架?我倒是挺想試試的壹粟!