处理器和处理器函数
-
处理器是一个拥有
ServeHTTP
方法的接口接收两个参数:
ResponseWriter
接口和指向Request
结构的指针也就是,形如
ServeHTTP(http.ResponseWriter, *http.Request)
就是一个处理器 -
处理器函数是与处理器具有相同行为的函数,它们接收
ResponseWriter
和指向Request
结构的指针作为参数 -
更详细的:关于Handle、HandleFunc、Handler和HandlerFunc | ruomu (gitee.io)
通过下面代码直观的感受:
package main
import (
"fmt"
"net/http"
)
// 处理器和处理器函数
type HelloHandler struct {} // 这是一个处理器
type WorldHandler struct {} // 同样的也是一个处理器
// 为它们定义动作,实现 Handler 中的 ServeHttp 方法
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World!")
}
// 使用处理器函数 处理请求
func helloFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello function!")
}
func main() {
// 使用处理器
hello := HelloHandler{}
world := WorldHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
}
// 直接绑定 处理器
http.Handle("/hello", &hello)
http.Handle("/world", &world)
// 使用 处理函数
http.HandleFunc("/hellof", helloFunc)
server.ListenAndServe()
}
实际上,golang中的HandlerFunc
函数类型将带有正确签名的函数f
转换成一个带有方法f
的Handler
,也就是说golang自动用这个处理器函数生成了一个处理器。
但是,处理器函数并不能完全替代处理器,即使看起来整洁一些。
串联多个处理器和处理器函数
先看代码:
package main
import (
"fmt"
"net/http"
"reflect"
"runtime"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello!")
}
//
// @Name: log
// @Desc: 记录一个handler function的日志
// @Param: h
// @Return: http.HandlerFunc
// @Notice: 使用 runtime.FuncForPC 获取正在调用系统日志,也就是该函数的一些信息
//
func log(h http.HandlerFunc) http.HandlerFunc {
// 使用匿名函数进行返回
return func(w http.ResponseWriter, r *http.Request) {
name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
fmt.Println("Hanlder function called - " + name)
h(w, r)
}
}
//
// @Name: main
// @Desc: 串联两个处理器函数
// @Notice:
//
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
// log 返回一个 handlerfunc 所以它满足类型 handler
http.HandleFunc("/hello", log(hello))
server.ListenAndServe()
}
利用go中函数式编程的特性,将不同的函数进行串联,上面的log()
函数接收一个函数作为参数,返回另一个函数。也就可以说,这段代码将log()
和hello()
进行串联。
再比如说,我们还需要使用一个protect()
函数在调用传入的处理器之前去验证用户的身份,进行访问控制:
package chaining_func
import (
"fmt"
"net/http"
)
type HelloHandler struct {}
func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello!")
}
func log1(h http.Handler) http.Handler {
// 这里的 http.HandlerFunc 将一个 Function 转换成 Handler
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Handler called - %T\n", h)
h.ServeHTTP(w, r)
})
}
func protect(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Do some protect...")
h.ServeHTTP(w, r)
})
}
func chainingHandlers() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
hello := HelloHandler{}
http.Handle("/hello", protect(log1(hello)))
server.ListenAndServe()
}
从上面两个代码中,对处理handlerfunc
和handler
都进行了演示。对于处理器和处理器函数的关系还是值得好好思考下的。
函数
http.Server()
:对服务器进行详细配置,比如请求读取操作和响应写入操作的超时时间、日志记录器、服务器地址、处理器、TLS配置、连接状态…http.ListenAndServe()
:配置服务器地址、处理器http.ListenAndServeTLS()
:使用SSL证书和服务器私钥配置https服务
关于路由
/hello
和/hello/
有什么区别:前者会准确的进行路径匹配、后者可以用前缀进行匹配
值得注意的问题点
-
GET
和POST
都可以发送表单,但是由于GET
请求不会有请求主体,所以它的数据是包含在URL中传输的 -
几种不同的拿字段的方式: