golang中使用原生的http包編寫一個web服務

在golang中實現一個簡單的web服務很簡單,代碼如下:

package main
import (
    "net/http"
    "fmt"
)
func main()  {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":9090", nil)
}
func hello(w http.ResponseWriter, r *http.Request)  {
    fmt.Fprintf(w, "hello world")
}

首先我們是通過ListenAndServe來監聽本地端口的,之後ListenAndServe將收到的新建一個Response連同收到的Request
作爲參數調用ServeMux結構體的ServeHTTP(省略了中間過程).ServeHTTP將Request作爲參數調用
Handler函數,Handler的返回值爲一個Handler類型的接口,ServeHTTP會調用接口實現的ServeHTTP處理Response.

如果Request.URL.Path中有不合法的內容,則調用cleanPath清理,隨後將Request.Host以及清理後的
內容傳入handler函數,隨後返回一個RedirectHandler以及handler所返回的路徑。如果Request.URL.Path合法,那麼
直接調用handler,返回值與handler返回值相同。

handler中通過判斷ServeMux.hosts來決定是否實現pattern = r.Host + r.URL.Path.之後將pattern作爲參數調用match,並將
match的返回值返回.

match的判別方式比較”有趣”,它雖然沒實現爲樹形結構(只是用了映射),但是搜索的方法就是樹形,因爲URL路徑就是個樹形.它按照樹的根節點
與子節點的關係進行判斷,譬如路徑”/home/select/usercourse”,match在匹配的時候會首先匹配到”/”(假如我們註冊了),其次是”/home”,
之後逐層匹配下來,假如我們沒註冊過”/home/select/usercourse”,但是註冊了”/home/select/”,那麼match就會匹配到這一層.然後返回
“/home/select/”的Handler以及url(pattern).match函數的匹配規則實現在pathMatch

ServerMux結構體

type ServeMux struct {
        mu    sync.RWMutex
        m     map[string]muxEntry
        hosts bool // whether any patterns contain hostnames
    }

type muxEntry struct {
        explicit bool
        h        Handler
        pattern  string
    }

NewServeMux()

DefaultServeMux變量就是直接調用這個函數,那麼這個函數只make了一個變量m,也就是爲map分配了空間,在golang的語法中,結構體裏未申明的變量也是存在的(即分配了內存)

pathMath()

這個函數是ServeMux用來匹配路勁的主要函數,所以看一下策略還是很重要的,函數中pattern是我們註冊的路勁,path是用戶請求的路徑

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
    }
    return len(path) >= n && path[0:n] == pattern
}

如果我們掛在的路徑不是以/結尾的,那麼就直接判斷兩個參數是否相同,如果是以/結尾的,只要path的路徑包含pattern那麼久被判定是匹配,也就是說,如若我註冊了/home/select/,那麼/home/select/hello也會被定位到/home/select/上面,掛在的HanderFunc上面,這樣做相當於爲路徑設置了一個index,不符合規則的URL都會被Redirct到這個index上。
* ServeMux.Handler()
註釋寫的很清楚,這個函數就是處理URL,然後調用*ServeMux.handler().首先調用cleanPath清理請求URL中的不合法內容。如果存在不合法內容,
則將清理過的URL交由*ServeMux.handler()處理並獲得匹配到的pattern,然後修改url.Path的內容並調用RedirectHandler.
如果內容合法,則直接調用*ServeMux.handler()並返回結果

  • ServeMux.handler()
    調用ServeMux.match()(封裝了pathMatch函數)來獲得匹配到的Handler以及對應pattern,如果ServeMux.hosts==true,那麼
    傳入的參數爲host + path,如果找不到的話,調用NotFoundHandler函數,並將其結果返回.

  • ServeMux.Handle()
    Handle函數是用來註冊路徑與處理過程的.如果該路徑已經存在了一個用戶註冊的Handler則會panic(意思就是說不支持覆蓋).判別了合法參數以後就將
    pattern作爲key,新建一個muxEntry類型變量作爲value加入到map中。

if pattern[0] != ‘/’ {
mux.hosts = true
}
這是這個函數中比較有意思的一個部分,通過這裏我們可以看到如果註冊路徑的時候並不是以’/’開頭的,那麼ServeMux就會開啓hosts,然後會在
請求到達的時候將URL.Host和URL.Path連接在一起放入match中尋找,具體信息請看這裏

接下來是關於路徑的處理,也就是關於”/home”與”/home/”的區別.我們先來看看作者怎麼說

// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.

如果路徑的末尾是以’/’結尾並且該路徑去掉末尾的’/’以後並沒有被註冊.那麼將會去掉’/’並且爲其綁定一個Redirect到現在的路徑.
我自己寫起來都覺得繞,舉個例子就清楚了.
我註冊了一個路徑”/home/”,但是沒有註冊”/home”,那麼如果用戶訪問了”/home”會發生什麼呢?是的,會被Redirect到”/home/”.
需要注意的是,這裏的muxEntry中的explicit沒有填,也就是說是false,那麼即是可以覆蓋的.

  • ServeMux.ServeHTTP()
    ServeHTTP會檢測非法的URI(* )
    如果通過檢測就會調用自身的Handler()來返回註冊的Handler,隨後調用Handler的ServeHTTP方法

在http.ListenAndServe中的第二個參數傳遞處理hander可以自定義,這樣就可以自己實現一個路由,在處理函數中必須實現ServeHTTP函數,這個函數在handler中直接執行。例子如下:

package routes

import (
    "encoding/json"
    "encoding/xml"
    "io/ioutil"
    "net/http"
    "net/url"
    "path/filepath"
    "regexp"
    "strconv"
    "strings"
)

const (
    CONNECT = "CONNECT"
    DELETE  = "DELETE"
    GET     = "GET"
    HEAD    = "HEAD"
    OPTIONS = "OPTIONS"
    PATCH   = "PATCH"
    POST    = "POST"
    PUT     = "PUT"
    TRACE   = "TRACE"
)

//commonly used mime-types
const (
    applicationJson = "application/json"
    applicationXml  = "application/xml"
    textXml         = "text/xml"
)

type route struct {
    method  string
    regex   *regexp.Regexp
    params  map[int]string
    handler http.HandlerFunc
}

type RouteMux struct {
    routes  []*route
    filters []http.HandlerFunc
}

func New() *RouteMux {
    return &RouteMux{}
}

// Get adds a new Route for GET requests.
func (m *RouteMux) Get(pattern string, handler http.HandlerFunc) {
    m.AddRoute(GET, pattern, handler)
}

// Put adds a new Route for PUT requests.
func (m *RouteMux) Put(pattern string, handler http.HandlerFunc) {
    m.AddRoute(PUT, pattern, handler)
}

// Del adds a new Route for DELETE requests.
func (m *RouteMux) Del(pattern string, handler http.HandlerFunc) {
    m.AddRoute(DELETE, pattern, handler)
}

// Patch adds a new Route for PATCH requests.
func (m *RouteMux) Patch(pattern string, handler http.HandlerFunc) {
    m.AddRoute(PATCH, pattern, handler)
}

// Post adds a new Route for POST requests.
func (m *RouteMux) Post(pattern string, handler http.HandlerFunc) {
    m.AddRoute(POST, pattern, handler)
}

// Adds a new Route for Static http requests. Serves
// static files from the specified directory
func (m *RouteMux) Static(pattern string, dir string) {
    //append a regex to the param to match everything
    // that comes after the prefix
    pattern = pattern + "(.+)"
    m.AddRoute(GET, pattern, func(w http.ResponseWriter, r *http.Request) {
        path := filepath.Clean(r.URL.Path)
        path = filepath.Join(dir, path)
        http.ServeFile(w, r, path)
    })
}

// Adds a new Route to the Handler
func (m *RouteMux) AddRoute(method string, pattern string, handler http.HandlerFunc) {

    //split the url into sections
    parts := strings.Split(pattern, "/")

    //find params that start with ":"
    //replace with regular expressions
    j := 0
    params := make(map[int]string)
    for i, part := range parts {
        if strings.HasPrefix(part, ":") {
            expr := "([^/]+)"
            //a user may choose to override the defult expression
            // similar to expressjs: ‘/user/:id([0-9]+)’
            if index := strings.Index(part, "("); index != -1 {
                expr = part[index:]
                part = part[:index]
            }
            params[j] = part
            parts[i] = expr
            j++
        }
    }

    //recreate the url pattern, with parameters replaced
    //by regular expressions. then compile the regex
    pattern = strings.Join(parts, "/")
    regex, regexErr := regexp.Compile(pattern)
    if regexErr != nil {
        //TODO add error handling here to avoid panic
        panic(regexErr)
        return
    }

    //now create the Route
    route := &route{}
    route.method = method
    route.regex = regex
    route.handler = handler
    route.params = params

    //and finally append to the list of Routes
    m.routes = append(m.routes, route)
}

// Filter adds the middleware filter.
func (m *RouteMux) Filter(filter http.HandlerFunc) {
    m.filters = append(m.filters, filter)
}

// FilterParam adds the middleware filter iff the REST URL parameter exists.
func (m *RouteMux) FilterParam(param string, filter http.HandlerFunc) {
    if !strings.HasPrefix(param,":") {
        param = ":"+param
    }

    m.Filter(func(w http.ResponseWriter, r *http.Request) {
        p := r.URL.Query().Get(param)
        if len(p) > 0 { filter(w, r) }
    })
}

// Required by http.Handler interface. This method is invoked by the
// http server and will handle all page routing
func (m *RouteMux) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

    requestPath := r.URL.Path

    //wrap the response writer, in our custom interface
    w := &responseWriter{writer: rw}

    //find a matching Route
    for _, route := range m.routes {

        //if the methods don't match, skip this handler
        //i.e if request.Method is 'PUT' Route.Method must be 'PUT'
        if r.Method != route.method {
            continue
        }

        //check if Route pattern matches url
        if !route.regex.MatchString(requestPath) {
            continue
        }

        //get submatches (params)
        matches := route.regex.FindStringSubmatch(requestPath)

        //double check that the Route matches the URL pattern.
        if len(matches[0]) != len(requestPath) {
            continue
        }

        if len(route.params) > 0 {
            //add url parameters to the query param map
            values := r.URL.Query()
            for i, match := range matches[1:] {
                values.Add(route.params[i], match)
            }

            //reassemble query params and add to RawQuery
            r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
            //r.URL.RawQuery = url.Values(values).Encode()
        }

        //execute middleware filters
        for _, filter := range m.filters {
            filter(w, r)
            if w.started {
                return
            }
        }

        //Invoke the request handler
        route.handler(w, r)
        break
    }

    //if no matches to url, throw a not found exception
    if w.started == false {
        http.NotFound(w, r)
    }
}

// -----------------------------------------------------------------------------
// Simple wrapper around a ResponseWriter

// responseWriter is a wrapper for the http.ResponseWriter
// to track if response was written to. It also allows us
// to automatically set certain headers, such as Content-Type,
// Access-Control-Allow-Origin, etc.
type responseWriter struct {
    writer  http.ResponseWriter
    started bool
    status  int
}

// Header returns the header map that will be sent by WriteHeader.
func (w *responseWriter) Header() http.Header {
    return w.writer.Header()
}

// Write writes the data to the connection as part of an HTTP reply,
// and sets `started` to true
func (w *responseWriter) Write(p []byte) (int, error) {
    w.started = true
    return w.writer.Write(p)
}

// WriteHeader sends an HTTP response header with status code,
// and sets `started` to true
func (w *responseWriter) WriteHeader(code int) {
    w.status = code
    w.started = true
    w.writer.WriteHeader(code)
}

// -----------------------------------------------------------------------------
// Below are helper functions to replace boilerplate
// code that serializes resources and writes to the
// http response.

// ServeJson replies to the request with a JSON
// representation of resource v.
func ServeJson(w http.ResponseWriter, v interface{}) {
    content, err := json.MarshalIndent(v, "", "  ")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Length", strconv.Itoa(len(content)))
    w.Header().Set("Content-Type", applicationJson)
    w.Write(content)
}

// ReadJson will parses the JSON-encoded data in the http
// Request object and stores the result in the value
// pointed to by v.
func ReadJson(r *http.Request, v interface{}) error {
    body, err := ioutil.ReadAll(r.Body)
    r.Body.Close()
    if err != nil {
        return err
    }
    return json.Unmarshal(body, v)
}

// ServeXml replies to the request with an XML
// representation of resource v.
func ServeXml(w http.ResponseWriter, v interface{}) {
    content, err := xml.Marshal(v)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Length", strconv.Itoa(len(content)))
    w.Header().Set("Content-Type", "text/xml; charset=utf-8")
    w.Write(content)
}

// ReadXml will parses the XML-encoded data in the http
// Request object and stores the result in the value
// pointed to by v.
func ReadXml(r *http.Request, v interface{}) error {
    body, err := ioutil.ReadAll(r.Body)
    r.Body.Close()
    if err != nil {
        return err
    }
    return xml.Unmarshal(body, v)
}

// ServeFormatted replies to the request with
// a formatted representation of resource v, in the
// format requested by the client specified in the
// Accept header.
func ServeFormatted(w http.ResponseWriter, r *http.Request, v interface{}) {
    accept := r.Header.Get("Accept")
    switch accept {
    case applicationJson:
        ServeJson(w, v)
    case applicationXml, textXml:
        ServeXml(w, v)
    default:
        ServeJson(w, v)
    }

    return
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章