HTTP Router
- 即不用HandleFunc 实现路由,ListenAndServe 不用HandleFunc 实现路由时需要handler 参数
- 安装 go get -u github.com/julienschmidt/httprouter
- Router 实现了http.Handler 接口。实现了Handler 接口的对象可以注册到HTTP 服务端,为特定的路径及其子树提供服务。
type Handler interface { ServeHTTP(ResponseWriter, *Request) } router := httprouter.New() //创建一个新的Router http.ListenAndServe("127.0.0.1:5656", router) //监听 - 为各种request method 提供了便捷的路由方式。
- 支持restful 请求方式。
- 支持ServeFiles 访问静态文件。即访问路径返回静态页面
- 可以自定义捕获panic 的方法。
简单请求路由设置
- 由于代码大体相同,这里只以GET 进行演示
- (*httprouter.Router).GET(path string, handle httprouter.Handle)
- httprouter.Handle自行实现(里面内容可自由实现,本例只是简单实现)
func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) { fmt.Printf("request method: %s\n", r.Method) fmt.Printf("request body: ") io.Copy(os.Stdout, r.Body) //把r.Body流里的内容拷贝到os.Stdout流里 fmt.Println() w.Write([]byte("Hi boy, you request " + method)) }
restful 请求方式
- Server 端
func postParams(w http.ResponseWriter, r *http.Request, params httprouter.Params) { fmt.Printf("name:%s, type:%s, addr:%s\n", params.ByName("name"), params.ByName("type"), params.ByName("addr")) } router.POST("/user/:name/:type/*addr", postParams) //*号只能有一个,且必须放路径的末尾。 user后面是参数 - Client 端
- xiaoming为name,vip为type, bj,haidian为addr
resp, err := http.Post("http://127.0.0.1:5656/user/xiaoming/vip/bj/haidian", "text/plain", nil)
ServeFile 访问静态文件
router.ServeFiles("/file/*filepath", http.Dir("./http/static"))
- 必须以/*filepath结尾,为通配符
- 在浏览器中访问:127.0.0.1:5656/file/home.html 或 127.0.0.1:5656/file/readme.md
自定义捕获panic 方法
func panic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var arr []int
_ = arr[1] //数组越界panic
}
router.GET("/panic", panic)
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface{}) {
fmt.Println("panic发生了")
w.WriteHeader(http.StatusInternalServerError) //设置response status
fmt.Fprintf(w, "error:%s", err) //线上环境不要把原始错误信息返回给前端。测试阶段可以这么搞
}
中间件 MiddleWare
- 中间件的作用:将业务代码和非业务代码解耦。非业务代码指限流、超时控制、打日志等等。
- 中间件的实现原理:传入一个http.Handler,外面套上一些非业务功能代码,再返回一个http.Handler。支持中间件层层嵌套。
- 通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为Handler。
- 设计一个计时中间件和一个限制并发数量中间件
func timeMiddleWare(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { begin := time.Now() next.ServeHTTP(rw, r) timeElapsed := time.Since(begin) log.Printf("request %s use %d ms\n", r.URL.Path,timeElapsed.Milliseconds()) }) } var limitCh = make(chan struct{}, 100) //最多并发处理100个请求 func limitMiddleWare(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { limitCh <- struct{}{} //并发度达到100时就会阻塞 log.Printf("concurrence %d\n", len(limitCh)) next.ServeHTTP(rw, r) <-limitCh }) }
- 设计一个计时中间件和一个限制并发数量中间件
- home handle 创建
func home(w http.ResponseWriter, r *http.Request){ time.Sleep(150 * time.Millisecond) w.Write([]byte("welcome home!~")) }http.Handle("/", timeMiddleWare(limitMiddleWare(http.HandlerFunc(home))))- 监听端口:
http.ListenAndServe(":5656", nil);
请求校验 Validator
- 安装:go get github.com/go-playground/validator
约束
- 示例
type RegistRequest struct { UserName string `validate:"gt=0"` // >0 长度大于0 PassWord string `validate:"min=6,max=12"` //密码长度[6, 12] PassRepeat string `validate:"eqfield=PassWord"` //跨字段相等校验 Email string `validate:"email"` //需要满足email的格式 自定义的 } - 范围约束
- 对于字符串、切片、数组和map,约束其长度。对于数值,约束其取值。
len:等于参数值,例如len=10;max:小于等于参数值,例如max=10;min:大于等于参数值,例如min=10;eq:等于参数值,注意与len不同。对于字符串,eq约束字符串本身的值,而len约束字符串长度。例如eq=10;ne:不等于参数值,例如ne=10;gt:大于参数值,例如gt=10;gte:大于等于参数值,例如gte=10;lt:小于参数值,例如lt=10;lte:小于等于参数值,例如lte=10;oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,例如oneof=6 8。
- 跨字段约束
- 跨字段就在范围约束的基础上加field后缀。如
eqfield=PassWord - 如果还跨结构体就在跨字段的基础上在field前面加cs:
eqcsfield=PassWord
- 跨字段就在范围约束的基础上加field后缀。如
- 字符串约束
contains=:包含参数子串,例如contains=email;containsany:包含参数中任意的 UNICODE 字符,例如containsany=abcd;containsrune:包含参数表示的 rune 字符,例如containsrune=☻;excludes:不包含参数子串,例如excludes=email;excludesall:不包含参数中任意的 UNICODE 字符,例如excludesall=abcd;excludesrune:不包含参数表示的 rune 字符,excludesrune=☻;startswith:以参数子串为前缀,例如startswith=hello;endswith:以参数子串为后缀,例如endswith=bye。
- 唯一性uniq
- 对于数组和切片,约束没有重复的元素。
- 对于map,约束没有重复的value。
- 对于元素类型为结构体的切片,unique约束结构体对象的某个字段不重复,通过unqiue=field指定这个字段名。 Friends []User
validate:"unique=Name"
使用方法
- 创建validator
val = validator.New() - 自定义约束(即上一示例的Email变量):
//通过正则表达式验证该string是否为邮箱 func validateEmail(fl validator.FieldLevel) bool { input := fl.Field().String() if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, input); pass { return true } return false }- 注册一个自定义的validator:
(*validator.Validate).RegisterValidation(tag string, fn validator.Func, callValidationEvenIfNull ...bool) error val.RegisterValidation("school", validateEmail)school为tag
- 注册一个自定义的validator:
- 检验:
(*validator.Validate).Struct(s interface{}) error- Struct()返回的error分为两种类型:InvalidValidationError和ValidationErrors
//给Validate.Struct()函数传了一个非法的参数 invalid, ok := err.(*validator.InvalidValidationError) if ok { fmt.Println("param error:", invalid) return } //ValidationErrors是一个错误切片,它保存了每个字段违反的每个约束信息 validationErrs := err.(validator.ValidationErrors) for _, validationErr := range validationErrs { fmt.Printf("field %s 不满足条件 %s\n", validationErr.Field(), validationErr.Tag()) }
- Struct()返回的error分为两种类型:InvalidValidationError和ValidationErrors