// app/router/router.go // HTTP router для shared-sqs // Updated: 2026-04-09 — добавлены TenantStore, admin API, auth middleware package router import ( "encoding/json" "encoding/xml" "fmt" "io" "net/http" "strings" "shared-sqs/app/admin" "shared-sqs/app/auth" "shared-sqs/app/interfaces" sqs "shared-sqs/app/gosqs" "shared-sqs/app/tenant" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" ) // New — создаёт HTTP router с tenant auth и admin API func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler { r := mux.NewRouter() // /health — публичный, без auth r.HandleFunc("/health", health).Methods("GET") // Admin API — Bearer token auth, регистрируется через AdminHandler adminHandler := admin.NewHandler(tenantStore, adminToken) adminHandler.RegisterRoutes(r) // SQS API — tenant auth middleware sqsRouter := r.NewRoute().Subrouter() sqsRouter.Use(auth.AuthMiddleware(tenantStore)) sqsRouter.HandleFunc("/", actionHandler).Methods("GET", "POST") sqsRouter.HandleFunc("/{account}", actionHandler).Methods("GET", "POST") sqsRouter.HandleFunc("/queue/{queueName}", actionHandler).Methods("GET", "POST") sqsRouter.HandleFunc("/{account}/{queueName}", actionHandler).Methods("GET", "POST") return r } func encodeResponse(w http.ResponseWriter, req *http.Request, statusCode int, body interfaces.AbstractResponseBody) { protocol := resolveProtocol(req) switch protocol { case AwsJsonProtocol: w.Header().Set("x-amzn-RequestId", body.GetRequestId()) w.Header().Set("Content-Type", "application/x-amz-json-1.0") w.WriteHeader(statusCode) if body.GetResult() == nil { return } err := json.NewEncoder(w).Encode(body.GetResult()) if err != nil { log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body) http.Error(w, "General Error", http.StatusInternalServerError) } case AwsQueryProtocol: w.Header().Set("Content-Type", "application/xml") w.WriteHeader(statusCode) result, err := xml.Marshal(body) if err != nil { log.Errorf("Response Encoding Error: %v\nResponse: %+v", err, body) http.Error(w, "General Error", http.StatusInternalServerError) } _, _ = w.Write(result) } } // routingTableV1 — только SQS actions (SNS удалён) var routingTableV1 = map[string]func(r *http.Request) (int, interfaces.AbstractResponseBody){ "CreateQueue": sqs.CreateQueueV1, "ListQueues": sqs.ListQueuesV1, "GetQueueAttributes": sqs.GetQueueAttributesV1, "SetQueueAttributes": sqs.SetQueueAttributesV1, "SendMessage": sqs.SendMessageV1, "ReceiveMessage": sqs.ReceiveMessageV1, "ChangeMessageVisibility": sqs.ChangeMessageVisibilityV1, "DeleteMessage": sqs.DeleteMessageV1, "GetQueueUrl": sqs.GetQueueUrlV1, "PurgeQueue": sqs.PurgeQueueV1, "DeleteQueue": sqs.DeleteQueueV1, "SendMessageBatch": sqs.SendMessageBatchV1, "DeleteMessageBatch": sqs.DeleteMessageBatchV1, } func health(w http.ResponseWriter, req *http.Request) { w.WriteHeader(200) fmt.Fprint(w, "OK") } func actionHandler(w http.ResponseWriter, req *http.Request) { action := extractAction(req) log.WithFields(log.Fields{ "action": action, "url": req.URL, }).Debug("Handling URL request") jsonFn, ok := routingTableV1[action] if ok { statusCode, responseBody := jsonFn(req) encodeResponse(w, req, statusCode, responseBody) return } log.Warnf("Bad Request - Action: %s", action) w.WriteHeader(http.StatusBadRequest) io.WriteString(w, "Bad Request") } type AwsProtocol int const ( AwsJsonProtocol AwsProtocol = iota AwsQueryProtocol AwsProtocol = iota ) // extractAction — извлекает Action из запроса (Query Protocol или JSON Protocol) func extractAction(req *http.Request) string { protocol := resolveProtocol(req) switch protocol { case AwsJsonProtocol: action := req.Header.Get("X-Amz-Target") return strings.Split(action, ".")[1] case AwsQueryProtocol: return req.FormValue("Action") } return "" } // resolveProtocol — определяет протокол по Content-Type func resolveProtocol(req *http.Request) AwsProtocol { if req.Header.Get("Content-Type") == "application/x-amz-json-1.0" { return AwsJsonProtocol } return AwsQueryProtocol }