2
0

server.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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.SyncCatalog()
  104. defaultSchema = defaultDB.Schema
  105. }
  106. s := &Server{
  107. config: config,
  108. executor: defaultExec,
  109. schema: defaultSchema,
  110. dbManager: dbManager,
  111. transactionExecutors: make(map[string]*transactionExecutor),
  112. stats: &Stats{
  113. StartTime: time.Now(),
  114. },
  115. }
  116. return s.init()
  117. }
  118. // init initializes the server routes and middleware.
  119. func (s *Server) init() *Server {
  120. mux := http.NewServeMux()
  121. // Apply middleware (order matters: logging -> auth -> cors -> compression -> handler)
  122. var handler http.Handler = mux
  123. if s.config.EnableCompression {
  124. handler = s.compressionMiddleware(handler)
  125. }
  126. if s.config.EnableCORS {
  127. handler = s.corsMiddleware(handler)
  128. }
  129. if s.config.EnableAuth {
  130. handler = s.authMiddleware(handler)
  131. }
  132. if s.config.EnableLogging {
  133. handler = s.loggingMiddleware(handler)
  134. }
  135. // Register routes
  136. mux.HandleFunc("/query", s.handleQuery)
  137. mux.HandleFunc("/execute", s.handleExecute)
  138. mux.HandleFunc("/schema/tables", s.handleSchemaTables)
  139. mux.HandleFunc("/schema/tables/", s.handleSchemaTable)
  140. mux.HandleFunc("/health", s.handleHealth)
  141. mux.HandleFunc("/stats", s.handleStats)
  142. mux.HandleFunc("/metrics", s.handleMetrics)
  143. mux.HandleFunc("/transaction/begin", s.handleTransactionBegin)
  144. mux.HandleFunc("/transaction/commit", s.handleTransactionCommit)
  145. mux.HandleFunc("/transaction/rollback", s.handleTransactionRollback)
  146. mux.HandleFunc("/export", s.handleExport)
  147. mux.HandleFunc("/import", s.handleImport)
  148. s.server = &http.Server{
  149. Addr: fmt.Sprintf("%s:%d", s.config.Host, s.config.Port),
  150. Handler: handler,
  151. ReadTimeout: s.config.ReadTimeout,
  152. WriteTimeout: s.config.WriteTimeout,
  153. }
  154. return s
  155. }
  156. // Start starts the HTTP server.
  157. func (s *Server) Start() error {
  158. addr := s.server.Addr
  159. log.Printf("Starting HTTP server on http://%s", addr)
  160. if s.config.TLSCertFile != "" && s.config.TLSKeyFile != "" {
  161. return s.server.ListenAndServeTLS(s.config.TLSCertFile, s.config.TLSKeyFile)
  162. }
  163. return s.server.ListenAndServe()
  164. }
  165. // Shutdown gracefully shuts down the server.
  166. func (s *Server) Shutdown(ctx context.Context) error {
  167. log.Println("Shutting down HTTP server...")
  168. return s.server.Shutdown(ctx)
  169. }
  170. // Addr returns the server address.
  171. func (s *Server) Addr() string {
  172. return s.server.Addr
  173. }
  174. // getExecutorForDatabase returns a fresh executor for the specified database.
  175. // A new executor is created per call so transaction state, subquery caches, and
  176. // other mutable per-executor fields are never shared across concurrent HTTP
  177. // requests. If dbName is empty, the default database is used.
  178. func (s *Server) getExecutorForDatabase(dbName string) (*executor.Executor, *storage.SchemaManager, error) {
  179. // If no database manager, create a fresh executor from the default managers.
  180. if s.dbManager == nil {
  181. return s.executor.NewSessionExecutor(), s.schema, nil
  182. }
  183. dbInstance, err := s.dbManager.GetDatabase(dbName)
  184. if err != nil {
  185. return nil, nil, err
  186. }
  187. exec := executor.New(dbInstance.Schema, dbInstance.Table)
  188. exec.SyncCatalog()
  189. return exec, dbInstance.Schema, nil
  190. }
  191. // beginTransaction starts a transaction bound to a new session executor and
  192. // registers it under the returned transaction ID.
  193. func (s *Server) beginTransaction(dbName string) (string, *executor.Executor, error) {
  194. dbName = strings.TrimSpace(dbName)
  195. exec, _, err := s.getExecutorForDatabase(dbName)
  196. if err != nil {
  197. return "", nil, err
  198. }
  199. l := lexer.New("BEGIN")
  200. p := parser.New(l)
  201. stmt, _ := p.Parse()
  202. if _, err := exec.Execute(stmt); err != nil {
  203. return "", nil, err
  204. }
  205. idBytes := make([]byte, 16)
  206. if _, err := rand.Read(idBytes); err != nil {
  207. return "", nil, fmt.Errorf("generate transaction ID: %w", err)
  208. }
  209. txID := "tx-" + hex.EncodeToString(idBytes)
  210. now := time.Now()
  211. s.transactionExecutorsMu.Lock()
  212. for id, tx := range s.transactionExecutors {
  213. if !tx.expiresAt.After(now) {
  214. delete(s.transactionExecutors, id)
  215. }
  216. }
  217. s.transactionExecutors[txID] = &transactionExecutor{
  218. exec: exec,
  219. database: dbName,
  220. expiresAt: now.Add(httpTransactionTTL),
  221. }
  222. s.transactionExecutorsMu.Unlock()
  223. return txID, exec, nil
  224. }
  225. func (s *Server) getTransactionExecutor(txID, dbName string) (*transactionExecutor, bool) {
  226. dbName = strings.TrimSpace(dbName)
  227. s.transactionExecutorsMu.Lock()
  228. tx, ok := s.transactionExecutors[txID]
  229. if ok && !tx.expiresAt.After(time.Now()) {
  230. delete(s.transactionExecutors, txID)
  231. ok = false
  232. }
  233. if ok && tx.database == dbName {
  234. tx.mu.Lock()
  235. } else {
  236. ok = false
  237. }
  238. s.transactionExecutorsMu.Unlock()
  239. return tx, ok
  240. }
  241. // takeTransactionExecutor removes a session before COMMIT or ROLLBACK so it
  242. // cannot receive another request while its terminal command is running.
  243. func (s *Server) takeTransactionExecutor(txID, dbName string) (*transactionExecutor, bool) {
  244. dbName = strings.TrimSpace(dbName)
  245. s.transactionExecutorsMu.Lock()
  246. tx, ok := s.transactionExecutors[txID]
  247. if ok && !tx.expiresAt.After(time.Now()) {
  248. delete(s.transactionExecutors, txID)
  249. ok = false
  250. }
  251. if ok && tx.database == dbName {
  252. delete(s.transactionExecutors, txID)
  253. } else {
  254. ok = false
  255. }
  256. s.transactionExecutorsMu.Unlock()
  257. if ok {
  258. tx.mu.Lock()
  259. }
  260. return tx, ok
  261. }