// Copyright 2021, Chef. All rights reserved. // https://github.com/q191201771/lal // // Use of this source code is governed by a MIT-style license // that can be found in the License file. // // Author: Chef (191201771@qq.com) package base import ( "crypto/tls" "errors" "net" "net/http" "reflect" "github.com/q191201771/naza/pkg/nazaerrors" ) // TODO(chef) // - 考虑移入naza中 // - 考虑增加一个pattern全部未命中的mux回调 var ( ErrAddrEmpty = errors.New("lal.base: http server addr empty") ErrMultiRegistForPattern = errors.New("lal.base: http server multiple registrations for pattern") ) const ( NetworkTcp = "tcp" ) type LocalAddrCtx struct { IsHttps bool Addr string CertFile string KeyFile string Network string } type HttpServerManager struct { addr2ServerCtx map[string]*ServerCtx } type ServerCtx struct { addrCtx LocalAddrCtx listener net.Listener httpServer http.Server mux *http.ServeMux pattern2Handler map[string]Handler } func NewHttpServerManager() *HttpServerManager { return &HttpServerManager{ addr2ServerCtx: make(map[string]*ServerCtx), } } type Handler func(http.ResponseWriter, *http.Request) // AddListen // // @param addrCtx IsHttps 是否为https // 注意,如果要为相同的路由同时绑定http和https,那么应该调用该函数两次,分别将该参数设置为true和false // Addr 监听地址,内部会为其建立监听 // http和https不能够使用相同的地址 // 注意,多次调用,允许使用相同的地址绑定不同的`pattern` // CertFile // KeyFile // Network 如果为空默认为NetworkTcp="tcp" // // @param pattern 必须以`/`开始,并以`/`结束 // 注意,如果是`/`,则在其他所有pattern都匹配失败后,做为兜底匹配成功 // 相同的pattern不能绑定不同的`handler`回调函数(显然,我们无法为相同的监听地址,相同的路径绑定多个回调函数) // func (s *HttpServerManager) AddListen(addrCtx LocalAddrCtx, pattern string, handler Handler) error { var ( ctx *ServerCtx mux *http.ServeMux ok bool ) if addrCtx.Addr == "" { return ErrAddrEmpty } // 监听地址是否已经创建过 ctx, ok = s.addr2ServerCtx[addrCtx.Addr] if !ok { l, err := listen(addrCtx) if err != nil { return err } mux = http.NewServeMux() ctx = &ServerCtx{ addrCtx: addrCtx, listener: l, httpServer: http.Server{ Handler: mux, }, mux: mux, pattern2Handler: make(map[string]Handler), } s.addr2ServerCtx[addrCtx.Addr] = ctx } // 路径相同,比较回调函数是否相同 // 如果回调函数也相同,意味着重复绑定,这种情况是允许的,忽略掉就行了 // 如果回调函数不同,返回错误 if prevHandler, ok := ctx.pattern2Handler[pattern]; ok { if reflect.ValueOf(prevHandler).Pointer() == reflect.ValueOf(handler).Pointer() { return nil } else { return ErrMultiRegistForPattern } } ctx.pattern2Handler[pattern] = handler ctx.mux.HandleFunc(pattern, handler) return nil } func (s *HttpServerManager) RunLoop() error { errChan := make(chan error, len(s.addr2ServerCtx)) for _, v := range s.addr2ServerCtx { go func(ctx *ServerCtx) { errChan <- ctx.httpServer.Serve(ctx.listener) _ = ctx.httpServer.Close() }(v) } // 阻塞直到接到第一个error return <-errChan } func (s *HttpServerManager) Dispose() error { var es []error for _, v := range s.addr2ServerCtx { err := v.httpServer.Close() es = append(es, err) } return nazaerrors.CombineErrors(es...) } // 为传入的`Addr`地址创建http或https监听 // func listen(ctx LocalAddrCtx) (net.Listener, error) { if ctx.Network == "" { ctx.Network = NetworkTcp } if !ctx.IsHttps { return net.Listen(ctx.Network, ctx.Addr) } cert, err := tls.LoadX509KeyPair(ctx.CertFile, ctx.KeyFile) if err != nil { return nil, err } tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} return tls.Listen(ctx.Network, ctx.Addr, tlsConfig) }