Files
SQS-service/app/router/router.go
T
Naeel c3ba2dcae4 chore: initial import from sless/shared-sqs (v0.1.14)
- Standalone SQS-service repository
- Multi-tenant message queue service, AWS SQS compatible
- Based on GoAws, with mutable tenants, auth, WebUI, Redis persistence
- Ready for independent development and deployment
- See doc/ and README.md for architecture and usage
2026-04-10 16:47:27 +03:00

147 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
sqs "shared-sqs/app/gosqs"
"shared-sqs/app/interfaces"
"shared-sqs/app/tenant"
"shared-sqs/app/ui"
"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)
// UI public API — без auth, для встроенной console
adminHandler.RegisterPublicRoutes(r)
// UI console — встроенный SPA, публичный доступ
r.PathPrefix("/ui").Handler(http.StripPrefix("/ui", ui.Handler()))
// SQS API — tenant auth middleware оборачивает каждый handler отдельно.
// r.NewRoute().Subrouter() с Use() некорректно работает в gorilla/mux v1.8.0
// при пустом prefix — ответы теряются. Поэтому используем явную обёртку.
sqsAuth := auth.AuthMiddleware(tenantStore)
r.Handle("/", sqsAuth(http.HandlerFunc(actionHandler))).Methods("GET", "POST")
r.Handle("/{account}", sqsAuth(http.HandlerFunc(actionHandler))).Methods("GET", "POST")
r.Handle("/queue/{queueName}", sqsAuth(http.HandlerFunc(actionHandler))).Methods("GET", "POST")
r.Handle("/{account}/{queueName}", sqsAuth(http.HandlerFunc(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
}