Go语言Handler详细说明
作者:骏马金龙 发布时间:2024-04-27 15:32:50
Multiplexer根据URL将请求路由给指定的Handler。Handler用于处理请求并给予响应。更严格地说,用来读取请求体、并将请求对应的响应字段(respones header)写入ResponseWriter中,然后返回。
什么是Handler
什么是Handler。它是一个接口,定义在net/http/server.go中:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
也就是说,实现了ServerHTTP方法的都是Handler。注意ServerHTTP方法的参数:http.ResponesWriter接口和Request指针。
在Handler的注释中,给出了几点主要的说明:
Handler用于响应一个HTTP request
接口方法ServerHTTP应该用来将response header和需要响应的数据写入到ResponseWriter中,然后返回。返回意味着这个请求已经处理结束,不能再使用这个ResponseWriter、不能再从Request.Body中读取数据,不能并发调用已完成的ServerHTTP方法
handler应该先读取Request.Body,然后再写ResponseWriter。只要开始向ResponseWriter写数据后,就不能再从Request.Body中读取数据
handler只能用来读取request的body,不能修改已取得的Request(因为它的参数Request是指针类型的)
ResponseWriter接口说明
再看看ResponseWriter接口的定义:
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
//
// A ResponseWriter may not be used after the Handler.ServeHTTP method
// has returned.
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
注释中已经说明,ResponseWriter接口的作用是用于构造HTTP response。且明确指定了Handler.ServerHTTP方法返回以后就不能再使用ResponseWriter了。
这个接口有3个方法:
Header()方法用来构造响应Header,它返回一个Header对象,这个Header对象稍后将被WriterHeader()响应出去。Header类型是一个map类型的结构,字段名为key、字段值为value:
type Header map[string][]string
Write()方法用于向网络连接中写响应数据。
WriteHeader()方法将给定的响应状态码和响应Header一起发送出去。
很显然,ResponseWriter的作用是构造响应header,并将响应header和响应数据通过网络链接发送给客户端。
再看ListenAndServe()
在启动go http自带的web服务时,调用了函数ListenAndServe()。这个函数的定义如下:
func ListenAndServe(addr string, handler Handler) error
该函数有两个参数,第一个参数是自带的web监听地址和端口,第二个参数是Handler,用来处理每个接进来的http request,但一般第二个参数设置为nil,表示调用默认的Multiplexer:DefaultServeMux。这个默认的ServeMux唯一的作用,是将请求根据URL路由给对应的handler进行处理。
var DefaultServeMux = &defaultServeMux
这里有两个问题:
(1).第二个参数为什么建议设置为nil
(2).设置为nil后,DefaultServeMux是请求的路由器,它为什么可以充当一个handler
先看第二个问题,很简单,因为ServeMux类型定义了ServeHTTP()方法,它实现了Handler接口:
type ServeMux struct {
// Has 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)
前面说过,只要实现了ServerHTTP()方法的类型,就是一个Handler。而DefaultServeMux是默认的ServeMux,所以它是一个Handler。
关于第一个问题,看一个示例就知道了。
package main
import (
"fmt"
"net/http"
)
// MyHandler实现Handler接口
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!\n")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler, // 以&handler作为第二个参数
}
server.ListenAndServe()
}
上面的示例中定义了一个handler,它实现的ServeHTTP()方法只有一个作用,输出Hello World!
。并且将这个handler作为ListenAndServe()的第二个参数。
注意,上面以&handler
作为参数而非handler
,因为此处MyHandler中实现的ServerHTTP()方法的receiver是指针类型的,所以MyHandler的实例对象也必须是指针类型的,如此才能实现Handler接口。
启动这个web服务后,以不同的URL去访问它,将总是得到完全相同的响应结果:
很显然,当handler作为ListenAndServe()的第二个参数时,任意请求都会使用这个唯一的handler进行处理。
所以,建议将第二个参数设置为nil(或者上面的Serve Struct不指定Handler字段),它表示调用默认的DefaultServeMux作为handler,使得每个访问请求都会调用这个特殊的handler,而这个handler的作用是将请求根据url路由给不同的handler。
另外需要注意的是,http包中提供的Handle()和HandleFunc()函数其实是DefaultServeMux.XXX的封装,所以直接调用http.Handle()和http.HandleFunc()实际上是在调用DefaultServeMux.Handle()和DefaultServeMux.HandleFunc()。
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
使用DefaultServeMux后的Handler示例
下面是使用了DefaultServeMux的示例。
创建了两个handler,一个handler用于对应/hello
,该handler用于输出Hello
,另一个handler用于对应world
,该handler用于输出World
:
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct{}
type WorldHandler struct{}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello\n")
}
func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World\n")
}
func main() {
helloHandler := HelloHandler{}
worldHandler := WorldHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.Handle("/hello",&helloHandler)
http.Handle("/world",&worldHandler)
server.ListenAndServe()
}
下面是访问的结果:
HandleFunc是什么
除了使用Handle处理http请求,也能使用HandleFunc()处理。
先看一个使用HandleFunc()处理请求的示例,示例的效果和前文是一样的。
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello\n")
}
func world(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World\n")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", hello)
http.HandleFunc("/world", world)
server.ListenAndServe()
}
下面是访问的结果:
Go有一个函数HandleFunc(),它表示使用第二个参数的函数作为handler,处理匹配到的url路径请求。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
不难看出,HandleFunc()使得我们可以直接使用一个函数作为handler,而不需要自定义一个实现Handler接口的类型。正如上面的示例中,我们没有定义Handler类型,也没有去实现ServeHTTP()方法,而是直接定义函数,并将其作为handler。
换句话说,HandleFunc()使得我们可以更简便地为某些url路径注册handler。但是,使用HandleFunc()毕竟是图简便,有时候不得不使用Handle(),比如我们确定要定义一个type。
Handle()、HandleFunc()和Handler、HandlerFunc的关系
说实话,一开始感觉挺乱的。
Handle()和HandleFunc()是函数,用来给url绑定handler。Handler和HandlerFunc类型,用来处理请求。
看Handle()、HandleFunc()以及Handler、HandlerFunc的定义就已经很清晰了:
func Handle(pattern string, handler Handler) {}
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
Handle()和HandleFunc()都是为某个url路径模式绑定一个对应的handler,只不过HandleFunc()是直接使用函数作为handler,而Handle()是使用Handler类型的实例作为handler。
Handler接口的实例都实现了ServeHTTP()方法,都用来处理请求并响应给客户端。
HandlerFunc类型不是接口,但它有一个方法ServeHTTP(),也就是说HandlerFunc其实也是一种Handler。
因为HandlerFunc是类型,只要某个函数的签名是func(ResponseWriter, *Request)
,它就是HandlerFunc类型的一个实例。另一方面,这个类型的实例(可能是参数、可能是返回值类型)可以和某个签名为func(ResponseWriter, *Request)
的函数进行互相赋值。这个过程可能很隐式,但确实经常出现相关的用法。
例如:
// 一个函数类型的handler
func myhf(ResponseWriter, *Request){}
// 以HandlerFunc类型作为参数类型
func a(hf HandlerFunc){}
// 所以,可以将myhf作为a()的参数
a(myhf)
实际上,可以使用HandlerFunc()进行转换。例如有一个函数world(),它的参数是合理的,使用HandlerFunc(world)表示将其转换为一个Handler。这个转换、适应在后面会经常用到。
例如:
// 两个函数
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello\n")
}
func world(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World\n")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
// 第一个使用HandleFunc()为路径注册hello()函数handler
// 第二个使用Handle()为路径注册转换后的handler
http.HandleFunc("/hello", hello)
http.Handle("/world", http.HandlerFunc(world))
server.ListenAndServe()
}
上面的示例中,Handle()函数的第二个参数要求的是Handler类型,使用http.HandlerFunc(world)
就将函数world()转换成了Handler类型的一个实例。
链式handler
handler是用来处理http请求的,处理过程可能会很简单,也可能会很复杂。复杂的情况下,可能无法使用一个单独的handler来完成工作,毕竟handler只是一个函数。尽管我们可以直接在这个函数中调用其它函数。
很经常地,可能handler中需要嵌套其它handler,甚至多层嵌套,这就是链式handler。
由于Handle()或HandleFunc()注册的时候需要指定参数类型,所以handler嵌套的时候,也要关注handler的参数类型以及返回类型。看下面示例就会明白参数类型和返回类型是怎么要求的。
HandleFunc()的嵌套示例
代码如下:
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!\n")
}
func log(hf http.HandlerFunc) http.HandlerFunc {
count := 0
return func(w http.ResponseWriter, r *http.Request) {
count++
fmt.Printf("Handler Function called %d times\n", count)
hf(w, r)
}
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", log(hello))
server.ListenAndServe()
}
多次访问http://127.0.0.1:8080/hello
,将在浏览器中输出"Hello World!",但同时会在运行这个go程序的终端上多次输出以下内容:
$ go run test.go
Handler Function called 1 times
Handler Function called 2 times
Handler Function called 3 times
Handler Function called 4 times
Handler Function called 5 times
上面的示例中,主要看下面两段代码:
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!\n")
}
func log(hf http.HandlerFunc) http.HandlerFunc {
count := 0
return func(w http.ResponseWriter, r *http.Request) {
count++
fmt.Printf("Handler Function called %d times\n", count)
hf(w, r)
}
}
hello()是一个普通的HandlerFunc类型函数,因为它的签名符合HandlerFunc类型,所以它是HandlerFunc类型的一个实例。而log()函数正是以HandlerFunc类型作为参数的,所以前面的示例代码中,将hello函数作为了log函数的参数:
http.HandleFunc("/hello", log(hello))
HandleFunc()的第二个参数要求是HandlerFunc类型的,所以log()的返回值是HandlerFunc类型。在log()中,使用匿名函数作为它的返回值,这里的这个匿名函数是一个闭包(因为引用了外层函数的变量hf和count)。这个匿名函数最后调用hf(w,r)
,由于hf是HandlerFunc类型的一个实例,所以可以如此调用。
上面体现了HandlerFunc嵌套时候关于参数以及返回值的一些细节。
上面的示例中还有一个细节需要引起注意:为什么每次访问时,上面的count都会记住之前的值并自增,而不是重置为0后自增。
之所以有这个疑问,可能是认为每次访问时,请求处理完成后handler就退出了,闭包虽然会记住外层函数的自由变量count,但也会因为处理完成后退出,导致每次访问都重置为0后自增。但实际上,handler是注册在给定路径上的,只要web服务没有退出,这个handler就一直不会退出,不会因为每次访问都重新注册handler。所以,闭包handler一直引用着hf和count这两个自由变量。
HandlerFunc嵌套Handler
将上面的HandlerFunc嵌套HandlerFunc修改一下,变成Handler嵌套HandlerFunc。
package main
import (
"fmt"
"net/http"
)
type MyHandler struct{}
func (wh *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!\n")
}
func log(h http.Handler) http.Handler {
count := 0
f := func(w http.ResponseWriter, r *http.Request) {
count++
fmt.Printf("Handler Function called %d times\n", count)
h.ServeHTTP(w, r)
}
return http.HandlerFunc(f)
}
func main() {
myHandler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.Handle("/hello", log(&myHandler))
server.ListenAndServe()
}
逻辑也很简单,无非是将HandlerFunc转换成Handler。
思考一下,Handler是否可以嵌套Handler,或者Handler嵌套HandlerFunc。可以,但是很不方便,因为ServeHTTP()方法限制了没法调用其它的Handler,除非定义的某个Handler是嵌套在某个Handler类型中的类型。
来源:https://www.cnblogs.com/f-ck-need-u/p/10020951.html


猜你喜欢
- doctest库就是一个测试用的标准库,从意义上我们可以看出是关于测试有关系的,基本上就是测试是否和自己想要的结果是否一致,经常能在编写文档
- 在《数据库原理》里面,对聚簇索引的解释是:聚簇索引的顺序就是数据的物理存储顺序,而对非聚簇索引的解释是:索引顺序与数据物理排列顺序无关。正式
- 话说网站首页是用.NET语言写的,而二级栏目页却是用ASP写的,然后再配上众多全手工的静态专题页,整个网站形成了一个大杂烩。想要在这大杂烩中
- 我们知道IE6是不支持透明的PNG的,这无疑限制了网页设计的发挥空间.然而整个互联网上解决这个IE6的透明PNG的方案也是多不胜数,从使用I
- 一、数据库的备份1、选择要备份的数据库“accountInfo”,点击鼠标右键 → 任务 → 备份2、在打开的“备份数据库 —account
- 用Python编写关于计算图形面积的代码实现,供大家参考,具体内容如下#寒假打卡28天第7天import mathclass Round()
- 我在工作的时候,在测试环境下使用的数据库跟生产环境的数据库不一致,当我们的测试环境下的数据库完成测试准备更新到生产环境上的数据库时候,需要准
- 描述int函数可以将一个指定进制的数字型字符串或者十进制数字转化为整形。语法int(object, base)名称说明备注object一个数
- SQL Server 阻止了对组件 'Ad Hoc Distributed&nbs
- 一、安装第三方模块首先要下载名为"pymssql"的模块,然后import该模块安装方法 :1.第一种方法:按win+r
- 从接触Python以来,一直都是采用 virtualenv 和 virtualenvwrapper 来管理不同项目的依赖环境,通过 work
- 本文实例讲述了js实现文本框宽度自适应文本宽度的方法。分享给大家供大家参考。具体如下:一个会随着输入文本框的字符多少而自动增加宽度的JS代码
- 本文实例讲述了PHP实现打包下载文件的方法。分享给大家供大家参考,具体如下:/*** 下载文件* @param $img* @return
- 阐述写SQL时本想通过 A left B join on and 后面的条件查出的两条记录变成一条,奈何发现还是有两条。后来发现 join
- 本文实例讲述了Python3.4列表、数组操作。分享给大家供大家参考,具体如下:python列表,数组类型要相同,python不需要指定数据
- 上一篇我们写了怎么将xmind转换成想要的excel格式,这篇再讲一下用Python自带的tkinter库设计一个简单的gui界面,让我们的
- 我最近在参与Python字节码相关的工作,想与大家分享一些这方面的经验。更准确的说,我正在参与2.6到2.7版本的CPython解释器字节码
- 超文本传输协议http构成了万维网的基础,它利用URI(统一资源标识符)来识别Internet上的数据,而指定文档地址的URI被称为URL(
- 芬兰数学家因卡拉花费3个月时间设计出的世界上迄今难度最大的数独。数独是 9 横 9 竖共有 81 个格子,同时又分为 9 个九宫格。规则很简
- 前面我们已经介绍了速度动画、透明度动画、多物体运动和任意值变化,并且我们在Javascript动画效果(二)中介绍到我们封装了一个简单的插件