package rest import ( "context" "errors" "fmt" "net/http" "sync/atomic" "time" "git.ali33.ru/fcg-xvii/go-tools/json" "github.com/dgrijalva/jwt-go" ) // IServer реализует интерфейс сервера для рест апи type IServer interface { // Addr возвращеат адрес, который сервер слушает Addr() string // Secret возвращает секретный ключ, при помощи которого генерируются токены авторизации пользователей Secret() []byte // HandleFunc устанавливает функцию обработчик, которая будет вызвана по обращению к URL, удовлетворяющему pattern HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) // Listen запускает прослушивание сервера. Если не удалось успешно запустить прослушивание через // заданный временной интервал (timeout), будет возвращена ошибка Listen(timeout time.Duration, ctxParent context.Context) error // TokenGenerate - функция генерации токена авторизации TokenGenerate(m json.Map, expire int64) (string, error) // TokenParse - функция для расшифровки токена в карту значений TokenParse(token string) (json.Map, error) // Close останавливает прослушивание Close() error // Context возвращает контекст сервера Context() context.Context } // NewServer создает сервер для реализации рест апи func NewServer(addr string, secret []byte) IServer { return &Server{ secret: secret, server: &http.Server{ Addr: addr, Handler: http.NewServeMux(), }, } } func NewServerTLS(addr string, secret []byte, tlsKey, tlsCert string) IServer { serv := NewServer(addr, secret).(*Server) serv.tlsEnabled = true serv.tlsKey = tlsKey serv.tlsCert = tlsCert return serv } // Server реализует сервер для рест апи type Server struct { secret []byte server *http.Server opened atomic.Bool ctx context.Context cancel context.CancelFunc tlsEnabled bool tlsKey string tlsCert string } // Context возвращает контекст сервера func (s *Server) Context() context.Context { return s.ctx } // Close останавливает прослушивание func (s *Server) Close() error { if !s.opened.Load() { return errors.New("ErrNotOpened") } s.cancel() return nil } // Listen запускает прослушивание сервера. Если не удалось успешно запустить прослушивание через // заданный временной интервал (timeout), будет возвращена ошибка func (s *Server) Listen(timeout time.Duration, ctxParent context.Context) (err error) { if s.opened.Swap(true) { return errors.New("ErrAlreadyOpened") } ctx, _ := context.WithTimeout(ctxParent, timeout) go func() { var cancel context.CancelFunc s.ctx, cancel = context.WithCancel(ctxParent) if !s.tlsEnabled { err = s.server.ListenAndServe() } else { err = s.server.ListenAndServeTLS(s.tlsCert, s.tlsKey) } s.opened.Store(false) cancel() }() <-ctx.Done() if err == nil { go func() { <-s.ctx.Done() s.server.Close() }() } return } // TokenGenerate - функция генерации токена авторизации func (s *Server) TokenGenerate(m json.Map, expire int64) (string, error) { token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) for key, val := range m { claims[key] = val } if expire > 0 { claims["exp"] = time.Now().Add(time.Minute * 30).Unix() } tokenString, err := token.SignedString(s.Secret()) return tokenString, err } func (s *Server) TokenParse(tokenString string) (json.Map, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return s.Secret(), nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return json.Map(claims), nil } return nil, errors.New("invalid token") } // Addr возвращеат адрес, который сервер слушает func (s *Server) Addr() string { return s.server.Addr } // Secret возвращает секретный ключ, при помощи которого генерируются токены авторизации пользователей func (s *Server) Secret() []byte { return s.secret } // HandleFunc устанавливает функцию обработчик, которая будет вызвана по обращению к URL, удовлетворяющему pattern func (s *Server) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { s.server.Handler.(*http.ServeMux).HandleFunc(pattern, handler) }