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/http
package 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/http
package 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.HandleFunc
function, we registered a path through the function /
handler Index
, all will see the information displayed above. So this http.HandleFunc
how he registered Index
a 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 DefaultServeMux
route, this DefaultServeMux
type ServeMux
, we registered path function information is stored ServeMux
in the m
field so that the processing time using HTTP requests.
DefaultServeMux.HandleFunc
Function will eventually call the ServeMux.Handle
function.
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 m
field.
Since the registration into the appropriate information, then when dealing with HTTP requests, we can use. Go language net/http
more detailed analysis of the underlying details will not, as long as we know when the HTTP request, will call processing Handler
interface ServeHTTP
method, which ServeMux
just 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.Handler
will get us to the registration Index
function, and then execute it, specific mux.Handler
detailed analysis is no longer achieved, we can look at their own source code.
Now we can summarize net/http
for packet HTTP
processing of the request.
HTTP请求->ServeHTTP函数->ServeMux的Handler方法->Index函数
This is a request for the entire processing chain, now we understand the net/http
principle in the HTTP request.
Less than net / http of
We're using the built-in net/http
processing 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/http
use 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.ListenAndServe
starting a HTTP service function when the final handler
value 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.ListenAndServe
the last argument handler
passed 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 CustomMux
is our route, and it shows the use of net/http
examples demonstrate the same function.
We now change under the code, only GET
method 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 ServeHTTP
processing 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 ServeHTTP
to add the implementation of any of the features we want, including the above listed sections of net/http
the 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/http
deficiencies 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 GET
the request /
when the route, displays the following information:
Blog:www.flysnow.org
wechat:flysnow_org
In this example, first by httprouter.New()
generating a *Router
route pointer, then GET
the method is adapted to register a /
path Index
function, and finally *Router
passed as parameters to ListenAndServe
start the HTTP service to function.
In fact, more than a GET
method, 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/http
problem 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 flysnow
the user's information, if you want to view other users, for example zhangsan
, we only need to access the API /user/zhangsan
can 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 UserInfo
this handler, through httprouter.Params
the ByName
acquisition of the corresponding value.
:name
This 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/new
the route and /user/:name
that 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, GET
handle methods, is not familiar to us http.Handler
, it is httprouter custom, compared to http.Handler
more 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/http
the 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 Handler
and HandlerFunc
two functions, providing compatible http.Handle
r and http.HandlerFunc
the perfect support. From the above source code, we can see that the way to achieve relatively simple, it is to do a http.Handler
to httprouter.Handle
conversion, to abandon support for wildcard parameter.
Handler processing chain
Thanks to http.Handler
the model, we can put a different http.Handler
composition of a processing chain, httprouter.Router
is achieved http.Handler
, so it can be used as http.Handler
part of the processing chain, such as and Negroni
, Gorilla handlers
both 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, HostSwitch
and httprouter.Router
the two http.Handler
will form a http.Handler
processing 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 ServeFiles
should 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 GET
request for service by http.FileServer
the filepath
displayed 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 *filepath
this 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 PanicHandler
for 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.PanicHandler
to deal with panic occurred approach is to print out the exception information. Then deliberately Index
throw 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.NotFound
to achieve, we take a look at Router
this 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.