server.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. package httpserver
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "encoding/hex"
  6. "fmt"
  7. "log"
  8. "net/http"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/danfragoso/pizzasql-next/pkg/executor"
  13. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  14. "github.com/danfragoso/pizzasql-next/pkg/parser"
  15. "github.com/danfragoso/pizzasql-next/pkg/storage"
  16. )
  17. const httpTransactionTTL = 30 * time.Minute
  18. // Config holds HTTP server configuration.
  19. type Config struct {
  20. Host string
  21. Port int
  22. ReadTimeout time.Duration
  23. WriteTimeout time.Duration
  24. MaxConnections int
  25. EnableCORS bool
  26. EnableAuth bool
  27. EnableCompression bool
  28. EnableLogging bool
  29. APIKeys []string
  30. TLSCertFile string
  31. TLSKeyFile string
  32. }
  33. // DefaultConfig returns default server configuration.
  34. func DefaultConfig() *Config {
  35. return &Config{
  36. Host: "localhost",
  37. Port: 8080,
  38. ReadTimeout: 30 * time.Second,
  39. WriteTimeout: 30 * time.Second,
  40. MaxConnections: 1000,
  41. EnableCORS: true,
  42. EnableAuth: false,
  43. EnableCompression: true,
  44. EnableLogging: true,
  45. APIKeys: []string{},
  46. }
  47. }
  48. // Server represents the HTTP API server.
  49. type Server struct {
  50. config *Config
  51. executor *executor.Executor // Default executor (for backward compatibility)
  52. schema *storage.SchemaManager
  53. dbManager *storage.DatabaseManager // Multi-database support
  54. server *http.Server
  55. stats *Stats
  56. // transactionExecutors holds a per-session executor for the HTTP
  57. // transaction endpoints (BEGIN/COMMIT/ROLLBACK), keyed by transaction ID.
  58. // This prevents transaction state and caches from being shared across
  59. // concurrent HTTP requests.
  60. transactionExecutorsMu sync.RWMutex
  61. transactionExecutors map[string]*transactionExecutor
  62. }
  63. type transactionExecutor struct {
  64. mu sync.Mutex
  65. exec *executor.Executor
  66. database string
  67. expiresAt time.Time
  68. }
  69. // Stats tracks server statistics.
  70. type Stats struct {
  71. QueriesExecuted int64
  72. QueriesSuccess int64
  73. QueriesError int64
  74. StartTime time.Time
  75. }
  76. // New creates a new HTTP server.
  77. // Deprecated: Use NewWithDatabaseManager for multi-database support.
  78. func New(config *Config, exec *executor.Executor, schema *storage.SchemaManager) *Server {
  79. if config == nil {
  80. config = DefaultConfig()
  81. }
  82. s := &Server{
  83. config: config,
  84. executor: exec,
  85. schema: schema,
  86. transactionExecutors: make(map[string]*transactionExecutor),
  87. stats: &Stats{
  88. StartTime: time.Now(),
  89. },
  90. }
  91. return s.init()
  92. }
  93. // NewWithDatabaseManager creates a new HTTP server with multi-database support.
  94. func NewWithDatabaseManager(config *Config, dbManager *storage.DatabaseManager) *Server {
  95. if config == nil {
  96. config = DefaultConfig()
  97. }
  98. defaultDB, _ := dbManager.GetDatabase("")
  99. var defaultExec *executor.Executor
  100. var defaultSchema *storage.SchemaManager
  101. if defaultDB != nil {
  102. defaultExec = executor.New(defaultDB.Schema, defaultDB.Table)
  103. defaultExec.SetNotificationHub(defaultDB.Hub)
  104. defaultExec.SyncCatalog()
  105. defaultSchema = defaultDB.Schema
  106. }
  107. s := &Server{
  108. config: config,
  109. executor: defaultExec,
  110. schema: defaultSchema,
  111. dbManager: dbManager,
  112. transactionExecutors: make(map[string]*transactionExecutor),
  113. stats: &Stats{
  114. StartTime: time.Now(),
  115. },
  116. }
  117. return s.init()
  118. }
  119. // init initializes the server routes and middleware.
  120. func (s *Server) init() *Server {
  121. mux := http.NewServeMux()
  122. // Apply middleware (order matters: logging -> auth -> cors -> compression -> handler)
  123. var handler http.Handler = mux
  124. if s.config.EnableCompression {
  125. handler = s.compressionMiddleware(handler)
  126. }
  127. if s.config.EnableCORS {
  128. handler = s.corsMiddleware(handler)
  129. }
  130. if s.config.EnableAuth {
  131. handler = s.authMiddleware(handler)
  132. }
  133. if s.config.EnableLogging {
  134. handler = s.loggingMiddleware(handler)
  135. }
  136. // Register routes
  137. mux.HandleFunc("/query", s.handleQuery)
  138. mux.HandleFunc("/execute", s.handleExecute)
  139. mux.HandleFunc("/schema/tables", s.handleSchemaTables)
  140. mux.HandleFunc("/schema/tables/", s.handleSchemaTable)
  141. mux.HandleFunc("/health", s.handleHealth)
  142. mux.HandleFunc("/version", s.handleVersion)
  143. mux.HandleFunc("/capabilities", s.handleCapabilities)
  144. mux.HandleFunc("/stats", s.handleStats)
  145. mux.HandleFunc("/metrics", s.handleMetrics)
  146. mux.HandleFunc("/transaction/begin", s.handleTransactionBegin)
  147. mux.HandleFunc("/transaction/commit", s.handleTransactionCommit)
  148. mux.HandleFunc("/transaction/rollback", s.handleTransactionRollback)
  149. mux.HandleFunc("/export", s.handleExport)
  150. mux.HandleFunc("/import", s.handleImport)
  151. s.server = &http.Server{
  152. Addr: fmt.Sprintf("%s:%d", s.config.Host, s.config.Port),
  153. Handler: handler,
  154. ReadTimeout: s.config.ReadTimeout,
  155. WriteTimeout: s.config.WriteTimeout,
  156. }
  157. return s
  158. }
  159. // Start starts the HTTP server.
  160. func (s *Server) Start() error {
  161. addr := s.server.Addr
  162. log.Printf("Starting HTTP server on http://%s", addr)
  163. if s.config.TLSCertFile != "" && s.config.TLSKeyFile != "" {
  164. return s.server.ListenAndServeTLS(s.config.TLSCertFile, s.config.TLSKeyFile)
  165. }
  166. return s.server.ListenAndServe()
  167. }
  168. // Shutdown gracefully shuts down the server.
  169. func (s *Server) Shutdown(ctx context.Context) error {
  170. log.Println("Shutting down HTTP server...")
  171. return s.server.Shutdown(ctx)
  172. }
  173. // Addr returns the server address.
  174. func (s *Server) Addr() string {
  175. return s.server.Addr
  176. }
  177. // getExecutorForDatabase returns a fresh executor for the specified database.
  178. // A new executor is created per call so transaction state, subquery caches, and
  179. // other mutable per-executor fields are never shared across concurrent HTTP
  180. // requests. If dbName is empty, the default database is used.
  181. func (s *Server) getExecutorForDatabase(dbName string) (*executor.Executor, *storage.SchemaManager, error) {
  182. // If no database manager, create a fresh executor from the default managers.
  183. if s.dbManager == nil {
  184. return s.executor.NewSessionExecutor(), s.schema, nil
  185. }
  186. dbInstance, err := s.dbManager.GetDatabase(dbName)
  187. if err != nil {
  188. return nil, nil, err
  189. }
  190. exec := executor.New(dbInstance.Schema, dbInstance.Table)
  191. exec.SetNotificationHub(dbInstance.Hub)
  192. exec.SyncCatalog()
  193. return exec, dbInstance.Schema, nil
  194. }
  195. // beginTransaction starts a transaction bound to a new session executor and
  196. // registers it under the returned transaction ID.
  197. func (s *Server) beginTransaction(dbName string) (string, *executor.Executor, error) {
  198. dbName = strings.TrimSpace(dbName)
  199. exec, _, err := s.getExecutorForDatabase(dbName)
  200. if err != nil {
  201. return "", nil, err
  202. }
  203. l := lexer.New("BEGIN")
  204. p := parser.New(l)
  205. stmt, _ := p.Parse()
  206. if _, err := exec.Execute(stmt); err != nil {
  207. return "", nil, err
  208. }
  209. idBytes := make([]byte, 16)
  210. if _, err := rand.Read(idBytes); err != nil {
  211. return "", nil, fmt.Errorf("generate transaction ID: %w", err)
  212. }
  213. txID := "tx-" + hex.EncodeToString(idBytes)
  214. now := time.Now()
  215. s.transactionExecutorsMu.Lock()
  216. for id, tx := range s.transactionExecutors {
  217. if !tx.expiresAt.After(now) {
  218. delete(s.transactionExecutors, id)
  219. }
  220. }
  221. s.transactionExecutors[txID] = &transactionExecutor{
  222. exec: exec,
  223. database: dbName,
  224. expiresAt: now.Add(httpTransactionTTL),
  225. }
  226. s.transactionExecutorsMu.Unlock()
  227. return txID, exec, nil
  228. }
  229. func (s *Server) getTransactionExecutor(txID, dbName string) (*transactionExecutor, bool) {
  230. dbName = strings.TrimSpace(dbName)
  231. s.transactionExecutorsMu.Lock()
  232. tx, ok := s.transactionExecutors[txID]
  233. if ok && !tx.expiresAt.After(time.Now()) {
  234. delete(s.transactionExecutors, txID)
  235. ok = false
  236. }
  237. if ok && tx.database == dbName {
  238. tx.mu.Lock()
  239. } else {
  240. ok = false
  241. }
  242. s.transactionExecutorsMu.Unlock()
  243. return tx, ok
  244. }
  245. // takeTransactionExecutor removes a session before COMMIT or ROLLBACK so it
  246. // cannot receive another request while its terminal command is running.
  247. func (s *Server) takeTransactionExecutor(txID, dbName string) (*transactionExecutor, bool) {
  248. dbName = strings.TrimSpace(dbName)
  249. s.transactionExecutorsMu.Lock()
  250. tx, ok := s.transactionExecutors[txID]
  251. if ok && !tx.expiresAt.After(time.Now()) {
  252. delete(s.transactionExecutors, txID)
  253. ok = false
  254. }
  255. if ok && tx.database == dbName {
  256. delete(s.transactionExecutors, txID)
  257. } else {
  258. ok = false
  259. }
  260. s.transactionExecutorsMu.Unlock()
  261. if ok {
  262. tx.mu.Lock()
  263. }
  264. return tx, ok
  265. }