package rest_http import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "log" "net/http" "strings" "sync/atomic" "time" mjson "git.ali33.ru/fcg-xvii/go-tools/json" "git.ali33.ru/fcg-xvii/rest" jwt "github.com/dgrijalva/jwt-go" ) func New(app rest.IApplication) *Rest { return &Rest{ app: app, } } type Rest struct { opened atomic.Bool server *http.Server app rest.IApplication } func (s *Rest) App() rest.IApplication { return s.app } func (s *Rest) TokenGenerate(m mjson.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.app.Secret()) return tokenString, err } // Listen start server in other goroutine func (s *Rest) Listen(timeout time.Duration) (err error) { if s.opened.Swap(true) { return errors.New("ErrAlreadyOpened") } ctx, _ := context.WithTimeout(context.Background(), timeout) go func() { mux := http.NewServeMux() mux.HandleFunc("/", s.handle) s.server = &http.Server{ Addr: s.app.Addr(), Handler: mux, } err = s.server.ListenAndServe() s.opened.Store(false) }() <-ctx.Done() return } // Close func (s *Rest) Close() error { if !s.opened.Load() { return errors.New("ErrNotOpened") } return s.server.Close() } func responseNotFound(w http.ResponseWriter) { w.WriteHeader(404) } func responseError(w http.ResponseWriter, err rest.IErrorArgs, code int) { w.WriteHeader(code) w.Write([]byte(err.Error())) } // handle func (s *Rest) handle(w http.ResponseWriter, r *http.Request) { // Инициализация restRequest rr := &Request{ Request: r, data: mjson.Map{}, files: make(map[string]io.ReadCloser), tokenGenerator: s.TokenGenerate, } // Парсим Bearer токен и извлекаем claims authHeader := r.Header.Get("Authorization") if authHeader != "" { if parts := strings.Split(authHeader, " "); len(parts) == 2 && parts[0] == "Bearer" { tokenString := parts[1] 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.app.Secret(), nil }) if err != nil { log.Printf("Failed to parse JWT: %s", err) http.Error(w, "Invalid token", http.StatusUnauthorized) return } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { rr.auth = mjson.Map(claims) } } } // Если это многокомпонентный запрос, обрабатываем файлы if strings.Index(r.Header.Get("Content-Type"), "multipart/form-data") == 0 { err := r.ParseMultipartForm(32 << 20) // max memory 32MB, после этого файлы будут сохранены во временных файлах log.Println(err) if err != nil { responseError(w, rest.ErrorMessage("ErrMultipartParse", err.Error()), 500) return } multiPartForm := r.MultipartForm data, check := multiPartForm.Value["data"] if check { err := json.NewDecoder(bytes.NewBuffer([]byte(data[0]))).Decode(&rr.data) if err != nil { responseError(w, rest.ErrorMessage("ErrMultipartDataParse", err.Error()), 500) return } } for filename, headers := range multiPartForm.File { for _, header := range headers { file, err := header.Open() if err != nil { err := rest.NewError( "ErrMultipartFileParse", mjson.Map{ "filename": filename, "message": err.Error(), }, ) responseError(w, err, 500) } rr.files[filename] = file } } defer rr.Close() } else { err := json.NewDecoder(r.Body).Decode(&rr.data) if err != nil { responseError(w, rest.ErrorMessage("ErrDataParse", err.Error()), 500) return } } // get command //command, check := s.commands.GetCommand(r.URL.Path) command, check := s.app.Executer(rr) if !check { responseNotFound(w) return } log.Println(rr.data) // serialize if err := rest.Serialize(rr.data, command); err != nil { responseError(w, err, 500) return } // validate if validator, check := command.(rest.IValidator); check { resp := validator.Validate(rr) if resp != nil { if err := resp.Send(w); err != nil { responseError(w, err, 500) } return } } // execute resp := command.Execute(rr) if err := resp.Send(w); err != nil { responseError(w, err, 500) } resp.Close() }