Go classic library uses language analysis - a high-performance scalable HTTP routing httprouter (reprint)

Reprinted from snow ruthless blog

A Go Language (golang) The great advantage is that it is easy to develop a network of back-office services, and fast performance, high efficiency. In the development of HTTP back-end network application services, we need to deal with a lot of HTTP requests to access, such as the common API services, we have to deal with a lot of HTTP requests, then the information processing is returned to the user. For these needs, Golang provides built-in net/httppackage to help us to handle these HTTP requests, so that we can develop a more convenient HTTP service.

net/http

func main() {
    http.HandleFunc("/",Index)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

func Index(w http.ResponseWriter, r *http.Request){
    fmt.Fprint(w,"Blog:www.flysnow.org\nwechat:flysnow_org")
}

This is a net/httppackage in a classic HTTP service implementation, we run the open http://localhost:8080, you can see the following information:

Blog:www.flysnow.org
wechat:flysnow_org

The key is to show our http.HandleFuncfunction, we registered a path through the function /handler Index, all will see the information displayed above. So this http.HandleFunchow he registered Indexa function of it? Let's look at the source code for this function.

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

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

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

See the above source code, there is a default DefaultServeMuxroute, this DefaultServeMuxtype ServeMux, we registered path function information is stored ServeMuxin the mfield so that the processing time using HTTP requests.

DefaultServeMux.HandleFuncFunction will eventually call the ServeMux.Handlefunction.

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    //省略加锁和判断代码

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    //把我们注册的路径和相应的处理函数存入了m字段中
    mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

This time should understand that the path and registered the corresponding processing function is stored into the mfield.

Since the registration into the appropriate information, then when dealing with HTTP requests, we can use. Go language net/httpmore detailed analysis of the underlying details will not, as long as we know when the HTTP request, will call processing Handlerinterface ServeHTTPmethod, which ServeMuxjust achieved Handler.

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    //省略一些无关代码
    
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

The code above mux.Handlerwill get us to the registration Indexfunction, and then execute it, specific mux.Handlerdetailed analysis is no longer achieved, we can look at their own source code.

Now we can summarize net/httpfor packet HTTPprocessing of the request.

HTTP请求->ServeHTTP函数->ServeMux的Handler方法->Index函数

This is a request for the entire processing chain, now we understand the net/httpprinciple in the HTTP request.

Less than net / http of

We're using the built-in net/httpprocessing of HTTP request when the default path, you will find a lot of shortcomings, such as:

1. 不能单独的对请求方法(POST,GET等)注册特定的处理函数
2. 不支持Path变量参数
3. 不能自动对Path进行校准
4. 性能一般
5. 扩展性不足
6. ……

So how to solve the above problem? One way is to write your own HTTP request routing process, because the source code from the above we know that net/httpuse the default path.

//这个是我们启动HTTP服务的函数,最后一个handler参数是nil
http.ListenAndServe(":8080", nil)

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    
    //这个判断成立,因为我们传递的是nil
    if handler == nil {
        handler = DefaultServeMux
    }
    //省略了一些代码
    handler.ServeHTTP(rw, req)
}

Through the above analysis of the code, by ourselves in http.ListenAndServestarting a HTTP service function when the final handlervalue is nil, nil so the above decision is affirmative, the default is to use the route DefaultServeMux.

Now we know how to use their own definition of the route, that is, to http.ListenAndServethe last argument handlerpassed a custom route, such as:

type CustomMux struct {

}

func (cm *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w,"Blog:www.flysnow.org\nwechat:flysnow_org")
}

func main() {
    log.Fatal(http.ListenAndServe(":8080", &CustomMux{}))
}

This custom CustomMuxis our route, and it shows the use of net/httpexamples demonstrate the same function.

We now change under the code, only GETmethod appears above information.

func (cm *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        fmt.Fprint(w,"Blog:www.flysnow.org\nwechat:flysnow_org")
    } else {
        fmt.Fprint(w,"bad http method request")
    }
}

Only need to change under the ServeHTTPprocessing logic to process, and now we can change different request methods to try, it will display different content.

This is the custom, we can expand ServeHTTPto add the implementation of any of the features we want, including the above listed sections of net/httpthe shortage can be solved, but we do not have too much trouble, because the open source Daniel has helped us to do these things it is github

httprouter

httprouter is a high performance, scalable HTTP routing, we enumerated above net/httpdeficiencies default route, are implemented httprouter, let's use an example, under httprouter understanding of this powerful HTTP routing.

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
    "log"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprintf(w, "Blog:%s \nWechat:%s","www.flysnow.org","flysnow_org")
}
func main() {
    router := httprouter.New()
    router.GET("/", Index)

    log.Fatal(http.ListenAndServe(":8080", router))
}

This example, realized in GETthe request /when the route, displays the following information:

Blog:www.flysnow.org
wechat:flysnow_org

In this example, first by httprouter.New()generating a *Routerroute pointer, then GETthe method is adapted to register a /path Indexfunction, and finally *Routerpassed as parameters to ListenAndServestart the HTTP service to function.

In fact, more than a GETmethod, httprouter all HTTP Method provides a quick way to use, only need to call the corresponding method is.

func (r *Router) GET(path string, handle Handle) {
    r.Handle("GET", path, handle)
}

func (r *Router) HEAD(path string, handle Handle) {
    r.Handle("HEAD", path, handle)
}

func (r *Router) OPTIONS(path string, handle Handle) {
    r.Handle("OPTIONS", path, handle)
}

func (r *Router) POST(path string, handle Handle) {
    r.Handle("POST", path, handle)
}

func (r *Router) PUT(path string, handle Handle) {
    r.Handle("PUT", path, handle)
}

func (r *Router) PATCH(path string, handle Handle) {
    r.Handle("PATCH", path, handle)
}

func (r *Router) DELETE(path string, handle Handle) {
    r.Handle("DELETE", path, handle)
}

These methods are httprouter support, we can be very flexible as necessary, using the corresponding method, this would resolve the net/httpproblem of the default route.

httprouter named parameters

Modern API, basically Restful API, supports named parameters httprouter provided, can easily help us develop Restful API. For example, we designed the API /user/flysnow, so that a URL, you can view flysnowthe user's information, if you want to view other users, for example zhangsan, we only need to access the API /user/zhangsancan be.

Now we can see, this is actually a URL pattern matching, we can summarize it as /user/:name, this is a wild card, look at an example.

func UserInfo(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("/user/:name",UserInfo)

    log.Fatal(http.ListenAndServe(":8080", router))
}

When we run, type in the browser http://localhost:8080/user/flysnow, it will display hello, flysnow!.

By the above code example can be seen, the path parameter is :the beginning, followed by the variable name, for example :name, then UserInfothis handler, through httprouter.Paramsthe ByNameacquisition of the corresponding value.

:nameThis pattern matching, matching is accurate, while only a match, such as:

Pattern: /user/:name

 /user/gordon              匹配
 /user/you                 匹配
 /user/gordon/profile      不匹配
 /user/                    不匹配

Because httprouter This route is a single match, so when we use named parameters, must pay attention to whether there are routes in the routing and named other registration, matching the same path, such as /user/newthe route and /user/:namethat the conflict can not be registered at the same time .

Here the little mention of another httprouter wildcard pattern, is to :be replaced *, that is *name, all this is a matching pattern, not commonly used, such as:

Pattern: /user/*name

 /user/gordon              匹配
 /user/you                 匹配
 /user/gordon/profile      匹配
 /user/                    匹配

Because it is matching all *modes, so long as the *path in front of the match is a match, no matter how long the path, there are several layers are matched.

httprouter兼容http.Handler

By the example above, we should have found, GEThandle methods, is not familiar to us http.Handler, it is httprouter custom, compared to http.Handlermore support for a wildcard parameter.

type Handle func(http.ResponseWriter, *http.Request, Params)

Custom Handle, the sole purpose is to support wildcard parameter, if your HTTP service where some routes did not use wildcard parameter, you can use native http.Handler, httprouter is compatible with the support, which for us from net/httpthe way, upgrade to httprouter routing provides a convenient, efficient and a lot will.

func (r *Router) Handler(method, path string, handler http.Handler) {
    r.Handle(method, path,
        func(w http.ResponseWriter, req *http.Request, _ Params) {
            handler.ServeHTTP(w, req)
        },
    )
}

func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
    r.Handler(method, path, handler)
}

httprouter by Handlerand HandlerFunctwo functions, providing compatible http.Handler and http.HandlerFuncthe perfect support. From the above source code, we can see that the way to achieve relatively simple, it is to do a http.Handlerto httprouter.Handleconversion, to abandon support for wildcard parameter.

Handler processing chain

Thanks to http.Handlerthe model, we can put a different http.Handlercomposition of a processing chain, httprouter.Routeris achieved http.Handler, so it can be used as http.Handlerpart of the processing chain, such as and Negroni, Gorilla handlersboth libraries used in conjunction with explanations of both libraries, can be refer to my previous articles written.

The use of an official of the examples here, as Handler processing chain demonstration.

For example a plurality of different second level domain, different processing routes.

//一个新类型,用于存储域名对应的路由
type HostSwitch map[string]http.Handler

//实现http.Handler接口,进行不同域名的路由分发
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {

    //根据域名获取对应的Handler路由,然后调用处理(分发机制)
    if handler := hs[r.Host]; handler != nil {
        handler.ServeHTTP(w, r)
    } else {
        http.Error(w, "Forbidden", 403)
    }
}

func main() {
    //声明两个路由
    playRouter := httprouter.New()
    playRouter.GET("/", PlayIndex)
    
    toolRouter := httprouter.New()
    toolRouter.GET("/", ToolIndex)

    //分别用于处理不同的二级域名
    hs := make(HostSwitch)
    hs["play.flysnow.org:12345"] = playRouter
    hs["tool.flysnow.org:12345"] = toolRouter

    //HostSwitch实现了http.Handler,所以可以直接用
    log.Fatal(http.ListenAndServe(":12345", hs))
}

Above is a simple, for different domain names, example of using different routes, more detailed comments in the code, and not one by one to explain. In this example, HostSwitchand httprouter.Routerthe two http.Handlerwill form a http.Handlerprocessing chain.

httprouter static files

httprouter provides a very convenient static files, you can put a directory hosted on the server for access.

router.ServeFiles("/static/*filepath",http.Dir("./"))

Only need this one to the core code, this is the current directory hosted on the server for access, access path /static.

Use ServeFilesshould be noted that the first parameter path must be to /*filepath, because we want to get the path information to be accessed.

func (r *Router) ServeFiles(path string, root http.FileSystem) {
    if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
        panic("path must end with /*filepath in path '" + path + "'")
    }

    fileServer := http.FileServer(root)

    r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {
        req.URL.Path = ps.ByName("filepath")
        fileServer.ServeHTTP(w, req)
    })
}

This is a source code implementation, we found finally a GETrequest for service by http.FileServerthe filepathdisplayed contents of the path (if the path is listed in the directory directory file; if the path is a file, display content).

Through the above source code, we can also know that *filepaththis is a wildcard to get the file path to put ask, so to comply with reservations, or will panic.

httprouter exception caught

Few routing support this feature, httprouter allow a user, is provided PanicHandlerfor processing the HTTP request panic occurred.

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    panic("故意抛出的异常")
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.PanicHandler = func(w http.ResponseWriter, r *http.Request, v interface{}) {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "error:%s",v)
    }

    log.Fatal(http.ListenAndServe(":8080", router))
}

Demo example we set by router.PanicHandlerto deal with panic occurred approach is to print out the exception information. Then deliberately Indexthrow a painc function, then we run the test, you will see exception information.

This is a very good way, allows us to painc unified process, because they can not miss the panic affect users.

summary

httprouter there are many small useful features, such as processing of 404, set by us Router.NotFoundto achieve, we take a look at Routerthis array structure, you can find more useful features.

type Router struct {
    //是否通过重定向,给路径自定加斜杠
    RedirectTrailingSlash bool
    //是否通过重定向,自动修复路径,比如双斜杠等自动修复为单斜杠
    RedirectFixedPath bool
    //是否检测当前请求的方法被允许
    HandleMethodNotAllowed bool
    //是否自定答复OPTION请求
    HandleOPTIONS bool
    //404默认处理
    NotFound http.Handler
    //不被允许的方法默认处理
    MethodNotAllowed http.Handler
    //异常统一处理
    PanicHandler func(http.ResponseWriter, *http.Request, interface{})
}

These fields are exported (export), we can directly set to achieve our objective.

httprouter is a high-performance, low-memory footprint of the route, which uses the radix tree to achieve storage and matching to find, so efficiency is very high, very low memory footprint. About radix tree you can see the relevant information.

httprouter since realized http.Handler, so scalability is very good, and you can use other libraries, middleware combining custom gin this web framework is used httprouter.

Guess you like

Origin www.cnblogs.com/ivan-blog/p/12377144.html