GO HTTP: Router、MiddleWare、Validator

386 阅读4分钟

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 image.png

自定义捕获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
  • 字符串约束
    • 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"

使用方法

  • 创建validatorval = 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.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())
              }